From be9befe023b6393890454da221d33fd5ab8f95db Mon Sep 17 00:00:00 2001 From: HeuJung Date: Thu, 30 Jul 2026 20:00:23 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix(core,board,ecommerce,page,gdpr,basic):?= =?UTF-8?q?=20=EA=B9=8A=EC=9D=80=20OFFSET=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20raw/=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=EB=AA=85=20=EC=A0=84=EC=88=98=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 목록이 뒤쪽 페이지로 갈수록 느려지는 원인은 건너뛰는 행의 넓은 컬럼까지 읽는 것이었다. 지연 조인(inner 는 키 컬럼만, outer 는 이번 페이지 행만 전체 컬럼)과 정렬 컬럼 해석을 코어 공통 트레이트로 두고 코어 8 + 확장 6 저장소에 적용했다. 감사 지적 처리: - 그룹 쿼리의 총 건수 오산 — count 는 groupBy 를 벗기지 않아 첫 그룹의 행 수를 총 건수로 돌려준다. getCountForPagination 으로 교정. - audit 전수 스캔이 버퍼 한계로 한 번도 동작하지 않던 결함과, 확장 경로를 놓치던 정규식 5건을 함께 고쳤다. 그동안 번들 확장의 위반이 보이지 않았다. raw 20건을 빌더 표현으로 치환하고 하드코딩 테이블명 16건을 모델 유래로 바꿨다. 이 과정에서 마이페이지 「내가 댓글 단 글」 검색이 조인 별칭 충돌로 SQL 오류를 내던 결함이 해소됐다. 재발 차단용 룰 2종을 신설하고, 컬럼 프루닝 룰을 error 로 승격했다 (전역 위반 0건 — warning 으로 두면 "0 error" 판정을 통과하며 무음으로 재누적된다). 정렬 허용 목록은 FormRequest 게이트를 덮어야 한다. 좁으면 검증을 통과한 정렬이 422 도 로그도 없이 기본값으로 되돌아간다(스케줄 실행 이력의 종료시각·소요시간 정렬이 그랬다). 호출처를 스캔해 매핑 완전성까지 검사하는 계약 테스트로 전수 강제한다. 해외 배송지 결함도 함께 고쳤다 — 조회는 city/state/postal_code 로 내려주고 폼은 intl_* 로 제출하는 비대칭 때문에, 수정 시 입력칸이 비어 보이고 저장하면 기존 값이 유실됐다. 쿠폰 수정의 유효기간 미검증과 1인당 사용 제한 부분 수정 차단도 함께 수정. --- AGENTS.md | 16 +- CHANGELOG.md | 5 + .../Commands/BenchPaginationCommand.php | 556 ++++++++++++++ app/Repositories/ActivityLogRepository.php | 67 +- .../Concerns/PaginatesWithDeferredJoin.php | 258 +++++++ .../Concerns/ResolvesSortSpec.php | 105 +++ .../IdentityMessageDefinitionRepository.php | 21 +- app/Repositories/IdentityPolicyRepository.php | 1 + .../IdentityVerificationLogRepository.php | 19 +- app/Repositories/LanguagePackRepository.php | 21 +- app/Repositories/MenuRepository.php | 56 +- .../NotificationDefinitionRepository.php | 40 +- .../NotificationLogRepository.php | 41 +- app/Repositories/NotificationRepository.php | 30 +- app/Repositories/RoleRepository.php | 1 + .../ScheduleHistoryRepository.php | 61 +- app/Repositories/ScheduleRepository.php | 73 +- app/Repositories/UserRepository.php | 27 +- docs/backend/service-repository.md | 203 ++++++ docs/backend/validation.md | 63 ++ docs/cheatsheet.md | 11 + docs/database-guide.md | 19 + .../src/Repositories/MemoRepository.php | 5 +- modules/_bundled/sirsoft-board/CHANGELOG.md | 6 +- .../_bundled/sirsoft-board/docs/api/boards.md | 2 +- .../src/Http/Requests/UpdateBoardRequest.php | 15 +- .../src/Repositories/BoardRepository.php | 31 +- .../src/Repositories/CommentRepository.php | 31 +- .../src/Repositories/PostRepository.php | 171 +++-- .../src/Repositories/ReportRepository.php | 165 +++-- .../tests/Feature/BoardRequestTest.php | 120 ++- .../BoardRepositorySitemapTest.php | 8 +- .../CommentListDeferredJoinTest.php | 196 +++++ .../Repositories/PostListDeferredJoinTest.php | 196 +++++ .../UserCommentedActivityDeferredJoinTest.php | 167 +++++ .../_bundled/sirsoft-ecommerce/CHANGELOG.md | 7 + ...xes_to_ecommerce_product_reviews_table.php | 72 ++ .../sirsoft-ecommerce/docs/api/addresses.md | 2 + .../sirsoft-ecommerce/docs/api/orders.md | 2 +- .../docs/api/promotion-coupons.md | 2 + .../Controllers/Admin/ProductController.php | 10 +- .../Requests/Admin/ProductLogsRequest.php | 16 +- ...uest.php => ProductShowForCopyRequest.php} | 16 +- .../Requests/Admin/UpdateCouponRequest.php | 17 +- .../Concerns/MapsAddressBookFields.php | 16 +- .../User/UpdateUserAddressRequest.php | 142 +++- .../Contracts/ProductRepositoryInterface.php | 2 +- .../Repositories/CouponIssueRepository.php | 15 +- .../src/Repositories/CouponRepository.php | 39 +- .../ExtraFeeTemplateRepository.php | 30 +- .../Repositories/MileageBalanceRepository.php | 16 +- .../MileageTransactionRepository.php | 41 +- .../src/Repositories/OrderRepository.php | 115 ++- .../ProductCommonInfoRepository.php | 4 + .../Repositories/ProductInquiryRepository.php | 28 +- .../ProductNoticeTemplateRepository.php | 2 + .../src/Repositories/ProductRepository.php | 113 ++- .../Repositories/ProductReviewRepository.php | 66 +- .../ProductWishlistRepository.php | 2 + .../Repositories/ShippingPolicyRepository.php | 19 +- .../CouponUpdateConditionalValidationTest.php | 172 +++++ .../Admin/OrderListDeferredJoinTest.php | 199 +++++ ...serAddressUpdatePresenceValidationTest.php | 187 +++++ .../02_AddProductReviewSortIndexes.php | 78 ++ modules/_bundled/sirsoft-page/CHANGELOG.md | 1 + .../src/Repositories/PageRepository.php | 41 +- plugins/_bundled/sirsoft-gdpr/CHANGELOG.md | 5 +- plugins/_bundled/sirsoft-gdpr/plugin.json | 2 +- .../GdprPolicyVersionRepository.php | 14 +- .../GdprUserConsentHistoryRepository.php | 47 +- templates/_bundled/sirsoft-basic/CHANGELOG.md | 2 + .../partials/mypage/addresses/_list.json | 51 +- .../partials/shop/_modal_address_manage.json | 18 +- .../address-edit-intl-field-mapping.test.tsx | 112 +++ .../mypage-address-overseas-edit.spec.ts | 158 ++++ .../specs/pagination/deep-offset-list.spec.ts | 235 ++++++ .../DeferredJoinCallSiteContractTest.php | 161 ++++ .../PaginatesWithDeferredJoinTest.php | 690 ++++++++++++++++++ .../Concerns/ResolvesSortSpecTest.php | 143 ++++ .../SortableColumnsGateContractTest.php | 248 +++++++ tests/scenarios/deep-offset-pagination.yaml | 91 +++ 81 files changed, 5799 insertions(+), 457 deletions(-) create mode 100644 app/Console/Commands/BenchPaginationCommand.php create mode 100644 app/Repositories/Concerns/PaginatesWithDeferredJoin.php create mode 100644 app/Repositories/Concerns/ResolvesSortSpec.php create mode 100644 modules/_bundled/sirsoft-board/tests/Unit/Repositories/CommentListDeferredJoinTest.php create mode 100644 modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php create mode 100644 modules/_bundled/sirsoft-board/tests/Unit/Repositories/UserCommentedActivityDeferredJoinTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/database/migrations/2026_07_30_000001_add_created_at_indexes_to_ecommerce_product_reviews_table.php rename modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/{ShowProductForCopyRequest.php => ProductShowForCopyRequest.php} (71%) create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/CouponUpdateConditionalValidationTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserAddressUpdatePresenceValidationTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/02_AddProductReviewSortIndexes.php create mode 100644 templates/_bundled/sirsoft-basic/src/__tests__/layouts/address-edit-intl-field-mapping.test.tsx create mode 100644 templates/_bundled/sirsoft-basic/tests/Playwright/specs/mypage-address-overseas-edit.spec.ts create mode 100644 tests/Playwright/specs/pagination/deep-offset-list.spec.ts create mode 100644 tests/Unit/Repositories/Concerns/DeferredJoinCallSiteContractTest.php create mode 100644 tests/Unit/Repositories/Concerns/PaginatesWithDeferredJoinTest.php create mode 100644 tests/Unit/Repositories/Concerns/ResolvesSortSpecTest.php create mode 100644 tests/Unit/Repositories/Concerns/SortableColumnsGateContractTest.php create mode 100644 tests/scenarios/deep-offset-pagination.yaml diff --git a/AGENTS.md b/AGENTS.md index 9e9e4b5f..5da0d614 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -192,7 +192,7 @@ ### ① 코어 → 확장 동기화 (`requires.g7_version`) -- 트리거: 코어 공개 확장 표면(`app/Extension/Abstract*`, `HookManager`, `ExtensionManager`, `ModuleManager`, `PluginManager`, `TemplateManager`, `app/Contracts/Extension/**`, `app/Extension/Helpers/**`, `app/Seo/Contracts/**`, `app/ActivityLog/**` 공개 API, 루트 `CHANGELOG.md` Added/Changed/Removed) 수정 +- 트리거: 코어 공개 확장 표면(`app/Extension/Abstract*`, `HookManager`, `ExtensionManager`, `ModuleManager`, `PluginManager`, `TemplateManager`, `app/Contracts/Extension/**`, `app/Extension/Helpers/**`, `app/Repositories/Concerns/**`, `app/Seo/Contracts/**`, `app/ActivityLog/**` 공개 API, 루트 `CHANGELOG.md` Added/Changed/Removed) 수정 - 조치: 영향 받는 번들 확장의 `g7_version` 상향 + 각 확장 CHANGELOG 에 변경 기재 ### ② 확장 → 확장 동기화 (`dependencies.{modules|plugins}`) @@ -355,6 +355,20 @@ Icon 은 `` 글리프라 박스 크기가 곧 `font-size` 다. `w-N h-N` 은 > 상세: [validation.md "계층 리소스 순환 참조" / "배열 항목의 상위 스코프"](docs/backend/validation.md), [service-repository.md "중첩 리소스 스코프" / "설정 기반 한계값"](docs/backend/service-repository.md) +### 목록 조회 컬럼 프루닝과 지연 조인 + +| 금지 | 올바른 사용 | +|------|------------| +| `->paginate($perPage)` / `->paginate($perPage, ['*'])` (컬럼 목록 미지정) | 목록이 실제로 쓰는 컬럼만 명시. 깊은 OFFSET 이 가능한 목록은 `PaginatesWithDeferredJoin` | +| 요청 값에서 온 정렬 컬럼을 그대로 `orderBy` 에 전달 | `ResolvesSortSpec` 으로 닫힌 집합 해석 (방향만 검사하는 `in_array` 는 보호가 아니다) | +| 지연 조인의 `$query` 에 미리 `orderBy`/`with`/`select` 적용 | 필터/where 만 적용해 넘기고, 정렬·관계·컬럼은 trait 인자로 전달 | +| 목록 SELECT 에 `SUBSTRING(content, 1, N)` 을 두고 프루닝했다고 간주 | 오버플로 페이지 읽기가 그대로 발생 — 잘라내기는 outer(`$columns`)에서만 | +| 그룹 쿼리(`groupBy`)의 총 건수를 `count()` 로 계산 | `getCountForPagination()` (서브쿼리로 감싸 그룹 수를 센다) | +| raw SQL 안에 테이블명·별칭을 문자열로 조립 | 테이블명은 `(new Model)->getTable()`, 프리픽스는 `DB::getTablePrefix()`, 별칭은 빌더(`join($table.' as uc', …)`)가 만들게 | +| `whereRaw('1 = 0')` / `where($c, DB::raw("({$sub->toSql()})"))` + `mergeBindings` | `whereIn($key, [])` / `where($c, '=', $sub)` (빌더가 바인딩까지 처리) | + +> 상세: [service-repository.md "목록 조회 컬럼 프루닝과 지연 조인" / "정렬 컬럼 화이트리스트" / "허용되는 Raw 쿼리"](docs/backend/service-repository.md) + ### Listener 데이터 접근 | 금지 | 올바른 사용 | diff --git a/CHANGELOG.md b/CHANGELOG.md index ac8ac557..8284b4a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,8 +47,13 @@ - 사이트에서 쓰기로 한 언어의 언어팩을 한 번에 채워 넣는 기능을 추가했습니다. 한국어·영어 외 언어(일본어 등)는 설치되어 있어야 화면에 표시되는데, 설치가 빠져 있으면 오류 없이 한국어로 대체되어 운영자가 눈치채기 어려웠습니다. 이제 언어팩 관리 목록과 명령으로 "설치가 필요한 언어"를 한 번에 채울 수 있고, 여러 번 실행해도 안전합니다. - 언어팩 관리 목록이 "설치되어 있다고 표시되지만 실제 파일이 없어 다른 언어로 대체되는" 상태를 눈에 띄게 보여주고, 그 자리에서 다시 설치할 수 있는 버튼을 제공합니다. 예전에는 목록에 정상으로 보이는데도 실제로는 번역이 적용되지 않는 경우를 발견하기 어려웠습니다. 파일이 사라진 언어팩은 화면의 다시 설치 버튼으로도, 언어팩을 채우는 명령으로도 복구할 수 있습니다. +#### 목록 성능 + +- 목록 화면이 뒤쪽 페이지로 갈수록 느려지는 문제를 구조적으로 해결할 수 있도록, 확장이 함께 쓸 수 있는 공통 조회 방식을 코어에 추가했습니다. 목록을 두 단계(먼저 이번 페이지에 해당하는 항목만 추려내고, 그 항목에 대해서만 본문·상세 정보를 읽기)로 나눠 읽으므로 게시글·주문·로그가 수십만 건으로 늘어나도 마지막 페이지 조회 비용이 첫 페이지와 비슷하게 유지됩니다. 목록 정렬 기준을 미리 정해 둔 항목으로만 해석하는 공통 처리도 함께 제공하므로, 확장이 목록 조회를 직접 만들 때 정렬 처리를 매번 새로 구현하지 않아도 됩니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) + ### Changed +- 활동 로그·알림 발송 이력·본인인증 기록·스케줄 실행 이력·회원 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 기록의 본문·변경 내역까지 함께 읽어 느려졌지만, 이제 현재 페이지에 해당하는 기록만 상세 정보를 읽습니다. 화면에 보이는 내용은 이전과 동일합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 확장이 업로드 허용 형식을 넓힐 수 있도록 `core.attachment.allowed_extensions` 필터를 추가했습니다. 관리자 설정 목록을 기준으로, 확장이 자기 기능에 필요한 형식만 덧붙일 수 있습니다. - 확장이 검증 규칙을 추가할 때 쓰는 필터 이름 10개를 나머지 92개와 같은 규칙(`{대상}.{동작}_validation_rules`)으로 통일했습니다. 본인인증 메시지와 알림 관련 화면이 대상이며, 예전 이름(`...filter_index_rules` 형태)을 사용하던 확장은 새 이름으로 바꿔야 검증 규칙 추가가 계속 동작합니다. 코어와 기본 제공 확장에는 이 이름을 사용하던 곳이 없어 영향이 없습니다. - 같은 규칙에서 벗어나 있던 필터 이름 6개(플러그인 설정 저장, 비밀번호 재설정 토큰 확인, 비밀번호 확인, 변경사항 조회, 통합 검색, 프로필 사진 업로드)를 추가로 통일했습니다. 이번에는 예전 이름도 함께 계속 발행하므로 기존 확장은 수정 없이 그대로 동작하며, 예전 이름을 사용 중인 확장이 있으면 서버 기록에 한 번 안내가 남습니다. 새로 만드는 확장은 새 이름을 사용하세요. diff --git a/app/Console/Commands/BenchPaginationCommand.php b/app/Console/Commands/BenchPaginationCommand.php new file mode 100644 index 00000000..ad72b8cd --- /dev/null +++ b/app/Console/Commands/BenchPaginationCommand.php @@ -0,0 +1,556 @@ +, order: array, soft_delete: bool}> + */ + private function profiles(): array + { + return [ + 'board_posts' => [ + 'table' => 'board_posts', + 'columns' => ['id', 'board_id', 'title', 'author_name', 'view_count', 'is_notice', 'created_at'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + // 실제 목록은 게시판 하나를 조회한다. 필터 없이 재면 인덱스 선택이 달라져 + // 화면에서 일어나는 일과 다른 것을 재게 된다. + 'filters' => ['board_id' => 1], + 'seed_overrides' => ['board_id' => 1, 'is_notice' => 0], + ], + 'orders' => [ + 'table' => 'ecommerce_orders', + 'columns' => ['id', 'user_id', 'order_number', 'order_status', 'order_device', 'is_first_order', 'currency', 'currency_snapshot', 'total_amount', 'total_shipping_amount', 'total_paid_amount', 'total_cancelled_amount', 'total_refunded_amount', 'total_points_used_amount', 'total_earned_points_amount', 'mc_total_amount', 'mc_total_shipping_amount', 'ordered_at', 'created_at', 'updated_at', 'deleted_at'], + 'order' => [['ordered_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + // 아래 두 목록은 응답 계약상 넓은 컬럼(changes/properties, body)을 그대로 노출하므로 + // 컬럼 프루닝이 불가하다. 지연 조인만 적용했고, 계측 축도 select * vs select id 다. + 'activity_logs' => [ + 'table' => 'activity_logs', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => false, + ], + 'notification_logs' => [ + 'table' => 'notification_logs', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => false, + ], + 'users' => [ + 'table' => 'users', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + 'reports' => [ + 'table' => 'boards_reports', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + 'coupon_issues' => [ + 'table' => 'ecommerce_promotion_coupon_issues', + 'columns' => ['*'], + 'order' => [['issued_at', 'desc'], ['id', 'desc']], + 'soft_delete' => false, + ], + 'products' => [ + 'table' => 'ecommerce_products', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + 'product_inquiries' => [ + 'table' => 'ecommerce_product_inquiries', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + 'product_reviews' => [ + 'table' => 'ecommerce_product_reviews', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + 'pages' => [ + 'table' => 'pages', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + 'extra_fee_templates' => [ + 'table' => 'ecommerce_shipping_policy_extra_fee_templates', + 'columns' => ['*'], + 'order' => [['zipcode', 'asc'], ['id', 'asc']], + 'soft_delete' => false, + ], + ]; + } + + /** + * 커맨드를 실행합니다. + * + * @return int 종료 코드 + */ + public function handle(): int + { + $profiles = $this->profiles(); + + if ($this->option('list-profiles')) { + foreach ($profiles as $key => $profile) { + $this->line(sprintf('%-20s %s', $key, $profile['table'])); + } + + return self::SUCCESS; + } + + $key = (string) $this->option('table'); + + if (! isset($profiles[$key])) { + $this->error("등록되지 않은 프로파일: {$key} (--list-profiles 로 목록 확인)"); + + return self::FAILURE; + } + + $profile = $profiles[$key]; + + // 계측은 대량 시딩을 동반하므로 개발 DB 대신 폐기 가능한 DB 를 지정할 수 있어야 한다. + // config 가 캐시된 환경에서는 .env / --env 로 연결을 바꿀 수 없어 런타임 치환이 유일한 수단이다. + $database = (string) $this->option('database'); + + if ($database !== '') { + $connection = config('database.default'); + + config([ + "database.connections.{$connection}.database" => $database, + "database.connections.{$connection}.write.database" => $database, + "database.connections.{$connection}.read.database" => $database, + ]); + + DB::purge($connection); + } + + if (! Schema::hasTable($profile['table'])) { + $this->error("테이블이 없습니다: {$profile['table']} (해당 확장이 설치되어 있는지 확인)"); + + return self::FAILURE; + } + + // 계측 결과가 환경에 좌우되므로 실행 환경을 먼저 드러낸다 + $this->line(sprintf( + '환경: APP_ENV=%s / DB=%s / config:cache=%s', + config('app.env'), + config('database.default'), + app()->configurationIsCached() ? 'on' : 'off' + )); + + if ($this->option('fresh')) { + if (app()->environment('production')) { + $this->error('운영 환경에서는 --fresh 를 사용할 수 없습니다.'); + + return self::FAILURE; + } + + // 다른 테이블이 FK 로 참조하면 TRUNCATE 가 거부되므로 제약을 잠시 내린다 + Schema::withoutForeignKeyConstraints(function () use ($profile) { + DB::table($profile['table'])->truncate(); + }); + + $this->warn("비움: {$profile['table']}"); + } + + $seed = (int) $this->option('seed'); + + if ($seed > 0) { + $this->seedRows($profile['table'], $seed, $profile['seed_overrides'] ?? []); + } + + $columns = $this->existingColumns($profile['table'], $profile['columns']); + $order = $this->existingOrder($profile['table'], $profile['order']); + $offsets = $this->parseOffsets(); + $runs = max(1, (int) $this->option('runs')); + + $rows = []; + + foreach ($offsets as $offset) { + // 세 축: 컬럼 인자를 생략했을 때(=select *) / 목록 컬럼만 / 키 컬럼만. + // 첫 축이 프루닝 이전 상태, 셋째 축이 지연 조인의 inner 에 해당한다. + $allMs = $this->measure($profile, ['*'], $order, $offset, $runs); + $listMs = $this->measure($profile, $columns, $order, $offset, $runs); + $idOnlyMs = $this->measure($profile, ['id'], $order, $offset, $runs); + + $rows[] = [ + 'offset' => $offset, + 'all_ms' => $allMs, + 'list_ms' => $listMs, + 'id_only_ms' => $idOnlyMs, + 'ratio' => $idOnlyMs > 0 ? round($allMs / $idOnlyMs, 1) : null, + 'explain' => $this->option('explain') ? $this->explain($profile, $columns, $order, $offset) : null, + // 지연 조인의 inner 가 실제로 어떤 계획을 타는지가 인덱스 설계의 근거다 + 'explain_id_only' => $this->option('explain') ? $this->explain($profile, ['id'], $order, $offset) : null, + ]; + } + + if ($this->option('json')) { + $this->line(json_encode([ + 'profile' => $key, + 'table' => $profile['table'], + 'columns' => $columns, + 'runs' => $runs, + 'results' => $rows, + ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + + return self::SUCCESS; + } + + $this->table( + ['OFFSET', '전체 컬럼(ms)', '목록 컬럼(ms)', 'ID만(ms)', '전체÷ID'], + array_map(fn (array $row) => [ + number_format($row['offset']), + number_format($row['all_ms'], 1), + number_format($row['list_ms'], 1), + number_format($row['id_only_ms'], 1), + $row['ratio'] !== null ? $row['ratio'].'×' : '-', + ], $rows) + ); + + if ($this->option('explain')) { + foreach ($rows as $row) { + $this->newLine(); + $this->line("EXPLAIN @ OFFSET {$row['offset']} — 목록 컬럼"); + + foreach ($row['explain'] as $line) { + $this->line(' '.$line); + } + + $this->line("EXPLAIN @ OFFSET {$row['offset']} — ID 만 (지연 조인 inner)"); + + foreach ($row['explain_id_only'] as $line) { + $this->line(' '.$line); + } + } + } + + return self::SUCCESS; + } + + /** + * OFFSET 목록을 파싱합니다. + * + * @return array 오름차순 정렬된 OFFSET 목록 + */ + private function parseOffsets(): array + { + $offsets = array_map('intval', array_filter(explode(',', (string) $this->option('offsets')), 'strlen')); + sort($offsets); + + return $offsets === [] ? [0] : $offsets; + } + + /** + * 스키마에 실제로 존재하는 컬럼만 남깁니다. + * + * @param string $table 테이블명 + * @param array $columns 후보 컬럼 + * @return array 존재하는 컬럼 목록 + */ + private function existingColumns(string $table, array $columns): array + { + // ['*'] 는 "목록이 전 컬럼을 그대로 노출한다" 는 선언이다. 응답 계약상 넓은 컬럼을 + // 뺄 수 없는 목록(활동 로그의 changes, 알림 발송 이력의 body 등)이 여기 해당하며, + // 이 경우 계측의 비교 축은 "select * vs select id" 가 된다. + if ($columns === ['*']) { + return Schema::getColumnListing($table); + } + + $existing = array_values(array_filter($columns, fn (string $column) => Schema::hasColumn($table, $column))); + + return $existing === [] ? ['id'] : $existing; + } + + /** + * 스키마에 존재하는 정렬 컬럼만 남깁니다. + * + * @param string $table 테이블명 + * @param array $order 후보 정렬 + * @return array 적용 가능한 정렬 목록 + */ + private function existingOrder(string $table, array $order): array + { + $existing = array_values(array_filter($order, fn (array $spec) => Schema::hasColumn($table, $spec[0]))); + + return $existing === [] ? [['id', 'desc']] : $existing; + } + + /** + * 한 조합을 여러 번 실행해 중앙값(ms)을 돌려줍니다. + * + * DB::enableQueryLog() 는 오버헤드와 메모리 누적이 있어 쓰지 않고 직접 시간을 잰다. + * + * @param array $profile 프로파일 정의 + * @param array $columns select 컬럼 + * @param array $order 정렬 + * @param int $offset OFFSET + * @param int $runs 측정 횟수 + * @return float 중앙값 (밀리초) + */ + private function measure(array $profile, array $columns, array $order, int $offset, int $runs): float + { + // 첫 회는 캐시 워밍 성격이라 버린다 + $this->runQuery($profile, $columns, $order, $offset); + + $samples = []; + + for ($i = 0; $i < $runs; $i++) { + $start = microtime(true); + $this->runQuery($profile, $columns, $order, $offset); + $samples[] = (microtime(true) - $start) * 1000; + } + + sort($samples); + + return $samples[intdiv(count($samples), 2)]; + } + + /** + * 계측 대상 쿼리를 실행합니다. + * + * @param array $profile 프로파일 정의 + * @param array $columns select 컬럼 + * @param array $order 정렬 + * @param int $offset OFFSET + */ + private function runQuery(array $profile, array $columns, array $order, int $offset): void + { + $this->buildQuery($profile, $columns, $order, $offset)->get(); + } + + /** + * 계측 대상 쿼리를 조립합니다. + * + * @param array $profile 프로파일 정의 + * @param array $columns select 컬럼 + * @param array $order 정렬 + * @param int $offset OFFSET + * @return Builder 조립된 쿼리 + */ + private function buildQuery(array $profile, array $columns, array $order, int $offset) + { + $query = DB::table($profile['table'])->select($columns); + + if ($profile['soft_delete'] && Schema::hasColumn($profile['table'], 'deleted_at')) { + $query->whereNull('deleted_at'); + } + + foreach ($profile['filters'] ?? [] as $column => $value) { + if (Schema::hasColumn($profile['table'], $column)) { + $query->where($column, $value); + } + } + + foreach ($order as $spec) { + $query->orderBy($spec[0], $spec[1]); + } + + return $query->offset($offset)->limit(20); + } + + /** + * 실행 계획을 수집합니다. + * + * @param array $profile 프로파일 정의 + * @param array $columns select 컬럼 + * @param array $order 정렬 + * @param int $offset OFFSET + * @return array 실행 계획 요약 줄 목록 + */ + private function explain(array $profile, array $columns, array $order, int $offset): array + { + $query = $this->buildQuery($profile, $columns, $order, $offset); + + try { + $plan = DB::select('EXPLAIN '.$query->toSql(), $query->getBindings()); + } catch (\Throwable $e) { + return ['실행 계획 수집 실패: '.$e->getMessage()]; + } + + return array_map(function ($row) { + $row = (array) $row; + + return sprintf( + 'type=%s key=%s rows=%s filtered=%s extra=%s', + $row['type'] ?? '-', + $row['key'] ?? '-', + $row['rows'] ?? '-', + $row['filtered'] ?? '-', + $row['Extra'] ?? '-' + ); + }, $plan); + } + + /** + * 계측용 합성 행을 시딩합니다. + * + * 스키마를 introspect 해 NOT NULL + 기본값 없는 컬럼만 타입에 맞춰 채운다. 프로파일마다 + * 별도 시더를 두면 대상이 늘 때마다 시더가 따라 늘어나므로 일반화한다. + * + * @param string $table 대상 테이블 + * @param int $count 시딩 건수 + * @param array $overrides 컬럼별 고정값 (실제 조회 조건과 맞추기 위한 지정) + */ + private function seedRows(string $table, int $count, array $overrides = []): void + { + $columns = Schema::getColumns($table); + $fillable = array_values(array_filter( + $columns, + fn (array $column) => ! ($column['auto_increment'] ?? false) + && ! $column['nullable'] + && ($column['default'] === null) + )); + + $this->info("시딩: {$table} {$count} 건"); + $bar = $this->output->createProgressBar($count); + + $chunk = []; + $chunkSize = 500; + + for ($i = 1; $i <= $count; $i++) { + $row = []; + + foreach ($fillable as $column) { + $row[$column['name']] = array_key_exists($column['name'], $overrides) + ? $overrides[$column['name']] + : $this->synthesizeValue($column, $i); + } + + foreach ($overrides as $name => $value) { + if (! array_key_exists($name, $row) && Schema::hasColumn($table, $name)) { + $row[$name] = $value; + } + } + + $chunk[] = $row; + + if (count($chunk) >= $chunkSize) { + $this->insertSynthetic($table, $chunk); + $bar->advance(count($chunk)); + $chunk = []; + } + } + + if ($chunk !== []) { + $this->insertSynthetic($table, $chunk); + $bar->advance(count($chunk)); + } + + $bar->finish(); + $this->newLine(2); + } + + /** + * 합성 행을 삽입합니다. (외래키 제약 일시 해제) + * + * 합성 값은 실제 부모 행을 가리키지 않으므로 FK 를 그대로 두면 삽입이 거부된다. + * 본 커맨드가 재는 것은 대상 테이블의 OFFSET 스캔 비용이고 조인은 측정 대상이 아니므로, + * 부모 무결성 없이 행 수와 행 폭만 재현하면 목적에 충분하다. 폐기 가능한 계측용 DB + * (`--database`)에서만 쓰는 것을 전제로 한다. + * + * @param string $table 대상 테이블 + * @param array> $chunk 삽입할 행 묶음 + */ + private function insertSynthetic(string $table, array $chunk): void + { + Schema::withoutForeignKeyConstraints(function () use ($table, $chunk) { + DB::table($table)->insert($chunk); + }); + } + + /** + * 컬럼 타입에 맞는 합성 값을 만듭니다. + * + * @param array $column Schema::getColumns() 항목 + * @param int $index 행 인덱스 (고유값 생성용) + * @return mixed 합성 값 + */ + private function synthesizeValue(array $column, int $index): mixed + { + $type = strtolower((string) ($column['type_name'] ?? $column['type'] ?? 'varchar')); + + return match (true) { + str_contains($type, 'int') => $index, + str_contains($type, 'decimal'), str_contains($type, 'float'), str_contains($type, 'double') => 1000, + str_contains($type, 'bool'), str_contains($type, 'tinyint') => 0, + str_contains($type, 'json') => '{}', + // 시간 컬럼을 한 값으로 채우면 정렬이 전부 동률이 되어 filesort 가 지배해 버린다. + // 정렬 인덱스 효과를 재는 것이 목적이므로 행마다 값을 분산시킨다. + str_contains($type, 'date'), str_contains($type, 'time') => now()->subMinutes($index), + str_contains($type, 'text') => str_repeat('벤치마크 본문 ', 100), + default => $this->synthesizeString($column, $index), + }; + } + + /** + * 문자열 컬럼의 선언 길이에 맞는 합성 값을 만듭니다. + * + * 컬럼 길이를 무시하고 고정 길이 문자열을 넣으면 `varchar(10)` 류(신고 대상 타입, + * 우편번호, 로그 타입 등)에서 "Data too long" 으로 시딩 자체가 실패한다. + * + * @param array $column Schema::getColumns() 항목 + * @param int $index 행 인덱스 (고유값 생성용) + * @return string 합성 문자열 + */ + private function synthesizeString(array $column, int $index): string + { + $length = 40; + + // type 은 'varchar(10)' 형태로 선언 길이를 담고 있다 + if (preg_match('/\((\d+)\)/', (string) ($column['type'] ?? ''), $m) === 1) { + $length = max(1, (int) $m[1]); + } + + // 짧은 컬럼은 고유성 확보가 우선이라 인덱스 자체를 문자열로 쓴다 + if ($length <= 12) { + return substr((string) $index, -$length); + } + + return substr("bench-{$index}-".Str::random(8), 0, $length); + } +} diff --git a/app/Repositories/ActivityLogRepository.php b/app/Repositories/ActivityLogRepository.php index 513573b7..9eabb8c7 100644 --- a/app/Repositories/ActivityLogRepository.php +++ b/app/Repositories/ActivityLogRepository.php @@ -7,7 +7,10 @@ use App\Helpers\PermissionHelper; use App\Helpers\TimezoneHelper; use App\Models\ActivityLog; use App\Repositories\Concerns\HasMultipleSearchFilters; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; @@ -17,6 +20,25 @@ use Illuminate\Database\Eloquent\Model; class ActivityLogRepository implements ActivityLogRepositoryInterface { use HasMultipleSearchFilters; + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + /** + * 활동 로그 목록 정렬 허용 컬럼 + * + * 요청 값을 그대로 orderBy 에 넘기면 없는 컬럼으로 SQL 오류가 나거나 인덱스 없는 컬럼 + * 정렬을 강제할 수 있다. + * + * @var array + */ + private const SORTABLE_COLUMNS = [ + 'id', + 'created_at', + 'action', + 'log_type', + 'user_id', + ]; + /** * 특정 모델의 활동 로그를 페이지네이션하여 조회합니다. * @@ -26,8 +48,8 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface */ public function getPaginatedForModel(Model $model, array $filters = []): LengthAwarePaginator { - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query = ActivityLog::forModel($model)->orderBy('created_at', $sortOrder); + $sortOrder = $this->normalizeSortDirection($filters['sort_order'] ?? null); + $query = ActivityLog::forModel($model); if (isset($filters['action'])) { $query->action($filters['action']); @@ -37,7 +59,13 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface $query->byUser($filters['user_id']); } - return $query->paginate($filters['per_page'] ?? 15); + // 컬럼을 좁히지 않는 이유는 getPaginated() 와 같다 — 리소스가 변경 내역을 그대로 노출한다 + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [['column' => 'created_at', 'direction' => $sortOrder]], + perPage: (int) ($filters['per_page'] ?? 15), + ); } /** @@ -48,7 +76,8 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface */ public function getPaginated(array $filters = []): LengthAwarePaginator { - $query = ActivityLog::query()->with('user:id,uuid,name,email'); + // 관계는 지연 조인의 outer 에서만 로드한다 (inner 는 키 컬럼만 조회한다) + $query = ActivityLog::query(); // 스코프 기반 권한 필터링 (self: 본인 로그만, role: 소유역할 로그, null: 전체) PermissionHelper::applyPermissionScope($query, 'core.activities.read'); @@ -92,11 +121,18 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface $query->where('created_at', '<=', TimezoneHelper::fromSiteDateTime($filters['date_to'])); } - $sortBy = $filters['sort_by'] ?? 'created_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy($sortBy, $sortOrder); + $sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at'); - return $query->paginate($filters['per_page'] ?? 15); + // 목록 컬럼을 좁히지 않는 이유: 활동 로그 리소스는 변경 내역(mediumText `changes`)과 + // 부가 정보(`properties`)까지 그대로 노출하므로 컬럼을 빼면 응답 계약이 바뀐다. + // 지연 조인만으로도 이 넓은 컬럼들을 읽는 행 수가 이번 페이지 분량으로 고정된다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $sort, + perPage: (int) ($filters['per_page'] ?? 15), + relations: ['user:id,uuid,name,email'], + ); } /** @@ -105,10 +141,9 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface * action/description 필드는 영어 키를 저장하므로, * 검색어가 번역된 라벨과 일치하는 원본 키도 함께 검색합니다. * - * @param \Illuminate\Database\Eloquent\Builder $query 쿼리 빌더 - * @param string $search 검색어 - * @param string $searchType 검색 유형 (all, action, description, ip_address) - * @return void + * @param Builder $query 쿼리 빌더 + * @param string $search 검색어 + * @param string $searchType 검색 유형 (all, action, description, ip_address) */ private function applyBilingualSearch($query, string $search, string $searchType): void { @@ -146,7 +181,7 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface /** * 번역된 액션 라벨로 원본 action 값을 역추적합니다. * - * @param string $searchLower 소문자 검색어 + * @param string $searchLower 소문자 검색어 * @return array 일치하는 action 값 목록 */ private function findActionsByTranslatedLabel(string $searchLower): array @@ -179,7 +214,7 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface /** * 번역된 설명 텍스트로 원본 description_key를 역추적합니다. * - * @param string $searchLower 소문자 검색어 + * @param string $searchLower 소문자 검색어 * @return array 일치하는 description_key 목록 */ private function findDescriptionKeysByTranslatedText(string $searchLower): array @@ -200,7 +235,7 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface /** * 활동 로그를 삭제합니다. * - * @param int $id 삭제할 활동 로그 ID + * @param int $id 삭제할 활동 로그 ID * @return bool 삭제 성공 여부 */ public function delete(int $id): bool @@ -211,7 +246,7 @@ class ActivityLogRepository implements ActivityLogRepositoryInterface /** * 여러 활동 로그를 일괄 삭제합니다. * - * @param array $ids 삭제할 활동 로그 ID 목록 + * @param array $ids 삭제할 활동 로그 ID 목록 * @return int 삭제된 건수 */ public function deleteMany(array $ids): int diff --git a/app/Repositories/Concerns/PaginatesWithDeferredJoin.php b/app/Repositories/Concerns/PaginatesWithDeferredJoin.php new file mode 100644 index 00000000..90195eac --- /dev/null +++ b/app/Repositories/Concerns/PaginatesWithDeferredJoin.php @@ -0,0 +1,258 @@ + $sort 정렬 스펙 + * @param int $perPage 페이지당 건수 + * @param int|null $page 현재 페이지 (null = 요청에서 해석) + * @param array $relations outer 전용 eager load 관계 + * @param array $withCount outer 전용 관계 카운트 + * @param string $keyName 키 컬럼명 (기본 id) + * @param int|null $total 전체 건수 (null = COUNT 수행, 값 지정 시 캐시 total 사용) + * @param bool $simple true = COUNT 없이 다음 페이지 유무만 판정 + * @param bool $preserveIdOrder true = inner 가 정한 ID 순서를 outer 에서 그대로 복원 + * @param string $pageName 페이지 쿼리 파라미터명 + * @param Closure|null $outerUsing outer 에만 적용할 조인/집계 (inner 에서는 실행되지 않는다) + * @return PaginatorContract 페이지네이터 ($simple=true 면 Paginator, 아니면 LengthAwarePaginator) + */ + protected function paginateWithDeferredJoin( + Builder $query, + array $columns, + array $sort, + int $perPage, + ?int $page = null, + array $relations = [], + array $withCount = [], + string $keyName = 'id', + ?int $total = null, + bool $simple = false, + bool $preserveIdOrder = false, + string $pageName = 'page', + ?Closure $outerUsing = null, + ): PaginatorContract { + $page = $page ?: Paginator::resolveCurrentPage($pageName); + $page = max(1, $page); + + $sort = $this->normalizeSortSpecForKey($sort, $keyName); + + // inner — 키 컬럼만 조회한다. with() 를 비우는 이유 두 가지: + // (a) 성능 — 관계 쿼리가 inner/outer 두 번 실행된다 + // (b) 정합성 — inner 는 키 컬럼만 select 하므로 관계 FK 가 없어 eager load 가 깨진다 + // whereHas 는 결과 집합을 결정하는 조건이라 setEagerLoads 로 지워지지 않는다 (의도된 구분). + $inner = (clone $query)->setEagerLoads([]); + + if (! $simple && $total === null) { + // Laravel 의 paginate() 와 같은 집계 경로를 쓴다. count() 는 groupBy/having 이 있는 + // 쿼리에서 `select count(*) ... group by ...` 를 그대로 실행해 **첫 그룹의 행 수**를 + // 총 건수로 돌려준다. getCountForPagination() 은 그룹 쿼리를 서브쿼리로 감싸므로 + // 그룹 수가 정확히 나온다. toBase() 는 글로벌 스코프를 적용한 뒤 기반 쿼리를 주므로 + // 소프트 삭제/필터 조건이 집계에서 누락되지 않는다. + $total = (clone $inner)->toBase()->getCountForPagination(); + } + + $this->applySortSpec($inner, $sort); + + // simple 모드는 다음 페이지 유무 판정을 위해 한 건을 더 읽는다 (Paginator 가 잘라낸다). + // forPage() 를 쓰면 offset 이 (page-1) * (perPage+1) 로 계산돼 페이지가 넘어갈수록 + // 건너뛰는 행이 어긋나므로, offset 은 perPage 기준으로 직접 지정한다. + $fetchCount = $simple ? $perPage + 1 : $perPage; + + $ids = $inner + ->select($inner->getModel()->qualifyColumn($keyName)) + ->offset(($page - 1) * $perPage) + ->limit($fetchCount) + ->pluck($keyName); + + $items = $ids->isEmpty() + ? new Collection + : $this->fetchDeferredJoinRows($query, $columns, $sort, $ids->all(), $relations, $withCount, $keyName, $preserveIdOrder, $outerUsing); + + $options = [ + 'path' => Paginator::resolveCurrentPath(), + 'pageName' => $pageName, + ]; + + if ($simple) { + return new Paginator($items, $perPage, $page, $options); + } + + return new LengthAwarePaginator($items, $total ?? $items->count(), $perPage, $page, $options); + } + + /** + * outer 쿼리를 실행해 실제 행을 조회합니다. + * + * @param Builder $query 필터가 적용된 원본 쿼리 (soft delete 스코프 보존을 위해 재사용) + * @param array $columns select 목록 + * @param array $sort 정렬 스펙 + * @param array $ids 이번 페이지의 키 값 목록 + * @param array $relations eager load 관계 + * @param array $withCount 관계 카운트 + * @param string $keyName 키 컬럼명 + * @param bool $preserveIdOrder ID 순서 그대로 복원 여부 + * @param Closure|null $outerUsing outer 전용 조인/집계 + * @return Collection 조회된 행 컬렉션 + */ + private function fetchDeferredJoinRows( + Builder $query, + array $columns, + array $sort, + array $ids, + array $relations, + array $withCount, + string $keyName, + bool $preserveIdOrder, + ?Closure $outerUsing = null, + ): Collection { + $outer = (clone $query)->setEagerLoads([]); + $qualifiedKey = $outer->getModel()->qualifyColumn($keyName); + + $outer->whereIn($qualifiedKey, $ids); + + // 결과 집합을 좁히지 않는 조인/집계(최근 댓글 서브쿼리, 상관 COUNT 등)는 여기서만 + // 붙인다. inner 에 두면 건너뛸 행 전체에 대해 실행되므로 OFFSET 이 깊어질수록 그 + // 비용이 그대로 누적된다. whereIn 뒤에 적용하므로 이번 페이지 건수에만 실행된다. + if ($outerUsing !== null) { + $outerUsing($outer); + } + + if (! empty($relations)) { + $outer->with($relations); + } + + if (! empty($withCount)) { + $outer->withCount($withCount); + } + + if ($preserveIdOrder) { + $this->applyIdOrder($outer, $qualifiedKey, $ids); + } else { + // 정렬 스펙에 키 컬럼이 포함돼 전순서가 보장되므로, 같은 스펙을 다시 적용하면 + // inner 가 정한 순서와 결과가 동일하다 (MySQL 전용 FIELD() 가 필요 없다). + $this->applySortSpec($outer, $sort); + } + + return $outer->get($columns); + } + + /** + * 정렬 스펙을 쿼리에 적용합니다. + * + * @param Builder $query 대상 쿼리 + * @param array $sort 정렬 스펙 + */ + private function applySortSpec(Builder $query, array $sort): void + { + foreach ($sort as $spec) { + $query->orderBy($spec['column'], $spec['direction']); + } + } + + /** + * 정렬 스펙 끝에 키 컬럼을 덧붙여 전순서를 만듭니다. + * + * 정렬 컬럼에 중복이 많으면(상태/조회수/평점 등) 동률 행의 순서가 실행마다 달라져 페이지 + * 경계에서 행이 중복되거나 누락된다. inner 와 outer 가 같은 순서를 갖도록 하는 전제이기도 하다. + * + * @param array $sort 정렬 스펙 + * @param string $keyName 키 컬럼명 + * @return array 키 컬럼이 보장된 정렬 스펙 + */ + private function normalizeSortSpecForKey(array $sort, string $keyName): array + { + $normalized = []; + + foreach ($sort as $spec) { + $column = $spec['column'] ?? null; + + if (! is_string($column) || $column === '') { + continue; + } + + $normalized[] = [ + 'column' => $column, + 'direction' => strtolower($spec['direction'] ?? 'desc') === 'asc' ? 'asc' : 'desc', + ]; + } + + foreach ($normalized as $spec) { + // 이미 키 컬럼으로 정렬한다면 그것으로 전순서가 성립한다 + if ($spec['column'] === $keyName || str_ends_with($spec['column'], '.'.$keyName)) { + return $normalized; + } + } + + $lastDirection = $normalized === [] ? 'desc' : end($normalized)['direction']; + + $normalized[] = ['column' => $keyName, 'direction' => $lastDirection]; + + return $normalized; + } + + /** + * inner 가 정한 ID 순서를 outer 에서 그대로 복원합니다. + * + * outer 에 존재하지 않는 표현식(집계/조인 서브쿼리 등)으로 정렬한 경우에만 사용한다. + * MySQL 전용 `FIELD()` 대신 표준 SQL `CASE WHEN` 을 쓴다 (DB 비의존 규정). + * + * @param Builder $query 대상 쿼리 + * @param string $qualifiedKey 테이블명이 붙은 키 컬럼 + * @param array $ids 복원할 순서의 키 값 목록 + */ + private function applyIdOrder(Builder $query, string $qualifiedKey, array $ids): void + { + $wrapped = $query->getQuery()->getGrammar()->wrap($qualifiedKey); + + $cases = []; + $bindings = []; + + foreach (array_values($ids) as $index => $id) { + $cases[] = "WHEN {$wrapped} = ? THEN {$index}"; + $bindings[] = $id; + } + + $query->orderByRaw('CASE '.implode(' ', $cases).' ELSE '.count($ids).' END', $bindings); + } +} diff --git a/app/Repositories/Concerns/ResolvesSortSpec.php b/app/Repositories/Concerns/ResolvesSortSpec.php new file mode 100644 index 00000000..91639093 --- /dev/null +++ b/app/Repositories/Concerns/ResolvesSortSpec.php @@ -0,0 +1,105 @@ + $allowedColumns 허용 정렬 컬럼 화이트리스트 + * @param string $defaultColumn 화이트리스트에 없거나 미전송일 때 사용할 컬럼 + * @param string $defaultDirection 기본 정렬 방향 (asc|desc) + * @param array $columnAliases 요청 값 → 실제 컬럼 매핑 (예: ['author' => 'author_name']) + * @param string $columnKey 정렬 컬럼이 담긴 필터 키 + * @param string $directionKey 정렬 방향이 담긴 필터 키 + * @return array 정렬 스펙 배열 + */ + protected function resolveSortSpec( + array $filters, + array $allowedColumns, + string $defaultColumn, + string $defaultDirection = 'desc', + array $columnAliases = [], + string $columnKey = 'sort_by', + string $directionKey = 'sort_order', + ): array { + $column = $this->normalizeSortValue($filters[$columnKey] ?? null); + $direction = $this->normalizeSortValue($filters[$directionKey] ?? null); + + if ($column !== null && isset($columnAliases[$column])) { + $column = $columnAliases[$column]; + } + + if ($column === null || ! in_array($column, $allowedColumns, true)) { + $column = $defaultColumn; + } + + return [[ + 'column' => $column, + 'direction' => $this->normalizeSortDirection($direction, $defaultDirection), + ]]; + } + + /** + * 정렬 방향 문자열을 asc/desc 로 정규화합니다. + * + * @param string|null $direction 요청 값 + * @param string $default 허용 값이 아닐 때 사용할 기본 방향 + * @return string asc 또는 desc + */ + protected function normalizeSortDirection(?string $direction, string $default = 'desc'): string + { + $normalized = strtolower((string) $direction); + + if (in_array($normalized, ['asc', 'desc'], true)) { + return $normalized; + } + + $fallback = strtolower($default); + + return in_array($fallback, ['asc', 'desc'], true) ? $fallback : 'desc'; + } + + /** + * 정렬 관련 요청 값을 문자열로 정규화합니다. (Enum/빈 문자열 흡수) + * + * @param mixed $value 요청 값 (문자열 또는 BackedEnum) + * @return string|null 정규화된 문자열 (값이 없으면 null) + */ + private function normalizeSortValue(mixed $value): ?string + { + if ($value instanceof BackedEnum) { + $value = $value->value; + } + + if (! is_string($value) && ! is_int($value)) { + return null; + } + + $value = trim((string) $value); + + return $value === '' ? null : $value; + } +} diff --git a/app/Repositories/IdentityMessageDefinitionRepository.php b/app/Repositories/IdentityMessageDefinitionRepository.php index da177b78..7d2ae18a 100644 --- a/app/Repositories/IdentityMessageDefinitionRepository.php +++ b/app/Repositories/IdentityMessageDefinitionRepository.php @@ -4,11 +4,25 @@ namespace App\Repositories; use App\Contracts\Repositories\IdentityMessageDefinitionRepositoryInterface; use App\Models\IdentityMessageDefinition; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Database\Eloquent\Collection; use Illuminate\Pagination\LengthAwarePaginator; class IdentityMessageDefinitionRepository implements IdentityMessageDefinitionRepositoryInterface { + use ResolvesSortSpec; + + /** 허용 정렬 컬럼 (AdminIdentityMessageDefinitionIndexRequest 와 동일 집합) */ + private const SORTABLE_COLUMNS = [ + 'id', + 'provider_id', + 'scope_type', + 'scope_value', + 'is_active', + 'created_at', + 'updated_at', + ]; + /** * ID로 메시지 정의 조회. * @@ -180,10 +194,11 @@ class IdentityMessageDefinitionRepository implements IdentityMessageDefinitionRe }); } - $sortBy = $filters['sort_by'] ?? 'id'; - $sortOrder = $filters['sort_order'] ?? 'asc'; - $query->orderBy($sortBy, $sortOrder); + foreach ($this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'id', 'asc') as $sort) { + $query->orderBy($sort['column'], $sort['direction']); + } + // audit:allow repository-paginate-column-pruning reason: 본인인증 메시지 정의 테이블 — 정의 수가 고정이고 넓은 컬럼이 없다 return $query->paginate($perPage); } } diff --git a/app/Repositories/IdentityPolicyRepository.php b/app/Repositories/IdentityPolicyRepository.php index a6b7611b..410494d5 100644 --- a/app/Repositories/IdentityPolicyRepository.php +++ b/app/Repositories/IdentityPolicyRepository.php @@ -252,6 +252,7 @@ class IdentityPolicyRepository implements IdentityPolicyRepositoryInterface }); } + // audit:allow repository-paginate-column-pruning reason: 본인인증 정책 정의 테이블 — 정책 수가 고정이고 넓은 컬럼이 없다 return $query->orderByDesc('created_at')->orderByDesc('id')->paginate($perPage); } diff --git a/app/Repositories/IdentityVerificationLogRepository.php b/app/Repositories/IdentityVerificationLogRepository.php index 325222c4..6e51bd4d 100644 --- a/app/Repositories/IdentityVerificationLogRepository.php +++ b/app/Repositories/IdentityVerificationLogRepository.php @@ -4,7 +4,10 @@ namespace App\Repositories; use App\Contracts\Repositories\IdentityVerificationLogRepositoryInterface; use App\Enums\IdentityVerificationStatus; +use App\Models\IdentityPolicy; use App\Models\IdentityVerificationLog; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Support\Carbon; /** @@ -14,6 +17,8 @@ use Illuminate\Support\Carbon; */ class IdentityVerificationLogRepository implements IdentityVerificationLogRepositoryInterface { + use PaginatesWithDeferredJoin; + /** * 검증 로그를 생성합니다. * @@ -130,7 +135,7 @@ class IdentityVerificationLogRepository implements IdentityVerificationLogReposi * * @param array $filters 검색 필터 * @param int $perPage 페이지당 항목 수 - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 페이지네이터 + * @return LengthAwarePaginator 페이지네이터 */ public function search(array $filters, int $perPage = 20) { @@ -158,7 +163,7 @@ class IdentityVerificationLogRepository implements IdentityVerificationLogReposi if (! empty($filters['source_type'])) { $query->whereIn('origin_policy_key', function ($q) use ($filters) { $q->select('key') - ->from('identity_policies') + ->from((new IdentityPolicy)->getTable()) ->where('source_type', $filters['source_type']); if (! empty($filters['source_identifier'])) { $q->where('source_identifier', $filters['source_identifier']); @@ -202,9 +207,15 @@ class IdentityVerificationLogRepository implements IdentityVerificationLogReposi ? $filters['sort_by'] : 'created_at'; $sortOrder = ($filters['sort_order'] ?? 'desc') === 'asc' ? 'asc' : 'desc'; - $query->orderBy($sortBy, $sortOrder); - return $query->paginate($perPage); + // 본인인증 로그는 계속 쌓이는 이력이라 뒤쪽 페이지에서 OFFSET 비용이 커진다. + // 응답이 요청/응답 payload 를 그대로 노출하므로 컬럼은 좁히지 않고 지연 조인만 적용한다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [['column' => $sortBy, 'direction' => $sortOrder]], + perPage: $perPage, + ); } /** diff --git a/app/Repositories/LanguagePackRepository.php b/app/Repositories/LanguagePackRepository.php index 1ee83b3d..3e29ff0e 100644 --- a/app/Repositories/LanguagePackRepository.php +++ b/app/Repositories/LanguagePackRepository.php @@ -4,10 +4,13 @@ namespace App\Repositories; use App\Contracts\Repositories\LanguagePackRepositoryInterface; use App\Enums\LanguagePackScope; +use App\Enums\LanguagePackSourceType; use App\Enums\LanguagePackStatus; +use App\Enums\TextDirection; use App\Models\LanguagePack; use App\Repositories\Concerns\HasMultipleSearchFilters; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; @@ -127,6 +130,7 @@ class LanguagePackRepository implements LanguagePackRepositoryInterface */ public function paginate(array $filters = [], int $perPage = 20): LengthAwarePaginator { + // audit:allow repository-paginate-column-pruning reason: 언어팩 정의 테이블 — 설치된 팩 수만큼만 존재하고 넓은 컬럼이 없다 return $this->buildFilteredQuery($filters)->paginate($perPage); } @@ -148,9 +152,9 @@ class LanguagePackRepository implements LanguagePackRepositoryInterface * (실제 미설치 번들은 Service 계층에서 합쳐집니다). * * @param array $filters 필터 조건 - * @return \Illuminate\Database\Eloquent\Builder 정렬까지 적용된 쿼리 빌더 + * @return Builder 정렬까지 적용된 쿼리 빌더 */ - private function buildFilteredQuery(array $filters): \Illuminate\Database\Eloquent\Builder + private function buildFilteredQuery(array $filters): Builder { $query = LanguagePack::query(); @@ -165,7 +169,8 @@ class LanguagePackRepository implements LanguagePackRepositoryInterface } if (! empty($filters['status'])) { if ($filters['status'] === LanguagePackStatus::Uninstalled->value) { - $query->whereRaw('1 = 0'); + // 결과 없음 — 빈 whereIn 은 `0 = 1` 로 컴파일된다(raw 불필요) + $query->whereIn($query->getModel()->getKeyName(), []); } else { $query->where('status', $filters['status']); } @@ -282,10 +287,10 @@ class LanguagePackRepository implements LanguagePackRepositoryInterface $pack->version = (string) ($manifest['version'] ?? '0.0.0'); $pack->license = $manifest['license'] ?? null; $pack->description = is_array($manifest['description'] ?? null) ? $manifest['description'] : null; - $pack->status = \App\Enums\LanguagePackStatus::Uninstalled->value; + $pack->status = LanguagePackStatus::Uninstalled->value; $pack->is_protected = false; // lang-packs/_bundled/ 패키지는 사용자가 install/uninstall 자유 (요구사항 #3) $pack->manifest = $manifest; - $pack->source_type = \App\Enums\LanguagePackSourceType::Bundled->value; + $pack->source_type = LanguagePackSourceType::Bundled->value; $pack->source_url = $bundledIdentifier; $pack->setAttribute('bundled_identifier', $bundledIdentifier); @@ -335,11 +340,11 @@ class LanguagePackRepository implements LanguagePackRepositoryInterface $pack->locale = $locale; $pack->locale_name = strtoupper($locale); $pack->locale_native_name = $nativeName; - $pack->text_direction = \App\Enums\TextDirection::Ltr->value; + $pack->text_direction = TextDirection::Ltr->value; $pack->version = $version; - $pack->status = \App\Enums\LanguagePackStatus::Active->value; + $pack->status = LanguagePackStatus::Active->value; $pack->is_protected = true; - $pack->source_type = \App\Enums\LanguagePackSourceType::BuiltIn->value; + $pack->source_type = LanguagePackSourceType::BuiltIn->value; $pack->source_url = $langPathRelative; $pack->manifest = [ 'identifier' => $identifier, diff --git a/app/Repositories/MenuRepository.php b/app/Repositories/MenuRepository.php index 126ae382..f98854ed 100644 --- a/app/Repositories/MenuRepository.php +++ b/app/Repositories/MenuRepository.php @@ -3,15 +3,24 @@ namespace App\Repositories; use App\Contracts\Repositories\MenuRepositoryInterface; -use App\Helpers\PermissionHelper; use App\Enums\ExtensionOwnerType; +use App\Helpers\PermissionHelper; use App\Models\Menu; +use App\Models\Module; +use App\Models\Plugin; use App\Models\User; +use App\Repositories\Concerns\ResolvesSortSpec; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; class MenuRepository implements MenuRepositoryInterface { + use ResolvesSortSpec; + + /** 허용 정렬 컬럼 (MenuListRequest 와 동일 집합) */ + private const SORTABLE_COLUMNS = ['created_at', 'name', 'slug', 'order']; + /** * 모든 메뉴를 조회합니다. * @@ -37,6 +46,14 @@ class MenuRepository implements MenuRepositoryInterface ->get(); } + /** + * 관리용 최상위 메뉴 목록을 조회합니다. (비활성 확장 메뉴 제외) + * + * @param array $activeModuleIdentifiers 활성 모듈 식별자 목록 + * @param array $activePluginIdentifiers 활성 플러그인 식별자 목록 + * @param User|null $user 접근 권한 판정 대상 사용자 (null 이면 역할 필터 미적용) + * @return Collection 최상위 메뉴 컬렉션 (자식 메뉴 포함) + */ public function getTopLevelMenusForManagement(array $activeModuleIdentifiers = [], array $activePluginIdentifiers = [], ?User $user = null): Collection { $extensionFilter = function ($query) use ($activeModuleIdentifiers, $activePluginIdentifiers) { @@ -67,13 +84,13 @@ class MenuRepository implements MenuRepositoryInterface } return $query->with(['creator', 'roles', 'children' => function ($query) use ($extensionFilter, $user) { - $query->where($extensionFilter); - if ($user) { - $query->accessibleBy($user); - } - $query->with('roles') - ->orderBy('order'); - }]) + $query->where($extensionFilter); + if ($user) { + $query->accessibleBy($user); + } + $query->with('roles') + ->orderBy('order'); + }]) ->orderBy('order') ->get(); } @@ -260,7 +277,7 @@ class MenuRepository implements MenuRepositoryInterface $q->where('extension_type', ExtensionOwnerType::Module) ->whereExists(function ($subQuery) { $subQuery->select(DB::raw(1)) - ->from('modules') + ->from((new Module)->getTable()) ->whereColumn('modules.identifier', 'menus.extension_identifier') ->where('modules.status', 'active'); }); @@ -270,7 +287,7 @@ class MenuRepository implements MenuRepositoryInterface $q->where('extension_type', ExtensionOwnerType::Plugin) ->whereExists(function ($subQuery) { $subQuery->select(DB::raw(1)) - ->from('plugins') + ->from((new Plugin)->getTable()) ->whereColumn('plugins.identifier', 'menus.extension_identifier') ->where('plugins.status', 'active'); }); @@ -359,6 +376,15 @@ class MenuRepository implements MenuRepositoryInterface return (int) Menu::where('parent_id', $parentId)->max('order'); } + /** + * 필터 조건이 적용된 최상위 메뉴 목록을 조회합니다. + * + * @param array $filters 필터 조건 배열 (sort_by/sort_order/검색 필드) + * @param array $activeModuleIdentifiers 활성 모듈 식별자 목록 + * @param array $activePluginIdentifiers 활성 플러그인 식별자 목록 + * @param User|null $user 접근 권한 판정 대상 사용자 (null 이면 역할 필터 미적용) + * @return Collection 최상위 메뉴 컬렉션 (자식 메뉴 포함) + */ public function getFilteredTopLevelMenus(array $filters, array $activeModuleIdentifiers = [], array $activePluginIdentifiers = [], ?User $user = null): Collection { $extensionFilter = function ($query) use ($activeModuleIdentifiers, $activePluginIdentifiers) { @@ -418,10 +444,10 @@ class MenuRepository implements MenuRepositoryInterface } } - // 정렬 - $sortBy = $filters['sort_by'] ?? 'order'; - $sortOrder = $filters['sort_order'] ?? 'asc'; - $query->orderBy($sortBy, $sortOrder); + // 정렬 (허용 컬럼 화이트리스트로 해석) + foreach ($this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'order', 'asc') as $sort) { + $query->orderBy($sort['column'], $sort['direction']); + } // 자식 메뉴도 동일한 조건 적용하여 로드 return $query->with(['creator', 'roles', 'children' => function ($childQuery) use ($extensionFilter, $filters, $user) { @@ -444,7 +470,7 @@ class MenuRepository implements MenuRepositoryInterface /** * 필터 연산자를 쿼리에 적용합니다. * - * @param \Illuminate\Database\Eloquent\Builder $query 쿼리 빌더 + * @param Builder $query 쿼리 빌더 * @param string $field 필드명 * @param string $value 검색 값 * @param string $operator 연산자 (like, eq, starts_with, ends_with) diff --git a/app/Repositories/NotificationDefinitionRepository.php b/app/Repositories/NotificationDefinitionRepository.php index a652e898..e1da1c0e 100644 --- a/app/Repositories/NotificationDefinitionRepository.php +++ b/app/Repositories/NotificationDefinitionRepository.php @@ -4,15 +4,28 @@ namespace App\Repositories; use App\Contracts\Repositories\NotificationDefinitionRepositoryInterface; use App\Models\NotificationDefinition; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Database\Eloquent\Collection; use Illuminate\Pagination\LengthAwarePaginator; class NotificationDefinitionRepository implements NotificationDefinitionRepositoryInterface { + use ResolvesSortSpec; + + /** 허용 정렬 컬럼 (NotificationDefinitionIndexRequest 와 동일 집합) */ + private const SORTABLE_COLUMNS = [ + 'id', + 'type', + 'extension_type', + 'is_active', + 'created_at', + 'updated_at', + ]; + /** * ID로 알림 정의 조회. * - * @param int $id + * @param int $id * @return NotificationDefinition|null */ public function findById(int $id): ?NotificationDefinition @@ -23,7 +36,7 @@ class NotificationDefinitionRepository implements NotificationDefinitionReposito /** * 타입으로 알림 정의 조회. * - * @param string $type + * @param string $type * @return NotificationDefinition|null */ public function findByType(string $type): ?NotificationDefinition @@ -34,7 +47,7 @@ class NotificationDefinitionRepository implements NotificationDefinitionReposito /** * 활성 상태인 특정 타입 알림 정의 조회. * - * @param string $type + * @param string $type * @return NotificationDefinition|null */ public function getActiveByType(string $type): ?NotificationDefinition @@ -58,7 +71,7 @@ class NotificationDefinitionRepository implements NotificationDefinitionReposito * 알림 목록 응답에서 N+1 회피 목적으로 한 번 호출하여 [type => label] 맵을 생성합니다. * 라벨 fallback 우선순위: 사용자 로케일 → ko → en → type 식별자 * - * @param string|null $locale 사용자 로케일 (null이면 app()->getLocale()) + * @param string|null $locale 사용자 로케일 (null이면 app()->getLocale()) * @return array */ public function getLabelMap(?string $locale = null): array @@ -86,8 +99,8 @@ class NotificationDefinitionRepository implements NotificationDefinitionReposito /** * 특정 확장의 알림 정의 목록 조회. * - * @param string $extensionType - * @param string $extensionIdentifier + * @param string $extensionType + * @param string $extensionIdentifier * @return Collection */ public function getByExtension(string $extensionType, string $extensionIdentifier): Collection @@ -98,8 +111,8 @@ class NotificationDefinitionRepository implements NotificationDefinitionReposito /** * 알림 정의 수정. * - * @param NotificationDefinition $definition - * @param array $data + * @param NotificationDefinition $definition + * @param array $data * @return NotificationDefinition */ public function update(NotificationDefinition $definition, array $data): NotificationDefinition @@ -112,8 +125,8 @@ class NotificationDefinitionRepository implements NotificationDefinitionReposito /** * 페이지네이션 목록 조회. * - * @param array $filters - * @param int $perPage + * @param array $filters + * @param int $perPage * @return LengthAwarePaginator */ public function getPaginated(array $filters = [], int $perPage = 20): LengthAwarePaginator @@ -148,10 +161,11 @@ class NotificationDefinitionRepository implements NotificationDefinitionReposito }); } - $sortBy = $filters['sort_by'] ?? 'id'; - $sortOrder = $filters['sort_order'] ?? 'asc'; - $query->orderBy($sortBy, $sortOrder); + foreach ($this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'id', 'asc') as $sort) { + $query->orderBy($sort['column'], $sort['direction']); + } + // audit:allow repository-paginate-column-pruning reason: 알림 정의 테이블 — 정의 수가 고정이고 넓은 컬럼이 없다 return $query->paginate($perPage); } } diff --git a/app/Repositories/NotificationLogRepository.php b/app/Repositories/NotificationLogRepository.php index f92cf1a4..6dcb1edd 100644 --- a/app/Repositories/NotificationLogRepository.php +++ b/app/Repositories/NotificationLogRepository.php @@ -6,12 +6,35 @@ use App\Contracts\Repositories\NotificationLogRepositoryInterface; use App\Enums\NotificationLogStatus; use App\Models\NotificationLog; use App\Models\User; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Pagination\LengthAwarePaginator; class NotificationLogRepository implements NotificationLogRepositoryInterface { + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + /** + * 발송 이력 목록 정렬 허용 컬럼 + * + * 요청 값을 그대로 orderBy 에 넘기면 없는 컬럼으로 SQL 오류가 나거나 인덱스 없는 컬럼 + * 정렬을 강제할 수 있다. + * + * @var array + */ + private const SORTABLE_COLUMNS = [ + 'id', + 'sent_at', + 'created_at', + 'status', + 'channel', + 'notification_type', + 'recipient_name', + ]; + /** * 최근 발송된 알림 로그를 발송 시각 최신순으로 조회합니다 (대시보드 최근 알림). * @@ -81,7 +104,8 @@ class NotificationLogRepository implements NotificationLogRepositoryInterface */ public function getPaginated(array $filters = [], int $perPage = 20, ?User $scopeUser = null): LengthAwarePaginator { - $query = NotificationLog::with(['senderUser', 'recipientUser']); + // 관계는 지연 조인의 outer 에서만 로드한다 (inner 는 키 컬럼만 조회한다) + $query = NotificationLog::query(); // notification-logs scope: 전달된 사용자의 권한 스코프 적용 if ($scopeUser) { @@ -125,11 +149,18 @@ class NotificationLogRepository implements NotificationLogRepositoryInterface }); } - $sortBy = $filters['sort_by'] ?? 'sent_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy($sortBy, $sortOrder); + $sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'sent_at'); - return $query->paginate($perPage); + // 목록 컬럼을 좁히지 않는 이유: 이 목록의 리소스는 렌더링된 본문(longText `body`)까지 + // 그대로 노출하므로 컬럼을 빼면 응답 계약이 바뀐다. 지연 조인만으로도 본문을 읽는 + // 행 수가 OFFSET 과 무관하게 이번 페이지 분량으로 고정된다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $sort, + perPage: $perPage, + relations: ['senderUser', 'recipientUser'], + ); } /** diff --git a/app/Repositories/NotificationRepository.php b/app/Repositories/NotificationRepository.php index bc23df76..50998432 100644 --- a/app/Repositories/NotificationRepository.php +++ b/app/Repositories/NotificationRepository.php @@ -11,6 +11,11 @@ class NotificationRepository implements NotificationRepositoryInterface { /** * 사용자의 알림 목록을 페이지네이션으로 조회합니다. + * + * @param User $user 대상 사용자 + * @param array $filters 필터 조건 (read: unread|read) + * @param int $perPage 페이지당 항목 수 + * @return LengthAwarePaginator 페이지네이션된 알림 목록 */ public function getByUser(User $user, array $filters = [], int $perPage = 20): LengthAwarePaginator { @@ -22,11 +27,17 @@ class NotificationRepository implements NotificationRepositoryInterface $query->whereNotNull('read_at'); } + // audit:allow repository-paginate-column-pruning reason: 사용자 1명에 종속된 알림함 — + // $user->notifications() 로 이미 좁혀지고, 목록이 표시하는 본문 자체가 data(JSON) 라 + // 걷어낼 넓은 컬럼이 없다 return $query->orderBy('created_at', 'desc')->paginate($perPage); } /** * 사용자의 미읽음 알림 수를 반환합니다. + * + * @param User $user 대상 사용자 + * @return int 미읽음 알림 수 */ public function getUnreadCount(User $user): int { @@ -35,6 +46,10 @@ class NotificationRepository implements NotificationRepositoryInterface /** * 특정 알림을 읽음 처리합니다. + * + * @param User $user 대상 사용자 + * @param string $notificationId 알림 ID + * @return DatabaseNotification|null 읽음 처리된 알림 (없으면 null) */ public function markAsRead(User $user, string $notificationId): ?DatabaseNotification { @@ -51,6 +66,10 @@ class NotificationRepository implements NotificationRepositoryInterface /** * 지정된 알림들을 일괄 읽음 처리합니다. + * + * @param User $user 대상 사용자 + * @param array $ids 알림 ID 배열 + * @return int 읽음 처리된 알림 수 */ public function markBatchAsRead(User $user, array $ids): int { @@ -61,6 +80,9 @@ class NotificationRepository implements NotificationRepositoryInterface /** * 사용자의 모든 미읽음 알림을 읽음 처리합니다. + * + * @param User $user 대상 사용자 + * @return int 읽음 처리된 알림 수 */ public function markAllAsRead(User $user): int { @@ -73,6 +95,10 @@ class NotificationRepository implements NotificationRepositoryInterface /** * 알림을 삭제합니다. + * + * @param User $user 대상 사용자 + * @param string $notificationId 알림 ID + * @return bool 삭제 성공 여부 (알림이 없으면 false) */ public function delete(User $user, string $notificationId): bool { @@ -88,7 +114,7 @@ class NotificationRepository implements NotificationRepositoryInterface /** * 사용자의 모든 알림을 삭제합니다. * - * @param User $user + * @param User $user * @return int 삭제된 알림 수 */ public function deleteAll(User $user): int @@ -99,6 +125,8 @@ class NotificationRepository implements NotificationRepositoryInterface /** * 오래된 알림을 정리합니다. * + * @param int $readRetentionDays 읽은 알림 보관 일수 (0 이하면 정리하지 않음) + * @param int $unreadRetentionDays 미읽음 알림 보관 일수 (0 이하면 정리하지 않음) * @return array{deleted_read: int, deleted_unread: int} */ public function cleanup(int $readRetentionDays, int $unreadRetentionDays): array diff --git a/app/Repositories/RoleRepository.php b/app/Repositories/RoleRepository.php index 0b960311..782fa7dc 100644 --- a/app/Repositories/RoleRepository.php +++ b/app/Repositories/RoleRepository.php @@ -218,6 +218,7 @@ class RoleRepository implements RoleRepositoryInterface $query->orderByRaw('CASE WHEN extension_type IS NOT NULL THEN 0 ELSE 1 END') ->orderBy('created_at', 'desc'); + // audit:allow repository-paginate-column-pruning reason: 역할 정의 테이블 — 행 수가 운영상 고정(수십 건)이고 넓은 컬럼이 없다 return $query->paginate($perPage); } diff --git a/app/Repositories/ScheduleHistoryRepository.php b/app/Repositories/ScheduleHistoryRepository.php index 2f93cdc0..66379a82 100644 --- a/app/Repositories/ScheduleHistoryRepository.php +++ b/app/Repositories/ScheduleHistoryRepository.php @@ -4,15 +4,20 @@ namespace App\Repositories; use App\Contracts\Repositories\ScheduleHistoryRepositoryInterface; use App\Models\ScheduleHistory; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface { + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + /** * ID로 실행 이력을 찾습니다. * - * @param int $id 이력 ID + * @param int $id 이력 ID * @return ScheduleHistory|null 찾은 이력 모델 또는 null */ public function findById(int $id): ?ScheduleHistory @@ -23,7 +28,7 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface /** * 새로운 실행 이력을 생성합니다. * - * @param array $data 이력 생성 데이터 + * @param array $data 이력 생성 데이터 * @return ScheduleHistory 생성된 이력 모델 */ public function create(array $data): ScheduleHistory @@ -34,8 +39,8 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface /** * 기존 실행 이력을 업데이트합니다. * - * @param ScheduleHistory $history 업데이트할 이력 모델 - * @param array $data 업데이트할 데이터 + * @param ScheduleHistory $history 업데이트할 이력 모델 + * @param array $data 업데이트할 데이터 * @return bool 업데이트 성공 여부 */ public function update(ScheduleHistory $history, array $data): bool @@ -46,7 +51,7 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface /** * 실행 이력을 삭제합니다. * - * @param ScheduleHistory $history 삭제할 이력 모델 + * @param ScheduleHistory $history 삭제할 이력 모델 * @return bool 삭제 성공 여부 */ public function delete(ScheduleHistory $history): bool @@ -57,8 +62,8 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface /** * 특정 스케줄의 실행 이력을 페이지네이션하여 조회합니다. * - * @param int $scheduleId 스케줄 ID - * @param array $filters 필터 조건 배열 + * @param int $scheduleId 스케줄 ID + * @param array $filters 필터 조건 배열 * @return LengthAwarePaginator 페이지네이션된 이력 목록 */ public function getPaginatedByScheduleId(int $scheduleId, array $filters = []): LengthAwarePaginator @@ -67,40 +72,50 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface ->where('schedule_id', $scheduleId); // 상태 필터 - if (!empty($filters['status'])) { + if (! empty($filters['status'])) { $query->where('status', $filters['status']); } // 트리거 유형 필터 - if (!empty($filters['trigger_type'])) { + if (! empty($filters['trigger_type'])) { $query->where('trigger_type', $filters['trigger_type']); } // 날짜 필터 - if (!empty($filters['started_from'])) { + if (! empty($filters['started_from'])) { $query->whereDate('started_at', '>=', $filters['started_from']); } - if (!empty($filters['started_to'])) { + if (! empty($filters['started_to'])) { $query->whereDate('started_at', '<=', $filters['started_to']); } - // 정렬 적용 - $sortBy = $filters['sort_by'] ?? 'started_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy($sortBy, $sortOrder); + // 정렬 — 요청 값은 허용 목록으로만 해석한다. + // 허용 집합은 ScheduleHistoryListRequest 의 `in:` 규칙과 동일해야 한다. 저장소 목록이 + // 그보다 좁으면 게이트를 통과한 정렬이 조용히 기본값으로 되돌아간다 (종료시각/소요시간). + $sort = $this->resolveSortSpec( + $filters, + ['started_at', 'ended_at', 'duration', 'status'], + 'started_at' + ); - // 페이지네이션 적용 - $perPage = $filters['per_page'] ?? 15; + $perPage = (int) ($filters['per_page'] ?? 15); - return $query->paginate($perPage); + // 실행 이력은 계속 쌓이며 출력/에러 메시지를 그대로 노출하므로, 컬럼은 좁히지 않고 + // 지연 조인으로 넓은 컬럼을 읽는 행 수만 이번 페이지 분량으로 고정한다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $sort, + perPage: $perPage, + ); } /** * 특정 스케줄의 최근 실행 이력을 조회합니다. * - * @param int $scheduleId 스케줄 ID - * @param int $limit 조회 개수 + * @param int $scheduleId 스케줄 ID + * @param int $limit 조회 개수 * @return Collection 최근 이력 컬렉션 */ public function getRecentByScheduleId(int $scheduleId, int $limit = 10): Collection @@ -115,7 +130,7 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface /** * 여러 이력을 일괄 삭제합니다. * - * @param array $ids 이력 ID 배열 + * @param array $ids 이력 ID 배열 * @return int 삭제된 레코드 수 */ public function bulkDelete(array $ids): int @@ -126,7 +141,7 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface /** * 특정 스케줄의 모든 이력을 삭제합니다. * - * @param int $scheduleId 스케줄 ID + * @param int $scheduleId 스케줄 ID * @return int 삭제된 레코드 수 */ public function deleteByScheduleId(int $scheduleId): int @@ -137,7 +152,7 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface /** * 특정 기간 이전의 이력을 삭제합니다. * - * @param int $days 보관 기간 (일) + * @param int $days 보관 기간 (일) * @return int 삭제된 레코드 수 */ public function deleteOlderThan(int $days): int diff --git a/app/Repositories/ScheduleRepository.php b/app/Repositories/ScheduleRepository.php index dfe718b3..129ebfd8 100644 --- a/app/Repositories/ScheduleRepository.php +++ b/app/Repositories/ScheduleRepository.php @@ -3,12 +3,13 @@ namespace App\Repositories; use App\Contracts\Repositories\ScheduleRepositoryInterface; -use App\Helpers\PermissionHelper; use App\Enums\ScheduleFrequency; use App\Enums\ScheduleResultStatus; use App\Enums\ScheduleType; +use App\Helpers\PermissionHelper; use App\Models\Schedule; use App\Repositories\Concerns\HasMultipleSearchFilters; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -16,16 +17,29 @@ use Illuminate\Database\Eloquent\Collection; class ScheduleRepository implements ScheduleRepositoryInterface { use HasMultipleSearchFilters; + use ResolvesSortSpec; /** * 검색 가능한 필드 목록 */ private const SEARCHABLE_FIELDS = ['name', 'description', 'command']; + /** + * 허용 정렬 컬럼 (ScheduleListRequest 와 동일 집합) + */ + private const SORTABLE_COLUMNS = [ + 'created_at', + 'name', + 'next_run_at', + 'last_run_at', + 'is_active', + 'last_result', + ]; + /** * ID로 스케줄을 찾습니다. * - * @param int $id 스케줄 ID + * @param int $id 스케줄 ID * @return Schedule|null 찾은 스케줄 모델 또는 null */ public function findById(int $id): ?Schedule @@ -36,7 +50,7 @@ class ScheduleRepository implements ScheduleRepositoryInterface /** * 새로운 스케줄을 생성합니다. * - * @param array $data 스케줄 생성 데이터 + * @param array $data 스케줄 생성 데이터 * @return Schedule 생성된 스케줄 모델 */ public function create(array $data): Schedule @@ -47,8 +61,8 @@ class ScheduleRepository implements ScheduleRepositoryInterface /** * 기존 스케줄을 업데이트합니다. * - * @param Schedule $schedule 업데이트할 스케줄 모델 - * @param array $data 업데이트할 데이터 + * @param Schedule $schedule 업데이트할 스케줄 모델 + * @param array $data 업데이트할 데이터 * @return bool 업데이트 성공 여부 */ public function update(Schedule $schedule, array $data): bool @@ -59,7 +73,7 @@ class ScheduleRepository implements ScheduleRepositoryInterface /** * 스케줄을 삭제합니다. * - * @param Schedule $schedule 삭제할 스케줄 모델 + * @param Schedule $schedule 삭제할 스케줄 모델 * @return bool 삭제 성공 여부 */ public function delete(Schedule $schedule): bool @@ -80,7 +94,7 @@ class ScheduleRepository implements ScheduleRepositoryInterface /** * 필터링 및 페이지네이션이 적용된 스케줄 목록을 조회합니다. * - * @param array $filters 필터 조건 배열 + * @param array $filters 필터 조건 배열 * @return LengthAwarePaginator 페이지네이션된 스케줄 목록 */ public function getPaginatedSchedules(array $filters = []): LengthAwarePaginator @@ -93,33 +107,34 @@ class ScheduleRepository implements ScheduleRepositoryInterface // 필터 조건 적용 $this->applyFilters($query, $filters); - // 정렬 적용 - $sortBy = $filters['sort_by'] ?? 'created_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy($sortBy, $sortOrder); + // 정렬 적용 (허용 컬럼 화이트리스트로 해석) + foreach ($this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at') as $sort) { + $query->orderBy($sort['column'], $sort['direction']); + } // 페이지네이션 적용 $perPage = $filters['per_page'] ?? 15; + // audit:allow repository-paginate-column-pruning reason: 스케줄 정의 테이블 — 등록된 스케줄 수만큼만 존재하고 넓은 컬럼이 없다 return $query->paginate($perPage); } /** * 쿼리에 필터 조건을 적용합니다. * - * @param Builder $query Eloquent 쿼리 빌더 - * @param array $filters 적용할 필터 조건 배열 + * @param Builder $query Eloquent 쿼리 빌더 + * @param array $filters 적용할 필터 조건 배열 * @return void */ private function applyFilters(Builder $query, array $filters): void { // 다중 검색 조건 적용 - if (!empty($filters['filters']) && is_array($filters['filters'])) { + if (! empty($filters['filters']) && is_array($filters['filters'])) { $this->applyMultipleSearchFilters($query, $filters['filters'], self::SEARCHABLE_FIELDS); } // 타입 필터 - if (!empty($filters['type'])) { + if (! empty($filters['type'])) { $type = ScheduleType::tryFrom($filters['type']); if ($type) { $query->where('type', $type); @@ -127,7 +142,7 @@ class ScheduleRepository implements ScheduleRepositoryInterface } // 주기 필터 - if (!empty($filters['frequency'])) { + if (! empty($filters['frequency'])) { $frequency = ScheduleFrequency::tryFrom($filters['frequency']); if ($frequency) { $query->where('frequency', $frequency); @@ -135,7 +150,7 @@ class ScheduleRepository implements ScheduleRepositoryInterface } // 상태 필터 - if (!empty($filters['status'])) { + if (! empty($filters['status'])) { if ($filters['status'] === 'active') { $query->where('is_active', true); } elseif ($filters['status'] === 'inactive') { @@ -144,7 +159,7 @@ class ScheduleRepository implements ScheduleRepositoryInterface } // 마지막 실행 결과 필터 - if (!empty($filters['last_result'])) { + if (! empty($filters['last_result'])) { $result = ScheduleResultStatus::tryFrom($filters['last_result']); if ($result) { $query->where('last_result', $result); @@ -162,10 +177,10 @@ class ScheduleRepository implements ScheduleRepositoryInterface } // 확장 타입 필터 - if (!empty($filters['extension_type'])) { + if (! empty($filters['extension_type'])) { $query->where('extension_type', $filters['extension_type']); - if (!empty($filters['extension_identifier'])) { + if (! empty($filters['extension_identifier'])) { $query->where('extension_identifier', $filters['extension_identifier']); } } @@ -177,17 +192,17 @@ class ScheduleRepository implements ScheduleRepositoryInterface /** * 날짜 필터를 적용합니다. * - * @param Builder $query Eloquent 쿼리 빌더 - * @param array $filters 필터 조건 배열 + * @param Builder $query Eloquent 쿼리 빌더 + * @param array $filters 필터 조건 배열 * @return void */ private function applyDateFilters(Builder $query, array $filters): void { - if (!empty($filters['created_from'])) { + if (! empty($filters['created_from'])) { $query->whereDate('created_at', '>=', $filters['created_from']); } - if (!empty($filters['created_to'])) { + if (! empty($filters['created_to'])) { $query->whereDate('created_at', '<=', $filters['created_to']); } } @@ -233,8 +248,8 @@ class ScheduleRepository implements ScheduleRepositoryInterface /** * 여러 스케줄의 상태를 일괄 업데이트합니다. * - * @param array $ids 스케줄 ID 배열 - * @param bool $isActive 활성화 여부 + * @param array $ids 스케줄 ID 배열 + * @param bool $isActive 활성화 여부 * @return int 업데이트된 레코드 수 */ public function bulkUpdateStatus(array $ids, bool $isActive): int @@ -245,7 +260,7 @@ class ScheduleRepository implements ScheduleRepositoryInterface /** * 여러 스케줄을 일괄 삭제합니다. * - * @param array $ids 스케줄 ID 배열 + * @param array $ids 스케줄 ID 배열 * @return int 삭제된 레코드 수 */ public function bulkDelete(array $ids): int @@ -256,13 +271,13 @@ class ScheduleRepository implements ScheduleRepositoryInterface /** * 스케줄을 복제합니다. * - * @param Schedule $schedule 복제할 스케줄 + * @param Schedule $schedule 복제할 스케줄 * @return Schedule 복제된 스케줄 */ public function duplicate(Schedule $schedule): Schedule { $newSchedule = $schedule->replicate(); - $newSchedule->name = $schedule->name . ' (' . __('schedule.copy') . ')'; + $newSchedule->name = $schedule->name.' ('.__('schedule.copy').')'; $newSchedule->is_active = false; $newSchedule->last_result = ScheduleResultStatus::Never; $newSchedule->last_run_at = null; diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index c07b2f8b..3d031993 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -6,6 +6,8 @@ use App\Contracts\Repositories\UserRepositoryInterface; use App\Helpers\PermissionHelper; use App\Models\User; use App\Repositories\Concerns\HasMultipleSearchFilters; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -15,12 +17,19 @@ use Laravel\Sanctum\PersonalAccessToken; class UserRepository implements UserRepositoryInterface { use HasMultipleSearchFilters; + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; /** * 검색 가능한 필드 목록 */ private const SEARCHABLE_FIELDS = ['name', 'email']; + /** + * 허용 정렬 컬럼 (UserListRequest 와 동일 집합) + */ + private const SORTABLE_COLUMNS = ['created_at', 'name', 'email', 'last_login_at']; + /** * 이메일로 사용자를 찾습니다. * @@ -100,21 +109,23 @@ class UserRepository implements UserRepositoryInterface // 권한 스코프 필터링 PermissionHelper::applyPermissionScope($query, 'core.users.read'); - // roles 관계 eager loading - $query->with('roles'); - // 검색 조건 적용 $this->applyFilters($query, $filters); - // 정렬 적용 - $sortBy = $filters['sort_by'] ?? 'created_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy($sortBy, $sortOrder); + // 정렬 적용 (허용 컬럼 화이트리스트로 해석) + $sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at'); // 페이지네이션 적용 $perPage = $filters['per_page'] ?? 15; - return $query->paginate($perPage); + // 지연 조인: inner 는 id 만 훑고 roles eager loading 은 해당 페이지에만 적용된다 + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $sort, + perPage: $perPage, + relations: ['roles'], + ); } /** diff --git a/docs/backend/service-repository.md b/docs/backend/service-repository.md index 8c862c64..571913a5 100644 --- a/docs/backend/service-repository.md +++ b/docs/backend/service-repository.md @@ -13,6 +13,7 @@ 4. 검증 로직은 FormRequest에서 (Service에 검증 금지) 5. 다중 검색은 HasMultipleSearchFilters Trait 사용 6. Service 에서 Model 직접 인스턴스화 금지 — Repository 의 build/factory 메서드 위임 (가상 모델 합성 포함) +7. 목록 조회는 컬럼 목록 명시 + 깊은 OFFSET 이 가능한 목록은 PaginatesWithDeferredJoin Trait 사용 ``` --- @@ -25,6 +26,7 @@ - [트랜잭션 및 관계 삭제 패턴](#트랜잭션-및-관계-삭제-패턴) - [상태 변경 자동 처리](#상태-변경-자동-처리) - [Repository 클래스](#repository-클래스) +- [목록 조회 컬럼 프루닝과 지연 조인](#목록-조회-컬럼-프루닝과-지연-조인) - [다중 검색 필터 Trait](#다중-검색-필터-trait-hasmultiplesearchfilters) - [모듈에서 Repository 인터페이스 바인딩](#모듈에서-repository-인터페이스-바인딩) - [중첩 리소스 스코프](#중첩-리소스-스코프) @@ -708,6 +710,49 @@ $query->selectRaw('sales_status, COUNT(*) as count'); $query->whereColumn('stock_quantity', '<=', 'safe_stock_quantity'); ``` +##### Raw 안에 테이블명·별칭을 쓰지 않는다 + +Raw 를 쓰더라도 **테이블명·별칭·프리픽스를 문자열로 조립하지 않는다.** 조립한 순간 프리픽스 처리를 사람이 떠안게 되고, 빌더가 만든 SQL 과 어긋나 조용히 깨진다. + +Laravel 은 별칭에도 프리픽스를 붙인다(`Grammar::wrapAliasedTable`). `join('board_comments as uc', ...)` 는 `g7_board_comments as g7_uc` 가 되므로 `where('uc.content', ...)` 도 `g7_uc.content` 로 맞아떨어진다. 별칭을 `DB::raw` 로 직접 만들면 그 별칭만 프리픽스가 빠져, 같은 별칭을 참조하는 빌더 조건이 전부 어긋난다. + +```php +// ❌ DON'T: 별칭을 raw 로 만들고 프리픽스를 손으로 처리 — where('uc.content') 가 g7_uc 를 찾아 실패한다 +$prefix = DB::getTablePrefix(); +$query->join(DB::raw("{$prefix}board_comments AS uc"), function ($join) { + $join->on(DB::raw("{$prefix}board_posts.id"), '=', DB::raw('uc.post_id')); +}); + +// ✅ DO: 빌더가 별칭과 프리픽스를 함께 처리하게 둔다 +$query->join((new Comment)->getTable().' as uc', function ($join) use ($postsTable) { + $join->on("{$postsTable}.id", '=', 'uc.post_id'); +}); +``` + +부득이 raw 안에 테이블명이 필요하면(예: `LEFT(...)`, `SUBSTRING(...)`) **모델에서 얻은 이름**과 **연결 설정에서 읽은 프리픽스**를 쓴다. + +```php +// ❌ DON'T +DB::raw("LEFT(`{$prefix}board_posts`.`content`, 300) as excerpt") + +// ✅ DO +$postsTable = (new Post)->getTable(); +DB::raw('LEFT(`'.DB::getTablePrefix().$postsTable.'`.`content`, 300) as excerpt') +``` + +##### 빌더로 대체 가능한 raw 관용구 + +| Raw | 빌더 대체 | 비고 | +|---|---|---| +| `whereRaw('1 = 0')` / `whereRaw('0 = 1')` | `whereIn($model->getKeyName(), [])` | 빈 `whereIn` 이 정확히 `0 = 1` 로 컴파일된다 | +| `where($col, DB::raw("({$sub->toSql()})"))` + `mergeBindings` | `where($col, '=', $sub)` | 빌더를 값으로 넘기면 바인딩이 자동 병합된다 | +| `addSelect(DB::raw("({$subSql}) as alias"))` | `addSelect(['alias' => $sub])` | 서브쿼리 select 는 빌더를 그대로 받는다 | +| `whereRaw("... CASE ... IN (?,?)")` | 갈래별 `orWhere(fn ($q) => $q->where(...)->whereIn(...))` | 대상 종류에 따라 갈리는 조건은 갈래별 빌더 조건으로 | +| 상관 서브쿼리 문자열 | `whereColumn()` 으로 외부 컬럼 참조 | 값 비교는 `where()` 로 바인딩 | +| 조인 + `groupBy` 로 존재 판정 | `whereHas()` | 행 증폭이 없어 inner 스캔이 가벼워지고 그룹 COUNT 도 불필요 | + +집계 함수(`COUNT`/`SUM`/`COALESCE`)와 DB 고유 함수(`MATCH ... AGAINST`, `JSON_CONTAINS`, `LEFT`, `SUBSTRING`)는 빌더에 대응 표현이 없으므로 raw 로 남긴다. 이때도 **값은 바인딩**하고 테이블명은 넣지 않는다. + ### 패턴 ```php @@ -788,6 +833,164 @@ public function getAll(): Collection --- +## 목록 조회 컬럼 프루닝과 지연 조인 + +### 규칙 + +- 목록 조회는 `paginate($perPage)` 처럼 컬럼 인자를 생략하지 않는다. 화면이 실제로 쓰는 컬럼만 명시한다 (인자를 생략하면 `select *` 로 폴백해 넓은 컬럼까지 전부 읽는다). +- 행이 계속 늘어나는 테이블(게시글·주문·활동 로그·알림 발송 이력 등)의 목록은 `PaginatesWithDeferredJoin` Trait 을 사용한다. + +### 배경 + +`OFFSET n` 은 건너뛸 n 건을 읽지 않고 넘기는 것이 아니라 **실제로 읽은 뒤 버린다.** 목록 SELECT 에 `text`/`longText`/JSON 컬럼이 포함돼 있으면 버릴 n 건의 넓은 컬럼까지 함께 읽힌다. 게다가 InnoDB 는 행에 다 담기지 않는 값을 오버플로 페이지에 따로 저장하므로, 앞부분만 필요해도 그 페이지를 읽는다. + +게시글 테이블 20만 행 기준 실측 (각 3회 중앙값, 첫 회 버림): + +```bash +php artisan g7:bench:pagination --table=board_posts --seed=200000 --offsets=0,20000,50000,199980 --runs=3 --explain +``` + +| OFFSET | 목록 컬럼 | ID 만 조회 | 배수 | +|---:|---:|---:|---:| +| 0 | 4.1ms | 3.9ms | 1.0× | +| 20,000 | 1,682.6ms | 139.8ms | 12.0× | +| 50,000 | 1,623.8ms | 90.3ms | 18.0× | +| 199,980 | 1,811.2ms | 93.9ms | 19.3× | + +같은 계측의 실행 계획을 보면 원인이 하나 더 드러난다. OFFSET 0 은 `(board_id, created_at)` 인덱스를 역방향으로 훑어 정렬 없이 끝나지만, OFFSET 이 커지면 옵티마이저가 소프트 삭제 단독 인덱스(`deleted_at`)로 갈아타면서 `filesort` 가 붙는다. 넓은 컬럼을 걷어내는 것과 정렬을 인덱스로 덮는 것은 함께 가야 한다. + +`SUBSTRING(content, 1, N)` 을 목록 컬럼에 두는 것도 프루닝이 아니다. 앞 N 바이트를 얻기 위해 오버플로 페이지를 그대로 읽는다. + +`SUBSTRING(content, 1, 200)` 을 목록 컬럼에 두는 것은 프루닝이 아니다. 앞 200바이트를 얻기 위해 오버플로 페이지를 그대로 읽기 때문이다. 잘라내기는 지연 조인의 outer 로 옮긴다. + +### 사용 + +```php +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; + +class OrderRepository implements OrderRepositoryInterface +{ + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + public function paginate(array $filters, int $perPage): LengthAwarePaginator + { + $query = $this->model->newQuery(); + $this->applyFilters($query, $filters); // where 만 적용 + + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['id', 'order_number', 'status', 'total_amount', 'ordered_at'], + sort: $this->resolveSortSpec($filters, ['id', 'ordered_at', 'total_amount'], 'ordered_at'), + perPage: $perPage, + relations: ['user', 'items'], + ); + } +} +``` + +### 계약 + +| 항목 | 규칙 | +| ------ | ------ | +| `$query` | 필터/where 만 적용한다. `orderBy`/`with`/`select` 는 Trait 이 담당한다 | +| `whereHas` | 결과 집합을 결정하는 조건이므로 그대로 둔다. Trait 은 `with` 만 inner 에서 제거한다 | +| 정렬 | 닫힌 집합이어야 한다. 인덱스 없는 임의 컬럼 정렬은 inner 도 전체 스캔하므로 개선 폭이 보장되지 않는다 | +| 키 컬럼 | 정렬 스펙 끝에 자동으로 덧붙는다. 동률 행의 순서가 흔들려 페이지 경계에서 행이 중복·누락되는 것을 막는다 | + +`paginate` / `simplePaginate` / 캐시 total 주입은 인자로 구분한다 — inner 쿼리는 동일하고 COUNT 수행 여부와 반환 클래스만 달라진다. + +| 호출자 상황 | `$simple` | `$total` | 반환 | +| ------ | ------ | ------ | ------ | +| 일반 목록 | `false` | `null` | `LengthAwarePaginator` (COUNT 1회) | +| COUNT 를 피하고 다음 페이지 유무만 필요 | `true` | — | `Paginator` | +| 총 건수를 이미 캐시해 둠 | `false` | 캐시값 | `LengthAwarePaginator` (COUNT 없음) | + +정렬 순서 복원은 `whereIn` + 같은 정렬 스펙 재적용으로 한다. 키 컬럼이 정렬에 포함돼 전순서가 성립하므로 결과가 inner 순서와 동일하다. MySQL 전용 `FIELD()` 는 쓰지 않는다. outer 에 존재하지 않는 표현식(집계·조인 서브쿼리)으로 정렬해 재현이 불가능한 경우에만 `$preserveIdOrder = true` 로 표준 SQL `CASE WHEN` 경로를 쓴다. + +### 정렬 컬럼 화이트리스트 + +요청 값을 그대로 `orderBy()` 에 넘기지 않는다. Laravel 이 컬럼명을 백틱으로 감싸므로 SQL 인젝션은 성립하지 않지만, (a) 없는 컬럼으로 인한 SQL 오류가 스키마 정보를 노출하고 (b) 인덱스 없는 넓은 컬럼 정렬을 강제해 DoS 표면이 된다. + +정렬 선택지가 4개 이하로 고정된 곳은 `match` 표현식으로 이미 닫혀 있으므로 그대로 두고, 요청 값이 동적 컬럼으로 흘러 들어가는 곳만 `ResolvesSortSpec` 을 쓴다. + +허용 컬럼 집합은 대응하는 FormRequest 의 `Rule::in(...)` 과 같은 값으로 맞추고, 그 사실을 상수 주석에 남긴다. 요청이 아닌 경로(콘솔 커맨드·내부 호출)로도 같은 Repository 메서드가 불릴 수 있으므로, 화이트리스트는 FormRequest 하나에만 두지 않고 쿼리를 만드는 자리에도 둔다. + +**게이트보다 좁게 두지 않는다.** 허용 값의 SSoT 는 요청을 실제로 거절하는 FormRequest 이고, Repository 상수는 그 아래에 깔리는 안전망이다. 상수가 게이트의 부분집합이면 **검증을 통과한 정렬이 조용히 기본값으로 되돌아간다** — 422 도 로그도 남지 않아 "정렬 버튼이 안 먹는다" 로만 관측된다. FormRequest 가 `HookManager::applyFilters` 로 확장에 열려 있는 목록은 확장이 정렬 컬럼을 늘릴 수 있으므로, 상수를 게이트보다 넓게 두는 편이 안전하다. 상수를 새로 만들거나 고칠 때는 대응 FormRequest 의 `in:` 값을 눈으로 대조하고, 컬럼명이 실재하는지 마이그레이션에서 확인한다. + +**방향 검증은 컬럼 검증이 아니다.** 다음은 정렬 방향만 닫아 두고 컬럼은 그대로 흘려보낸 형태이며, 사람이 읽어도 `in_array` 가 있어 안전해 보이지만 실제로는 무방비다. + +```php +// ❌ $sortOrder 만 검사 — $sortBy 는 그대로 orderBy 로 들어간다 +$sortBy = $filters['sort_by'] ?? 'created_at'; +$sortOrder = strtolower($filters['sort_order'] ?? 'desc'); + +if (! in_array($sortOrder, ['asc', 'desc'])) { + $sortOrder = 'desc'; +} + +$query->orderBy($sortBy, $sortOrder); +``` + +```php +// ✅ 컬럼·방향을 한 번에 해석 (방향 정규화는 Trait 이 담당) +foreach ($this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at') as $sort) { + $query->orderBy($sort['column'], $sort['direction']); +} +``` + +`repository-sort-column-whitelist` 룰은 **정렬 컬럼 변수 자체를 인자로 받는** `in_array`/`match`/`array_key_exists` 또는 `resolveSortSpec` 만 화이트리스트로 인정한다. + +### 예외 + +설정/정의성 테이블처럼 행 수가 고정이고 넓은 컬럼이 없으면 컬럼 목록 없이 페이지네이션해도 된다. 다만 그 판단 근거를 코드 옆에 남긴다. + +```php +// audit:allow repository-paginate-column-pruning reason: 역할 정의 테이블 — 행 수 고정(<100), 넓은 컬럼 없음 +return $query->paginate($perPage); +``` + +면제 주석은 **위반 줄에 인접**해야 인식된다. 메서드 첫 줄에 두면 체인 중간의 `->paginate(...)` 줄과 사이에 코드가 끼어 인식되지 않는다. + +```php +// ✅ 체인 안에서는 ->paginate 바로 위에 둔다 +return $this->model->newQuery() + ->where('user_id', $userId) + ->orderByDesc('created_at') + // audit:allow repository-paginate-column-pruning reason: 사용자 1명에 종속된 목록 — OFFSET 이 깊어질 수 없다 + ->paginate($perPage); +``` + +면제 사유로 인정되는 판단 근거는 다음 세 가지다. 어느 것도 아니면 지연 조인으로 전환한다. + +| 근거 | 성립 조건 | 예 | +|---|---|---| +| 정의/템플릿 테이블 | 행 수가 운영자가 등록한 수에 묶이고 넓은 컬럼이 없다 | 게시판 정의, 배송정책, 상품 안내 템플릿 | +| 부모 1건에 종속 | `where(부모FK)` 로 좁혀져 OFFSET 이 깊어질 수 없다 | 상품 1건의 문의, 사용자 1명의 찜 | +| ID 집합으로 선한정 | `whereIn('id', $matchedIds)` 로 스캔 대상이 이미 묶였다 | FULLTEXT 매칭 후 조회하는 통합검색 | + +"부모 1건에 종속" 은 그 부모가 가질 수 있는 자식 수를 실제로 따져 본 뒤에만 쓴다. 대량 발급 쿠폰 1건의 발급 이력처럼 부모가 하나여도 수십만 행이 되는 경우는 이 근거가 성립하지 않는다. + +컨트롤러가 Service 에 목록 조회를 위임하는 형태(`$this->service->paginate(...)`)는 컬럼 선택 책임이 위임받은 Repository 에 있으므로 룰이 검사하지 않는다. + +### outer 의 `columns: ['*']` 는 위반이 아니다 + +지연 조인의 목적인 **OFFSET 구간의 넓은 컬럼 읽기**는 inner 가 키 컬럼만 읽는 것으로 이미 사라진다. outer 가 읽는 행 수는 페이지 크기로 고정되므로 `columns: ['*']` 여도 뒤쪽 페이지의 비용 증가는 없다. 넓은 컬럼을 가진 테이블에서 목록 컬럼을 좁히는 것은 그 위에 얹는 추가 최적화이고, 화면이 실제로 쓰는 컬럼이 분명할 때 적용한다. + +다만 `columns:` 자체를 생략하면 outer 가 무엇을 읽는지 호출처에서 드러나지 않으므로 항상 명시한다. `DeferredJoinCallSiteContractTest` 가 호출처를 스캔해 `columns:`/`sort:` 명시 여부를 전수 검사한다. + +### 정적 검사 대상 + +| 검출 대상 | 처리 | +|---|---| +| `->paginate($perPage)` / `->paginate($perPage, ['*'])` — 컬럼 목록 미지정 또는 전체 컬럼 | 차단 | +| 요청 값에서 온 정렬 컬럼을 닫힌 집합 검증 없이 `orderBy` 에 전달 | 차단 | + +검사 범위는 `app/Repositories/**`, `modules|plugins/_bundled/**/Repositories/**` 와 컨트롤러 3계열이다. 미수정 파일까지 확인하려면 변경분이 아니라 저장소 전체를 대상으로 검사한다 — 변경 파일만 보는 방식으로는 이미 저장소에 있던 위반이 드러나지 않는다. + +--- + ## 다중 검색 필터 Trait (HasMultipleSearchFilters) 목록 API에서 다중 검색 조건을 지원해야 할 때 `HasMultipleSearchFilters` Trait을 사용합니다. diff --git a/docs/backend/validation.md b/docs/backend/validation.md index ac160315..62fa7f2f 100644 --- a/docs/backend/validation.md +++ b/docs/backend/validation.md @@ -522,6 +522,7 @@ class TestMailRequest extends FormRequest | 기본값 제공 | `$this->input('mailer', 'smtp')` — 미전송 시 기본 드라이버 적용 | | Store/Update 정책 일치 | 같은 리소스의 Store 와 Update 는 동일 필드에 동일한 조건부 규칙을 적용한다. 한쪽에만 `exclude_if` 등이 있으면 다른 쪽에서만 저장이 막힌다 | | `sometimes` 는 부분 수정 지원 수단이 아니다 | 관리자 폼은 조회 응답 전체를 그대로 PUT 하므로 요청에는 항상 모든 키가 존재한다. 토글 OFF 상태의 종속 필드는 `sometimes` 가 아니라 `exclude_if` 로 검증에서 배제한다 | +| 상호 필수 조합은 결과 상태로 판정 | `required_with`/`required_without` 는 "상대 필드 부재" 자체가 발화 조건이라 부분 수정 요청과 상충한다. Update 에서는 페이로드 단독이 아니라 "기존 레코드 + 페이로드" 결과 상태를 기준으로 판정한다 | `exclude_if` 는 규칙 배열의 **첫 번째**에 둔다. 배제는 규칙 루프 도중 발동하며 이미 추가된 실패 메시지는 회수되지 않으므로, 앞선 규칙이 먼저 실패하면 배제되어도 오류가 남는다. @@ -533,6 +534,68 @@ class TestMailRequest extends FormRequest 'allowed_extensions' => ['exclude_if:use_file_upload,false', 'sometimes', 'required', 'array', 'min:1'], ``` +조건부 규칙의 종류에 따라 Update 로 옮기는 방식이 달라진다. + +| Store 규칙 | Update 에 옮기는 방식 | +| ------ | ------ | +| `required_if:other,value` | 그대로 복사한다. 조건 필드가 요청에 없으면 발화하지 않아 부분 수정과 충돌하지 않는다 | +| `exclude_if` / `exclude_unless` | 그대로 복사한다. 조건 필드가 요청에 없으면 배제가 동작하지 않으므로(`ValidatesAttributes::validateExcludeIf`), 부분 수정 경로가 있으면 `prepareForValidation()` 에서 기존 레코드 값을 주입해 판정 기준을 세운다 | +| `required_with` / `required_without` | 그대로 복사하면 안 된다. 상대 필드가 요청에 없다는 사실만으로 발화하므로, 그 조합과 무관한 필드 하나만 바꾸는 요청이 전부 422 가 된다 | + +`required_with`/`required_without` 는 "요청에 무엇이 실려 왔는지"가 아니라 "저장 후 레코드가 유효한지"를 묻는 규칙이다. 따라서 Update 에서는 요청에 없는 필드를 저장된 값으로 메운 뒤 판정한다. 이때 폼 제출 키와 저장 컬럼명이 다르면(예: 해외 주소를 `intl_city` 로 제출하고 `city` 컬럼에 저장) 메우는 쪽에서 그 매핑을 반영해야 한다 — 매핑을 빠뜨리면 저장된 값이 있는데도 빈 값으로 보여 정상 수정 경로가 막힌다. + +```php +// ❌ DON'T: Store 규칙을 그대로 복사 — 기본 배송지 토글만 바꾸는 요청이 422 +'zipcode' => 'required_without:intl_postal_code|nullable|string|max:10', + +// ✅ DO: 결과 상태(기존 레코드 + 페이로드) 기준으로 withValidator() 에서 판정 +$validator->after(function (Validator $validator) { + $existing = app(UserAddressRepositoryInterface::class)->findByUserIdAndId($userId, $addressId); + $effective = array_key_exists('zipcode', $payload) ? $payload['zipcode'] : $existing?->zipcode; + // ... 국가별 필수 조합 판정 후 $validator->errors()->add(...) +}); +``` + +이 방식은 `formrequest-store-update-conditional-symmetry` 룰의 토큰 비교로는 "누락"으로 보이므로, 해당 필드에 `// audit:allow formrequest-store-update-conditional-symmetry reason: ...` 로 판정 위치를 남긴다. + +### `exclude_if` vs `exclude_unless` — 조건 필드가 nullable 일 때 + +`exclude_if:other,false` 의 값 비교는 strict 다. 조건 필드가 `nullable|boolean` 이라 `null` 이 올 수 있으면 `null !== false` 라 매칭되지 않아 배제가 일어나지 않는다. "true 가 아니면 제외" 를 뜻하는 `exclude_unless:other,true` 는 `null`/`false`/미전송을 모두 포괄한다. + +```php +// 조건 필드가 boolean 고정 — false 가 확실히 온다 +'allowed_extensions' => ['exclude_if:use_file_upload,false', 'sometimes', 'required', 'array', 'min:1'], + +// 조건 필드가 nullable — null 이 올 수 있다 +'basic_defaults.allowed_extensions' => ['exclude_unless:basic_defaults.use_file_upload,true', 'sometimes', 'required', 'array', 'min:1'], +``` + +**중첩 배열의 조건 필드는 dot notation 으로 쓴다.** `exclude_unless:use_file_upload,true` 처럼 최상위 키만 적으면 요청 데이터에 그 키가 없어 조건이 성립하지 않고, 배제가 조용히 동작하지 않는다. 검증 대상 필드가 `basic_defaults.allowed_extensions` 라면 조건 필드도 `basic_defaults.use_file_upload` 여야 한다. + +### 조건 필드가 요청에 없을 때 — 저장값 주입 + +`exclude_if`/`exclude_unless` 는 **조건 필드가 데이터에 없으면 배제하지 않는다**(`ValidatesAttributes::validateExcludeIf`). 부분 수정 경로가 있는 Update 요청은 종속 필드만 전송되고 조건 토글은 빠질 수 있으므로, `prepareForValidation()` 에서 기존 레코드의 값을 주입해 판정 기준을 세운다. + +```php +protected function prepareForValidation(): void +{ + $data = $this->all(); + + // 종속 필드가 왔는데 조건 토글이 없으면 저장값으로 판정 기준을 세운다 + if (array_key_exists('allowed_extensions', $data) && ! array_key_exists('use_file_upload', $data)) { + $board = $this->route('board'); + + if ($board !== null && isset($board->use_file_upload)) { + $data['use_file_upload'] = (bool) $board->use_file_upload; + } + } + + $this->merge($data); +} +``` + +주입 조건에 **종속 필드가 실제로 전송되었는지**를 반드시 포함한다. 무조건 주입하면 사용자가 보내지도 않은 토글이 검증 대상으로 올라와, 그 필드와 무관한 부분 수정이 조건부 규칙의 영향을 받는다. + 자동 차단: 정적 검사 대상. store/update 사이의 조건부 검증 비대칭은 차단되고, `sometimes` + `array` + `min` 조합이 무효화되는 형태는 경고로 보고된다. --- diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index cf333c2e..fef00780 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -255,6 +255,17 @@ php artisan seo:generate-sitemap --rebuild # 전체 재생성 (mode=full) php artisan seo:generate-sitemap --mode=full|auto|incremental # 재생성 모드 지정 ``` +### 성능 계측 Artisan 커맨드 + +```bash +# 깊은 OFFSET 목록 조회 비용 계측 (목록 컬럼 vs 키 컬럼 두 축). 상세: docs/backend/service-repository.md +# 대량 합성 행을 시딩하므로 운영 데이터가 있는 환경에서 --seed/--fresh 를 쓰지 않는다. +php artisan --env=testing g7:bench:pagination --list-profiles +php artisan --env=testing g7:bench:pagination --table=board_posts --fresh --seed=200000 +php artisan --env=testing g7:bench:pagination --table=board_posts --offsets=0,20000,50000,199980 --runs=3 --explain +php artisan --env=testing g7:bench:pagination --table=orders --json # 기계 판독용 +``` + ### API 문서 Artisan 커맨드 ```bash diff --git a/docs/database-guide.md b/docs/database-guide.md index 22ccae0b..1025646c 100644 --- a/docs/database-guide.md +++ b/docs/database-guide.md @@ -221,6 +221,25 @@ public function up(): void } ``` +#### 정렬을 덮는 복합 인덱스의 선행 컬럼 + +인덱스가 `ORDER BY` 를 덮으려면 **선행 컬럼이 모두 등치로 고정**되고 그 다음 컬럼이 정렬 컬럼이어야 한다. 선택적 필터 컬럼을 선행에 두면, 그 필터를 걸지 않은 기본 진입에서 정렬을 못 덮어 filesort 가 그대로 남는다. + +```php +// ❌ status 는 선택적 필터 — 필터 없는 기본 목록에서 created_at 정렬을 못 덮는다 +$table->index(['status', 'created_at']); + +// ✅ SoftDeletes 가 항상 `deleted_at IS NULL` 을 붙이므로 선행 컬럼이 고정된다 +$table->index(['deleted_at', 'created_at']); +``` + +필터 조합이 다양한 목록은 단일 최적 인덱스가 없다. **항상 걸리는 조건**을 선행에 두고 하나만 추가한다 — 조합마다 인덱스를 추가하면 쓰기 비용만 늘어난다. + +카디널리티가 극히 낮은 컬럼(`deleted_at` 처럼 대부분 NULL)의 **단독** 인덱스는 옵티마이저가 좁은 range 로 오판해 index_merge 후보로 올리므로 거의 항상 해롭다. 다만 기존 단독 인덱스를 제거할 때는 다른 쿼리가 그것에 의존할 수 있으므로, 제거 전 해당 컬럼을 쓰는 쿼리를 전수 확인한다. + +> 후속 검토 항목: `g7_board_posts_deleted_at_index`(단독) — 지연 조인 전환으로 index_merge 는 사라졌으나 인덱스 자체는 남아 있다. 의존 쿼리 전수 확인 후 제거 여부를 별도로 판단한다. +> `board_posts` 에 정렬 전용 복합 인덱스를 **추가하지 않기로** 판정한 근거: 지연 조인 적용 후 재측정에서 index_merge 가 소멸했고 inner 가 기존 `idx_board_posts_list_count` 를 커버링으로 사용한다. 후보 인덱스 `(board_id, is_notice, parent_id, deleted_at, id)` 를 실제로 만들어 강제 비교했을 때의 이득이 1.6배에 그쳐 쓰기 비용 대비 근거가 부족하다. 계측 데이터가 단일 게시판·공지 없음·답글 없음의 균일 분포라 두 후보의 선택도가 같아지는 한계가 있어, 실데이터 분포에서 재확인이 필요하다. + #### `->change()` 패턴의 멱등성 `->change()` 는 이미 존재하는 컬럼의 타입/제약 변경이므로 컬럼 부재 fatal 위험은 없다. 단, MariaDB/MySQL 의 일부 ALTER 가 비-멱등인 경우 (예: enum 값 추가 후 재실행) 가 있으므로 마이그레이션 작성자가 의도 검증 필요. 정적 검사는 `->change()` 라인을 자동 제외하므로 false positive 미발생. diff --git a/modules/_bundled/gnuboard7-hello_module/src/Repositories/MemoRepository.php b/modules/_bundled/gnuboard7-hello_module/src/Repositories/MemoRepository.php index d90d9861..deaeb6ea 100644 --- a/modules/_bundled/gnuboard7-hello_module/src/Repositories/MemoRepository.php +++ b/modules/_bundled/gnuboard7-hello_module/src/Repositories/MemoRepository.php @@ -2,6 +2,7 @@ namespace Modules\Gnuboard7\HelloModule\Repositories; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Pagination\LengthAwarePaginator; use Modules\Gnuboard7\HelloModule\Models\Memo; use Modules\Gnuboard7\HelloModule\Repositories\Contracts\MemoRepositoryInterface; @@ -21,6 +22,8 @@ class MemoRepository implements MemoRepositoryInterface { return Memo::query() ->orderByDesc('created_at') + // audit:allow repository-paginate-column-pruning reason: 학습용 샘플 확장의 메모 테이블 — + // 넓은 컬럼(text/JSON)이 없고, 지연 조인을 넣으면 샘플 코드의 학습 목적이 흐려진다 ->paginate($perPage); } @@ -41,7 +44,7 @@ class MemoRepository implements MemoRepositoryInterface * @param int $id 메모 ID * @return Memo 메모 모델 * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + * @throws ModelNotFoundException */ public function findOrFail(int $id): Memo { diff --git a/modules/_bundled/sirsoft-board/CHANGELOG.md b/modules/_bundled/sirsoft-board/CHANGELOG.md index 3c63e53f..f7932f3e 100644 --- a/modules/_bundled/sirsoft-board/CHANGELOG.md +++ b/modules/_bundled/sirsoft-board/CHANGELOG.md @@ -20,6 +20,9 @@ ### Changed - 사이트맵을 만들 때 게시판과 게시글을 한 번에 모두 메모리에 올리지 않고 나눠서 읽도록 바꿨습니다. 게시글이 많은 사이트에서 사이트맵 생성 중 메모리가 부족해 실패하던 문제가 줄어듭니다. +- 게시글 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 게시글의 본문 앞부분까지 함께 읽어 느려졌지만, 이제 현재 페이지의 게시글만 본문 미리보기를 읽습니다. 공지 노출·답글 표시·다음 페이지 버튼은 이전과 동일하게 동작합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 신고 관리 목록도 같은 방식으로 개선했습니다. 신고 건수·대상 글 상태처럼 목록에 함께 표시되는 정보를 예전에는 건너뛰는 신고까지 모두 계산했지만, 이제 현재 페이지의 신고에 대해서만 계산합니다. 신고가 많이 쌓인 사이트에서 목록 페이지 이동이 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 마이페이지의 「내가 쓴 댓글」·「내가 댓글 단 글」 목록도 같은 방식으로 개선했습니다. 각 글의 최근 댓글을 예전에는 건너뛰는 글까지 모두 찾아봤지만, 이제 현재 페이지의 글에 대해서만 찾습니다. 댓글 활동이 많은 회원의 목록 페이지 이동이 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 개선된 사이트맵 생성 기능을 사용하기 위해 이 모듈은 이제 코어 7.0.6 이상이 필요합니다. ### Fixed @@ -32,7 +35,8 @@ - 데이터베이스에서 일부 컬럼의 설명이 비어 있던 문제를 바로잡았습니다. 회원·작성자 등 다른 표를 가리키는 컬럼이 대상이며, 업그레이드하면 이미 운영 중인 데이터베이스에도 설명이 채워집니다(직접 적어 넣은 설명은 그대로 둡니다). - 관리자 게시글 목록에서 글을 열어 이전글·다음글로 넘어가거나 수정 후 돌아올 때 보고 있던 페이지·검색어·분류가 사라지던 문제를 수정했습니다. 목록 ↔ 상세 ↔ 이전/다음 글 ↔ 작성·수정 폼 어디로 오가든 목록 상태가 유지되며, 신고 관리와 게시판 목록도 동일하게 개선됐습니다. 새로고침 버튼도 걸어 둔 검색·필터를 지운 채 새로 부르지 않고 보고 있던 목록을 그대로 다시 부릅니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) - 관리자 게시글 목록에서 뒤쪽 페이지를 보던 중 검색하거나 게시 상태를 바꾸면 그 페이지 번호가 그대로 유지돼 결과가 없는 빈 화면이 열리던 문제를 수정했습니다. 검색과 상태 변경은 1페이지부터 보여주며, 사용자가 고른 표시 개수와 정렬은 그대로 유지됩니다. 검색창에서 엔터를 치는 경우와 검색 버튼을 누르는 경우가 서로 다르게 동작하던 것도 함께 맞췄습니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) -- 첨부파일을 사용하지 않는 게시판을 아무것도 바꾸지 않고 저장해도 "허용 파일 확장자를 최소 1개 이상 입력하세요" 오류가 나며 저장이 막히던 문제를 수정했습니다. 첨부를 쓰지 않는 게시판에서는 허용 확장자를 비워 두어도 정상적으로 저장됩니다. 게시판 환경설정의 기본값도 동일하게 동작합니다. (#78 @jiwonpapa 님께서 제보해주셨습니다.) +- 첨부파일을 사용하지 않는 게시판을 아무것도 바꾸지 않고 저장해도 "허용 파일 확장자를 최소 1개 이상 입력하세요" 오류가 나며 저장이 막히던 문제를 수정했습니다. 첨부를 쓰지 않는 게시판에서는 허용 확장자를 비워 두어도 정상적으로 저장됩니다. 게시판 환경설정의 기본값도 동일하게 동작합니다. (#78 @glitter-gim 님께서 제보해주셨습니다.) +- 마이페이지 「내가 댓글 단 글」에서 검색어를 입력하면 목록이 열리지 않고 오류가 나던 문제를 수정했습니다. 이제 댓글 내용으로 정상 검색됩니다. - 새 글 표시 기간을 "0시간"(새 글 표시 안 함)으로 저장할 수 없던 문제를 수정했습니다. 0은 원래 새 글 배지를 끄는 값으로 동작하도록 되어 있었으나 저장 단계에서 막혀 있었습니다. - 게시판 환경설정에서 제목·댓글 최소 길이를 0(제한 없음)으로 저장할 수 없던 문제를 수정했습니다. 내용 최소 길이는 0이 허용되어 같은 화면에서 항목마다 동작이 달랐습니다. - 댓글 계층 정리 시 서로가 서로의 부모로 얽혀 최상위 댓글에서 닿을 수 없는 댓글이 아무 기록 없이 넘어가던 문제를 수정했습니다. 이런 댓글은 내용과 위치를 그대로 보존하되, 정리되지 않았다는 사실이 업데이트 기록에 남아 확인할 수 있습니다. diff --git a/modules/_bundled/sirsoft-board/docs/api/boards.md b/modules/_bundled/sirsoft-board/docs/api/boards.md index 93504de1..ed68e4f6 100644 --- a/modules/_bundled/sirsoft-board/docs/api/boards.md +++ b/modules/_bundled/sirsoft-board/docs/api/boards.md @@ -1232,7 +1232,7 @@ HTTP/1.1 200 | use_file_upload | body | boolean | 예 | — | 파일 첨부 기능 사용 여부 (true일 때 allowed_extensions 최소 1개 필수) | | max_file_size | body | integer | 아니오 | min 1, max 200 | 첨부파일 1개당 최대 크기 (MB 단위) | | max_file_count | body | integer | 아니오 | min 1, max 20 | 게시글 1건당 첨부할 수 있는 최대 파일 개수 | -| allowed_extensions | body | array | 조건부 | min 1 | 업로드 허용 확장자 목록 (예: jpg, png, pdf). `use_file_upload=true` 일 때만 필수이며 최소 1개가 있어야 한다. `use_file_upload=false` 이면 검증에서 제외되어 빈 배열·`null` 도 허용된다 | +| allowed_extensions | body | array | 조건부 | min 1 | 업로드 허용 확장자 목록 (예: jpg, png, pdf). `use_file_upload=true` 일 때만 필수이며 최소 1개가 있어야 한다. `use_file_upload=false` 이면 검증에서 제외되어 빈 배열·`null` 도 허용된다. **`allowed_extensions` 만 보내고 `use_file_upload` 를 생략하면 저장된 게시판의 값을 기준으로 판정한다** — 첨부를 쓰지 않는 게시판은 빈 배열도 통과하고, 첨부를 쓰는 게시판은 그대로 차단된다 | | board_manager_ids | body | array | 예 | min 1 | board manager 식별자 배열 | | board_step_ids | body | array | 아니오 | — | board step 식별자 배열 | | permissions | body | array | 예 | — | 게시판별 세부 권한 매트릭스 (권한 키별 mode/roles — 각 권한을 all 또는 특정 역할에 부여) | diff --git a/modules/_bundled/sirsoft-board/src/Http/Requests/UpdateBoardRequest.php b/modules/_bundled/sirsoft-board/src/Http/Requests/UpdateBoardRequest.php index 8cd0c6e2..d2e37eb8 100644 --- a/modules/_bundled/sirsoft-board/src/Http/Requests/UpdateBoardRequest.php +++ b/modules/_bundled/sirsoft-board/src/Http/Requests/UpdateBoardRequest.php @@ -35,12 +35,25 @@ class UpdateBoardRequest extends FormRequest /** * 검증 전 데이터 전처리 * - * blocked_keywords가 문자열로 전송된 경우 배열로 변환합니다. + * blocked_keywords가 문자열로 전송된 경우 배열로 변환하고, 조건부 배제(`exclude_if`)의 + * 판정 기준이 되는 토글이 요청에 없으면 기존 게시판의 저장값을 주입합니다. */ protected function prepareForValidation(): void { $data = $this->all(); + // allowed_extensions 만 부분 전송되고 use_file_upload 가 없으면 exclude_if 가 동작하지 + // 않는다(ValidatesAttributes::validateExcludeIf 는 조건 필드가 데이터에 없으면 배제하지 + // 않는다). 그러면 첨부를 쓰지 않는 게시판인데도 확장자 필수 규칙이 발화한다. + // 기존 게시판의 저장값을 주입해 "저장된 상태 + 페이로드" 결과 상태로 판정한다. + if (array_key_exists('allowed_extensions', $data) && ! array_key_exists('use_file_upload', $data)) { + $board = $this->route('board'); + + if ($board !== null && isset($board->use_file_upload)) { + $data['use_file_upload'] = (bool) $board->use_file_upload; + } + } + // blocked_keywords가 문자열이면 배열로 변환 (validation 전) if (isset($data['blocked_keywords']) && is_string($data['blocked_keywords'])) { $keywords = array_filter( diff --git a/modules/_bundled/sirsoft-board/src/Repositories/BoardRepository.php b/modules/_bundled/sirsoft-board/src/Repositories/BoardRepository.php index aa578fc3..a0eeea71 100644 --- a/modules/_bundled/sirsoft-board/src/Repositories/BoardRepository.php +++ b/modules/_bundled/sirsoft-board/src/Repositories/BoardRepository.php @@ -4,6 +4,8 @@ namespace Modules\Sirsoft\Board\Repositories; use App\Enums\UserStatus; use App\Helpers\PermissionHelper; +use App\Models\Attachment; +use App\Models\User; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -147,6 +149,8 @@ class BoardRepository implements BoardRepositoryInterface // 권한 스코프 필터링 PermissionHelper::applyPermissionScope($query, 'sirsoft-board.boards.read'); + // audit:allow repository-paginate-column-pruning reason: 게시판 정의 테이블 — + // 행 수가 운영자가 만든 게시판 수에 묶이고(글이 아니라 게시판), 넓은 컬럼이 없다 return $query->paginate($perPage); } @@ -434,27 +438,32 @@ class BoardRepository implements BoardRepositoryInterface default => now()->subYear(), }; - $prefix = DB::getTablePrefix(); + // 테이블명은 모델에서 얻는다 (문자열 하드코딩 금지). LEFT() 는 빌더에 대응 표현이 + // 없는 표준 SQL 함수라 이 컬럼 하나만 raw 로 두고, 프리픽스는 연결 설정에서 읽는다. + $postsTable = (new Post)->getTable(); + $usersTable = (new User)->getTable(); + $attachmentsTable = (new Attachment)->getTable(); + $prefixedPostsTable = DB::getTablePrefix().$postsTable; $query = Post::query() ->with('board:id,slug,name') - ->leftJoin('users', 'board_posts.user_id', '=', 'users.id') - ->leftJoin('attachments', function ($join) { - $join->on('attachments.attachmentable_id', '=', 'users.id') - ->where('attachments.attachmentable_type', '=', 'App\\Models\\User') - ->where('attachments.collection', '=', 'avatar'); + ->leftJoin($usersTable, "{$postsTable}.user_id", '=', "{$usersTable}.id") + ->leftJoin($attachmentsTable, function ($join) use ($usersTable, $attachmentsTable) { + $join->on("{$attachmentsTable}.attachmentable_id", '=', "{$usersTable}.id") + ->where("{$attachmentsTable}.attachmentable_type", '=', User::class) + ->where("{$attachmentsTable}.collection", '=', 'avatar'); }) ->select([ 'board_posts.id', 'board_posts.board_id', 'board_posts.title', - DB::raw("LEFT(`{$prefix}board_posts`.`content`, 300) as content_raw"), + DB::raw("LEFT(`{$prefixedPostsTable}`.`content`, 300) as content_raw"), 'board_posts.user_id', 'board_posts.author_name as guest_author_name', - 'users.name as user_name', - 'users.email as user_email', - 'users.status as user_status', - 'attachments.hash as attachment_hash', + "{$usersTable}.name as user_name", + "{$usersTable}.email as user_email", + "{$usersTable}.status as user_status", + "{$attachmentsTable}.hash as attachment_hash", 'board_posts.view_count', 'board_posts.comments_count', 'board_posts.created_at', diff --git a/modules/_bundled/sirsoft-board/src/Repositories/CommentRepository.php b/modules/_bundled/sirsoft-board/src/Repositories/CommentRepository.php index 95e980d4..0ce04015 100644 --- a/modules/_bundled/sirsoft-board/src/Repositories/CommentRepository.php +++ b/modules/_bundled/sirsoft-board/src/Repositories/CommentRepository.php @@ -3,6 +3,7 @@ namespace Modules\Sirsoft\Board\Repositories; use App\Helpers\PermissionHelper; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; use Carbon\CarbonImmutable; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\ModelNotFoundException; @@ -11,6 +12,7 @@ use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Board\Enums\TriggerType; use Modules\Sirsoft\Board\Models\Board; use Modules\Sirsoft\Board\Models\Comment; +use Modules\Sirsoft\Board\Models\Post; use Modules\Sirsoft\Board\Repositories\Contracts\CommentRepositoryInterface; use Modules\Sirsoft\Board\Traits\FormatsBoardDate; @@ -22,6 +24,7 @@ use Modules\Sirsoft\Board\Traits\FormatsBoardDate; class CommentRepository implements CommentRepositoryInterface { use FormatsBoardDate; + use PaginatesWithDeferredJoin; /** * 특정 게시글의 댓글 목록을 조회합니다. @@ -546,18 +549,16 @@ class CommentRepository implements CommentRepositoryInterface // 비활성 게시판 제외 — JOIN 대신 whereNotIn으로 인덱스 활용 $inactiveBoardIds = Board::where('is_active', false)->pluck('id')->all(); + // 관계/정렬은 지연 조인이 담당한다 (inner 는 키 컬럼만 조회) $query = Comment::query() - ->select('board_comments.*') ->where('board_comments.user_id', $userId) // 삭제된 게시글 제외 — whereExists로 JOIN 대체 ->whereExists(function ($q) { $q->select(DB::raw(1)) - ->from('board_posts') + ->from((new Post)->getTable()) ->whereColumn('board_posts.id', 'board_comments.post_id') ->whereNull('board_posts.deleted_at'); - }) - ->with(['post.board']) - ->orderBy('board_comments.created_at', $orderDirection); + }); // 비활성 게시판 제외 if (! empty($inactiveBoardIds)) { @@ -578,7 +579,25 @@ class CommentRepository implements CommentRepositoryInterface $query->where('board_comments.content', 'like', $keyword); } - $paginator = $query->paginate($perPage, ['*'], 'page', null, $cachedTotal); + // 캐시된 total 이 있으면 COUNT 를 건너뛴다. 검색/게시판 필터가 걸린 조회는 캐시를 + // 쓰지 않으므로 이때는 trait 이 직접 COUNT 한다. + // + // 목록 컬럼은 아래 through() 가 실제로 읽는 것만 남긴다 (post_id 는 post 관계 FK). + // 넓은 컬럼인 content 는 화면에 표시하므로 유지하되, 지연 조인으로 읽는 행 수가 + // 페이지 크기에 고정된다. + $paginator = $this->paginateWithDeferredJoin( + query: $query, + columns: [ + 'board_comments.id', + 'board_comments.post_id', + 'board_comments.content', + 'board_comments.created_at', + ], + sort: [['column' => 'board_comments.created_at', 'direction' => $orderDirection]], + perPage: $perPage, + relations: ['post.board'], + total: $cachedTotal, + ); // paginate 후 현재 페이지 항목에만 PHP 가공 적용 $paginator->through(function (Comment $comment) { diff --git a/modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php b/modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php index 6eb3a160..d360a09f 100644 --- a/modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php +++ b/modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php @@ -4,6 +4,7 @@ namespace Modules\Sirsoft\Board\Repositories; use App\Enums\PermissionType; use App\Helpers\PermissionHelper; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; use App\Search\Engines\DatabaseFulltextEngine; use Carbon\CarbonImmutable; use Illuminate\Contracts\Pagination\Paginator; @@ -31,6 +32,7 @@ class PostRepository implements PostRepositoryInterface { use ChecksBoardPermission; use FormatsBoardDate; + use PaginatesWithDeferredJoin; /** * PostRepository 생성자 @@ -692,12 +694,13 @@ class PostRepository implements PostRepositoryInterface // → 콜드 스타트(버퍼 풀 미적재)에서도 ~2ms (range scan 대비 100배 이상 빠름) $aggregateFunc = $strictOp === '<' ? 'MAX' : 'MIN'; $subQuery = $baseQuery() - ->select(DB::raw("{$aggregateFunc}(`{$orderBy}`)")) + ->select(DB::raw("{$aggregateFunc}({$orderBy})")) ->where($orderBy, $strictOp, $currentValue); + // 서브쿼리는 빌더 그대로 넘긴다 — toSql() 로 문자열을 붙이고 mergeBindings 로 + // 바인딩을 손수 옮기면 조건 추가 순서에 따라 바인딩이 어긋날 수 있다. return $baseQuery() - ->where($orderBy, DB::raw("({$subQuery->toSql()})")) - ->mergeBindings($subQuery->getQuery()) + ->where($orderBy, '=', $subQuery) ->orderBy('id', $idSort) ->first(); } @@ -827,22 +830,33 @@ class PostRepository implements PostRepositoryInterface $orderDirection = 'desc'; // 기본값으로 폴백 } - // 정렬 적용 (id를 2차 정렬로 추가 — 동일 값 내 순서를 결정론적으로 보장) + // 정렬 스펙 (id를 2차 정렬로 추가 — 동일 값 내 순서를 결정론적으로 보장) // created_at은 초 단위라 실질적 중복이 드물지만, view_count/title/author_name은 중복이 많음 - $parentQuery->orderBy($orderBy, $orderDirection)->orderBy('id', $orderDirection); - - if (! empty($relations)) { - $parentQuery->with($relations); - } + $sort = [['column' => $orderBy, 'direction' => $orderDirection]]; // 페이지네이션 여부에 따라 분기 if ($perPage !== null) { - // simplePaginate 사용 — COUNT(*) 쿼리 제거로 대량 데이터 성능 개선 - // total은 Service 캐시 카운트로 별도 제공 - $paginator = $parentQuery->simplePaginate($perPage, $columns, 'page', $currentPage); + // 지연 조인 — 이번 페이지의 원글 ID 를 먼저 구하고, 그 ID 에 대해서만 목록 컬럼과 + // 관계를 읽는다. OFFSET 이 건너뛰는 원글의 본문 앞부분(SUBSTRING)까지 읽던 비용이 + // 사라진다. COUNT 는 기존과 같이 수행하지 않는다(total 은 Service 캐시 카운트). + $paginator = $this->paginateWithDeferredJoin( + query: $parentQuery, + columns: $columns, + sort: $sort, + perPage: $perPage, + page: (int) $currentPage, + relations: $relations, + simple: true, + ); $parents = $paginator->getCollection(); } else { // 전체 조회 + $parentQuery->orderBy($orderBy, $orderDirection)->orderBy('id', $orderDirection); + + if (! empty($relations)) { + $parentQuery->with($relations); + } + $parents = $parentQuery->get($columns); $paginator = null; } @@ -1052,10 +1066,9 @@ class PostRepository implements PostRepositoryInterface $inactiveBoardIds = $this->getInactiveBoardIds(); $allExcludeIds = array_unique(array_merge($excludeBoardIds, $inactiveBoardIds)); + // 관계/정렬은 지연 조인이 담당한다 (inner 는 키 컬럼만 조회) $query = Post::query() - ->where('board_posts.user_id', $userId) - ->with('board') - ->orderBy($orderColumn, $orderDirection); + ->where('board_posts.user_id', $userId); if ($boardIdFilter) { $query->where('board_posts.board_id', $boardIdFilter); @@ -1078,7 +1091,26 @@ class PostRepository implements PostRepositoryInterface }); } - $paginator = $query->paginate($perPage, ['*'], 'page', null, $cachedTotal); + // 본문(content)은 목록 가공에 앞부분만 쓰이지만 태그 제거 후 길이가 줄어드는 것을 감안해 + // 넉넉히 잘라 읽는다. 잘라 읽기는 이번 페이지 분량(outer)에서만 일어난다. + $listColumns = [ + 'board_posts.id', 'board_posts.board_id', 'board_posts.user_id', + 'board_posts.title', 'board_posts.status', 'board_posts.is_secret', + 'board_posts.content_mode', 'board_posts.view_count', 'board_posts.comments_count', + 'board_posts.created_at', 'board_posts.updated_at', 'board_posts.deleted_at', + // DB::raw 는 테이블 프리픽스가 적용되지 않는다. 단일 테이블 조회라 컬럼만 적으면 충분하다. + DB::raw('SUBSTRING(content, 1, 1000) as content'), + ]; + + // 캐시된 total 을 그대로 통과시켜 COUNT 를 다시 수행하지 않는다 + $paginator = $this->paginateWithDeferredJoin( + query: $query, + columns: $listColumns, + sort: [['column' => $orderColumn, 'direction' => $orderDirection]], + perPage: $perPage, + relations: ['board'], + total: $cachedTotal, + ); // paginate 후 10건에만 PHP 가공 적용 (N+1 아님) $paginator->through(function ($post) { @@ -1125,59 +1157,88 @@ class PostRepository implements PostRepositoryInterface int $perPage, ?int $cachedTotal = null ): LengthAwarePaginator { - // DB::raw() / whereRaw() 내부 raw SQL은 prefix 자동 적용이 안 되므로 명시적으로 처리 - $prefix = DB::getTablePrefix(); - $commentsTable = $prefix.'board_comments'; - $postsTable = $prefix.'board_posts'; + // 테이블명은 모델에서 얻는다 — 문자열로 박으면 테이블명이 바뀔 때 조용히 깨진다 + $postsTable = (new Post)->getTable(); + $commentsTable = (new Comment)->getTable(); // 비활성 게시판 제외 — JOIN 없이 인덱스 활용 $inactiveBoardIds = $this->getInactiveBoardIds(); $allExcludeIds = array_unique(array_merge($excludeBoardIds, $inactiveBoardIds)); - $latestCommentSub = DB::table(DB::raw("{$commentsTable} as bc_outer")) - ->selectRaw('bc_outer.post_id, bc_outer.content, bc_outer.created_at') - ->whereRaw("bc_outer.id = ( - SELECT bc2.id FROM {$commentsTable} bc2 - WHERE bc2.post_id = bc_outer.post_id - AND bc2.user_id = ? - AND bc2.deleted_at IS NULL - ORDER BY bc2.created_at DESC - LIMIT 1 - ) AND bc_outer.user_id = ? AND bc_outer.deleted_at IS NULL", [$userId, $userId]); + /** + * 내가 이 글에 단 댓글 조건 (검색어까지 반영 — 활동 판정 · 건수 집계 공통). + * + * @param \Illuminate\Contracts\Database\Eloquent\Builder $q 댓글 하위 쿼리 + */ + $myComment = function ($q) use ($userId, $search) { + $q->where('user_id', $userId)->whereNull('deleted_at'); - $query = Post::query() - ->join(DB::raw("{$commentsTable} AS uc"), function ($join) use ($userId, $postsTable) { - $join->on(DB::raw("{$postsTable}.id"), '=', DB::raw('uc.post_id')) - ->whereRaw('uc.user_id = ?', [$userId]) - ->whereRaw('uc.deleted_at IS NULL'); - }) - ->leftJoinSub($latestCommentSub, 'lc', 'board_posts.id', '=', 'lc.post_id') - ->select([ - 'board_posts.*', - DB::raw('COUNT(DISTINCT uc.id) as activity_count'), - // board_posts.* 에 이미 comments_count 가 포함되므로 중복 지정 금지 - // (pagination count 쿼리가 subquery 로 감싸질 때 SQLSTATE[42S21] 유발) - 'lc.content as comment_content', - 'lc.created_at as comment_created_at', - ]) - ->with('board') - ->groupBy('board_posts.id', 'board_posts.board_id', 'board_posts.comments_count', 'lc.content', 'lc.created_at') - ->orderBy($orderColumn, $orderDirection); + if ($search) { + $q->where('content', 'like', '%'.$this->escapeLikeKeyword($search).'%'); + } + }; + + // "내가 댓글을 단 글" 집합은 EXISTS 로 정한다. 조인으로 행을 불린 뒤 groupBy 로 접는 + // 방식은 inner 가 읽는 행 수를 댓글 수만큼 늘리고, 그룹 쿼리라 총 건수도 서브쿼리로 + // 감싸야 한다. EXISTS 는 글 1건당 1행이라 둘 다 필요 없다. + $query = Post::query()->whereHas('comments', $myComment); if ($boardIdFilter) { - $query->where('board_posts.board_id', $boardIdFilter); + $query->where("{$postsTable}.board_id", $boardIdFilter); } if (! empty($allExcludeIds)) { - $query->whereNotIn('board_posts.board_id', $allExcludeIds); + $query->whereNotIn("{$postsTable}.board_id", $allExcludeIds); } - if ($search) { - $keyword = $this->escapeLikeKeyword($search); - $query->where('uc.content', 'like', "%{$keyword}%"); - } + // 목록 컬럼: 본문 전체 대신 앞부분만 (가공 후 표시 자수보다 넉넉히 잘라 읽는다). + // SUBSTRING 은 빌더에 대응 표현이 없는 표준 SQL 함수라 이 한 곳만 raw 로 남긴다. + // 테이블명·프리픽스는 문자열로 박지 않고 모델과 연결 설정에서 얻는다. + $listColumns = [ + "{$postsTable}.id", "{$postsTable}.board_id", "{$postsTable}.user_id", + "{$postsTable}.title", "{$postsTable}.status", "{$postsTable}.is_secret", + "{$postsTable}.content_mode", "{$postsTable}.view_count", "{$postsTable}.comments_count", + "{$postsTable}.created_at", "{$postsTable}.updated_at", "{$postsTable}.deleted_at", + DB::raw('SUBSTRING('.DB::getTablePrefix().$postsTable.'.content, 1, 1000) as content'), + 'lc.content as comment_content', + 'lc.created_at as comment_created_at', + ]; - $paginator = $query->paginate($perPage, ['*'], 'page', null, $cachedTotal); + // 정렬은 항상 board_posts 컬럼(작성일/조회수)이라 inner 에서 그대로 적용된다. + // 캐시된 total 이 있으면 COUNT 를 건너뛴다. 검색/게시판 필터가 걸린 조회는 캐시를 쓰지 + // 않아 total 이 null 로 들어오며, 이때 trait 이 그룹 쿼리를 서브쿼리로 감싸 그룹 수를 센다. + $paginator = $this->paginateWithDeferredJoin( + query: $query, + columns: $listColumns, + sort: [['column' => $orderColumn, 'direction' => $orderDirection]], + perPage: $perPage, + relations: ['board'], + keyName: 'id', + total: $cachedTotal, + // 최근 댓글 조회와 활동 건수 집계는 이번 페이지의 게시글에 대해서만 실행한다. + // inner 에 두면 건너뛸 행 전체에 대해 상관 서브쿼리가 돌아간다. + outerUsing: function ($outer) use ($userId, $myComment, $postsTable, $commentsTable) { + // 글마다 "내가 단 댓글 중 가장 최근 1건" — 원래 raw SQL 로 쓰던 상관 서브쿼리를 + // 빌더로 옮겼다. created_at 동률에서 순서가 흔들리지 않도록 id 를 덧붙인다. + $latest = DB::table("{$commentsTable} as bc_outer") + ->select('bc_outer.post_id', 'bc_outer.content', 'bc_outer.created_at') + ->where('bc_outer.user_id', $userId) + ->whereNull('bc_outer.deleted_at') + ->where('bc_outer.id', '=', function ($q) use ($userId, $commentsTable) { + $q->select('bc2.id') + ->from("{$commentsTable} as bc2") + ->whereColumn('bc2.post_id', 'bc_outer.post_id') + ->where('bc2.user_id', $userId) + ->whereNull('bc2.deleted_at') + ->orderByDesc('bc2.created_at') + ->orderByDesc('bc2.id') + ->limit(1); + }); + + $outer->leftJoinSub($latest, 'lc', "{$postsTable}.id", '=', 'lc.post_id') + ->withCount(['comments as activity_count' => $myComment]); + }, + ); // paginate 후 10건에만 PHP 가공 적용 $paginator->through(function ($post) { diff --git a/modules/_bundled/sirsoft-board/src/Repositories/ReportRepository.php b/modules/_bundled/sirsoft-board/src/Repositories/ReportRepository.php index 5223011f..ad3e05fb 100644 --- a/modules/_bundled/sirsoft-board/src/Repositories/ReportRepository.php +++ b/modules/_bundled/sirsoft-board/src/Repositories/ReportRepository.php @@ -3,9 +3,12 @@ namespace Modules\Sirsoft\Board\Repositories; use App\Helpers\PermissionHelper; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use App\Search\Engines\DatabaseFulltextEngine; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\QueryException; use Illuminate\Pagination\LengthAwarePaginator; @@ -13,6 +16,8 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Board\Enums\ReportStatus; use Modules\Sirsoft\Board\Exceptions\DuplicateReportException; +use Modules\Sirsoft\Board\Models\Comment; +use Modules\Sirsoft\Board\Models\Post; use Modules\Sirsoft\Board\Models\Report; use Modules\Sirsoft\Board\Models\ReportLog; use Modules\Sirsoft\Board\Repositories\Contracts\ReportRepositoryInterface; @@ -24,16 +29,32 @@ use Modules\Sirsoft\Board\Repositories\Contracts\ReportRepositoryInterface; */ class ReportRepository implements ReportRepositoryInterface { + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + /** + * 신고 대상 종류(`target_type`) → 대상 모델 매핑. + * + * 신고는 게시글 또는 댓글을 가리키므로 대상 테이블이 갈린다. 갈래를 SQL CASE 문자열로 + * 잇는 대신 이 매핑을 돌며 갈래별 빌더 조건을 만든다. + * + * @var array> + */ + private const TARGET_MODELS = [ + 'post' => Post::class, + 'comment' => Comment::class, + ]; + /** * 신고 목록을 페이지네이션하여 조회합니다. * * @param array $filters 필터 조건 * @param int $perPage 페이지당 항목 수 + * @return LengthAwarePaginator 페이지네이션된 신고 목록 */ public function paginate(array $filters = [], int $perPage = 15): LengthAwarePaginator { - $query = Report::query() - ->with(['board', 'author', 'processor']); + $query = Report::query(); // 권한 스코프 필터링 PermissionHelper::applyPermissionScope($query, 'sirsoft-board.reports.view'); @@ -64,10 +85,14 @@ class ReportRepository implements ReportRepositoryInterface $query->where('created_at', '<=', $filters['reported_at_to'].' 23:59:59'); } - // 정렬 - $query->orderBy('created_at', 'desc'); - - return $query->paginate($perPage); + // 지연 조인: inner 는 id 만 훑고, 관계 로딩은 해당 페이지에만 적용된다 + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [['column' => 'created_at', 'direction' => 'desc']], + perPage: $perPage, + relations: ['board', 'author', 'processor'], + ); } /** @@ -225,18 +250,35 @@ class ReportRepository implements ReportRepositoryInterface * * @param array $filters 필터 조건 * @param int $perPage 페이지당 항목 수 + * @return LengthAwarePaginator 페이지네이션된 신고 케이스 목록 */ public function paginateGrouped(array $filters = [], int $perPage = 15): LengthAwarePaginator { - $tableName = (new Report)->getTable(); - $prefix = DB::getTablePrefix(); - $fullTableName = $prefix.$tableName; - $logsTable = $prefix.'boards_report_logs'; - $postsTable = $prefix.'board_posts'; - $commentsTable = $prefix.'board_comments'; + // 테이블명은 전부 모델에서 얻는다 (문자열 하드코딩 금지) + $reportsTable = (new Report)->getTable(); - $query = Report::query() - ->with(['board', 'author', 'processor', 'logs' => fn ($q) => $q->oldest()->limit(1)->with('reporter')]); + /** + * 대상(게시글/댓글) 컬럼을 신고 행에 대응시켜 읽는 상관 서브쿼리를 만듭니다. + * + * 신고는 target_type 에 따라 게시글 또는 댓글을 가리키므로 대상 테이블이 갈린다. + * 두 갈래를 CASE 로 잇는 대신, 갈래마다 빌더 서브쿼리를 만들고 그 중 조건에 맞는 + * 쪽만 값을 내도록 한다 — 결과는 같고 SQL 문자열 조립이 사라진다. + * + * @param class-string $model 대상 모델 + * @param string $targetType 이 갈래가 담당하는 target_type 값 + * @param string $column 읽어올 대상 컬럼 + * @return Builder 상관 서브쿼리 + */ + $targetColumn = function (string $model, string $targetType, string $column) use ($reportsTable) { + return $model::query() + ->select($column) + ->whereColumn((new $model)->getTable().'.board_id', "{$reportsTable}.board_id") + ->whereColumn((new $model)->getTable().'.id', "{$reportsTable}.target_id") + ->where("{$reportsTable}.target_type", $targetType) + ->limit(1); + }; + + $query = Report::query(); // 상태 필터 if (! empty($filters['status'])) { @@ -272,52 +314,64 @@ class ReportRepository implements ReportRepositoryInterface // 검색 $this->applySearchConditions($query, $filters, ['post_title']); - // 서브쿼리: 신고 건수, 대상 상태, trigger_type, post_id - $query->selectRaw("`{$fullTableName}`.*") - ->selectRaw("( - SELECT COUNT(*) - FROM `{$logsTable}` - WHERE `{$logsTable}`.report_id = `{$fullTableName}`.id - ) AS report_count") - ->selectRaw("( - CASE `{$fullTableName}`.target_type - WHEN 'post' THEN (SELECT bp.status FROM `{$postsTable}` bp WHERE bp.board_id = `{$fullTableName}`.board_id AND bp.id = `{$fullTableName}`.target_id LIMIT 1) - WHEN 'comment' THEN (SELECT bc.status FROM `{$commentsTable}` bc WHERE bc.board_id = `{$fullTableName}`.board_id AND bc.id = `{$fullTableName}`.target_id LIMIT 1) - END - ) AS target_status") - ->selectRaw("( - CASE `{$fullTableName}`.target_type - WHEN 'post' THEN (SELECT bp.trigger_type FROM `{$postsTable}` bp WHERE bp.board_id = `{$fullTableName}`.board_id AND bp.id = `{$fullTableName}`.target_id LIMIT 1) - WHEN 'comment' THEN (SELECT bc.trigger_type FROM `{$commentsTable}` bc WHERE bc.board_id = `{$fullTableName}`.board_id AND bc.id = `{$fullTableName}`.target_id LIMIT 1) - END - ) AS target_trigger_type") - ->selectRaw("( - CASE `{$fullTableName}`.target_type - WHEN 'post' THEN `{$fullTableName}`.target_id - WHEN 'comment' THEN (SELECT bc.post_id FROM `{$commentsTable}` bc WHERE bc.board_id = `{$fullTableName}`.board_id AND bc.id = `{$fullTableName}`.target_id LIMIT 1) - END - ) AS target_post_id"); - - // 대상 상태 필터 + // 대상 상태 필터 — 대상 종류에 따라 갈리는 조건을 CASE 문자열 대신 갈래별 조건으로 쓴다 if (! empty($filters['target_status'])) { $targetStatuses = is_array($filters['target_status']) ? $filters['target_status'] : [$filters['target_status']]; $targetStatuses = array_filter($targetStatuses, fn ($v) => $v !== 'all'); + if (! empty($targetStatuses)) { - $placeholders = implode(',', array_fill(0, count($targetStatuses), '?')); - $query->whereRaw("( - CASE `{$fullTableName}`.target_type - WHEN 'post' THEN (SELECT bp.status FROM `{$postsTable}` bp WHERE bp.board_id = `{$fullTableName}`.board_id AND bp.id = `{$fullTableName}`.target_id LIMIT 1) - WHEN 'comment' THEN (SELECT bc.status FROM `{$commentsTable}` bc WHERE bc.board_id = `{$fullTableName}`.board_id AND bc.id = `{$fullTableName}`.target_id LIMIT 1) - END - ) IN ({$placeholders})", $targetStatuses); + $query->where(function (Builder $outerWhere) use ($targetStatuses, $reportsTable) { + foreach (self::TARGET_MODELS as $targetType => $model) { + $outerWhere->orWhere(function (Builder $branch) use ($targetType, $model, $targetStatuses, $reportsTable) { + $branch->where("{$reportsTable}.target_type", $targetType) + ->whereIn("{$reportsTable}.target_id", $model::query() + ->select('id') + ->whereIn('status', $targetStatuses) + ->whereColumn((new $model)->getTable().'.board_id', "{$reportsTable}.board_id")); + }); + } + }); } } // 정렬: last_reported_at DESC (재신고 시 위로 올라옴) - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy('last_reported_at', $sortOrder); + $sortOrder = $this->normalizeSortDirection($filters['sort_order'] ?? null); - return $query->paginate($perPage); + $paginator = $this->paginateWithDeferredJoin( + query: $query, + columns: ["{$reportsTable}.*"], + sort: [['column' => 'last_reported_at', 'direction' => $sortOrder]], + perPage: $perPage, + relations: ['board', 'author', 'processor', 'logs' => fn ($q) => $q->oldest()->limit(1)->with('reporter')], + // 신고 건수와 대상(게시글/댓글) 정보는 스캔되는 행마다 계산되므로 + // outer(이번 페이지 $perPage 건)에서만 붙인다. + outerUsing: function (Builder $outer) use ($reportsTable, $targetColumn) { + $outer->select("{$reportsTable}.*")->withCount(['logs as report_count']); + + foreach (self::TARGET_MODELS as $targetType => $model) { + $outer->addSelect([ + "target_status_{$targetType}" => $targetColumn($model, $targetType, 'status'), + "target_trigger_type_{$targetType}" => $targetColumn($model, $targetType, 'trigger_type'), + ]); + } + + // 대상 글 ID — 게시글 신고는 target_id 자체, 댓글 신고는 그 댓글이 달린 글 + $outer->addSelect(['target_post_id_comment' => $targetColumn(Comment::class, 'comment', 'post_id')]); + }, + ); + + // 갈래별로 나눠 읽은 대상 정보를 화면이 쓰는 단일 속성으로 합친다. + // 이번 페이지 항목에만 적용되므로 추가 조회가 발생하지 않는다. + return $paginator->through(function (Report $report) { + $report->setAttribute('target_status', $report->target_status_post ?? $report->target_status_comment); + $report->setAttribute('target_trigger_type', $report->target_trigger_type_post ?? $report->target_trigger_type_comment); + $report->setAttribute( + 'target_post_id', + $report->target_type === 'post' ? $report->target_id : $report->target_post_id_comment + ); + + return $report; + }); } /** @@ -525,12 +579,15 @@ class ReportRepository implements ReportRepositoryInterface * @param int $reportId 신고 케이스 ID * @param int $perPage 페이지당 항목 수 * @param int $page 페이지 번호 + * @return LengthAwarePaginator 페이지네이션된 신고자 로그 */ public function paginateLogsByReport(int $reportId, int $perPage = 10, int $page = 1): LengthAwarePaginator { return ReportLog::where('report_id', $reportId) ->with('reporter') ->latest() + // audit:allow repository-paginate-column-pruning reason: 신고 케이스 1건에 종속된 신고자 로그 — + // where(report_id) 로 이미 좁혀져 OFFSET 이 깊어질 수 없다 ->paginate($perPage, ['*'], 'page', $page); } @@ -583,12 +640,12 @@ class ReportRepository implements ReportRepositoryInterface * ID 목록으로 조회하고 ID 키 맵으로 반환합니다 (bulk activity log lookup). * * @param array $ids ID 목록 - * @return \Illuminate\Database\Eloquent\Collection + * @return Collection */ - public function findByIdsKeyed(array $ids): \Illuminate\Database\Eloquent\Collection + public function findByIdsKeyed(array $ids): Collection { if (empty($ids)) { - return new \Illuminate\Database\Eloquent\Collection(); + return new Collection; } return Report::whereIn('id', $ids)->get()->keyBy('id'); diff --git a/modules/_bundled/sirsoft-board/tests/Feature/BoardRequestTest.php b/modules/_bundled/sirsoft-board/tests/Feature/BoardRequestTest.php index bc7da535..57224da8 100644 --- a/modules/_bundled/sirsoft-board/tests/Feature/BoardRequestTest.php +++ b/modules/_bundled/sirsoft-board/tests/Feature/BoardRequestTest.php @@ -16,6 +16,7 @@ use Modules\Sirsoft\Board\Http\Requests\UpdateBoardRequest; use Modules\Sirsoft\Board\Models\Board; use Modules\Sirsoft\Board\Models\BoardType; use Modules\Sirsoft\Board\Tests\ModuleTestCase; +use PHPUnit\Framework\Attributes\DataProvider; /** * 게시판 FormRequest 검증 통합 테스트 @@ -287,26 +288,30 @@ class BoardRequestTest extends ModuleTestCase /** * Mock Board 객체 생성 */ - private function getMockBoard(): object + private function getMockBoard(bool $useFileUpload = true): object { return (object) [ 'slug' => 'notice', 'categories' => ['일반', '중요'], + // prepareForValidation() 이 exclude_if 판정 기준으로 주입하는 저장값 + 'use_file_upload' => $useFileUpload, ]; } /** * UpdateBoardRequest 인스턴스 생성 (route 파라미터 포함) + * + * @param bool $boardUseFileUpload 기존 게시판에 저장된 첨부 사용 여부 */ - private function makeUpdateRequest(): UpdateBoardRequest + private function makeUpdateRequest(bool $boardUseFileUpload = true): UpdateBoardRequest { $request = new UpdateBoardRequest; - $request->setRouteResolver(function () { + $request->setRouteResolver(function () use ($boardUseFileUpload) { $route = \Mockery::mock(Route::class); $route->shouldReceive('parameter') ->with('board', null) - ->andReturn($this->getMockBoard()); + ->andReturn($this->getMockBoard($boardUseFileUpload)); return $route; }); @@ -314,6 +319,27 @@ class BoardRequestTest extends ModuleTestCase return $request; } + /** + * prepareForValidation() 을 거친 검증 입력을 만듭니다. + * + * `Validator::make($data, $request->rules())` 만으로는 전처리가 실행되지 않아 + * 조건 필드 주입(기존 게시판 값)을 검증할 수 없다. + * + * @param UpdateBoardRequest $request 대상 요청 + * @param array $data 전송 페이로드 + * @return array 전처리를 마친 입력 + */ + private function prepareUpdateInput(UpdateBoardRequest $request, array $data): array + { + $request->merge($data); + + $prepare = new \ReflectionMethod($request, 'prepareForValidation'); + $prepare->setAccessible(true); + $prepare->invoke($request); + + return $request->all(); + } + /** * 정상적인 수정 데이터 */ @@ -1010,21 +1036,89 @@ class BoardRequestTest extends ModuleTestCase } /** - * UpdateBoardRequest - allowed_extensions 빈 배열 전송 시 검증 실패해야 함 (회귀) + * UpdateBoardRequest - 첨부 사용 게시판에서 allowed_extensions 빈 배열은 실패 (케이스 2) * - * sometimes + array + min:1 로, 키가 전송되면 빈 배열은 차단된다. - * (nullable 제거로 빈 배열이 통과되던 버그 차단) + * 첨부를 쓰는 게시판에 확장자를 비워 저장하면 이후 모든 파일이 거부되므로 차단한다. + * 첨부 미사용 게시판은 exclude_if 로 배제되어 통과한다(케이스 3 참조) — 이 테스트는 + * 그 경계 중 "차단되어야 하는 쪽"만 고정한다. */ - public function test_update_request_empty_allowed_extensions_fails(): void + public function test_update_request_empty_allowed_extensions_fails_when_upload_enabled(): void { $request = $this->makeUpdateRequest(); - $validator = Validator::make(['allowed_extensions' => []], $request->rules()); + $validator = Validator::make( + ['use_file_upload' => true, 'allowed_extensions' => []], + $request->rules() + ); - $this->assertFalse($validator->passes(), 'allowed_extensions=[] 전송 시 실패해야 함'); + $this->assertFalse($validator->passes(), 'use_file_upload=true + [] 는 실패해야 함'); $this->assertArrayHasKey('allowed_extensions', $validator->errors()->toArray()); } + /** + * UpdateBoardRequest - 첨부 미사용 게시판의 빈 확장자는 통과 (케이스 3 · GH #78) + */ + public function test_update_request_empty_allowed_extensions_passes_when_upload_disabled(): void + { + $request = $this->makeUpdateRequest(); + + $validator = Validator::make( + ['use_file_upload' => false, 'allowed_extensions' => []], + $request->rules() + ); + + $this->assertTrue($validator->passes(), 'use_file_upload=false 이면 빈 확장자도 통과해야 함'); + } + + /** + * UpdateBoardRequest - 토글 미전송 + 확장자 전송 시 기존 게시판 값을 따른다 (케이스 5) + * + * exclude_if 는 조건 필드가 데이터에 없으면 배제하지 않는다. 부분 수정 요청이 + * use_file_upload 없이 allowed_extensions 만 보내면 첨부 미사용 게시판인데도 + * 확장자 필수 규칙이 발화하므로, prepareForValidation() 이 저장값을 주입해야 한다. + */ + public function test_update_request_omitted_toggle_follows_stored_board_value(): void + { + // 저장된 게시판이 첨부 미사용 → 빈 확장자 통과 + $disabled = $this->makeUpdateRequest(boardUseFileUpload: false); + $disabledInput = $this->prepareUpdateInput($disabled, ['allowed_extensions' => []]); + + $this->assertFalse( + $disabledInput['use_file_upload'], + '토글 미전송 시 기존 게시판의 use_file_upload 가 주입되어야 함' + ); + $this->assertTrue( + Validator::make($disabledInput, $disabled->rules())->passes(), + '첨부 미사용 게시판은 토글 미전송 + 빈 확장자도 통과해야 함' + ); + + // 저장된 게시판이 첨부 사용 → 빈 확장자 차단 + $enabled = $this->makeUpdateRequest(boardUseFileUpload: true); + $enabledInput = $this->prepareUpdateInput($enabled, ['allowed_extensions' => []]); + + $this->assertTrue($enabledInput['use_file_upload']); + $this->assertFalse( + Validator::make($enabledInput, $enabled->rules())->passes(), + '첨부 사용 게시판은 토글 미전송이어도 빈 확장자를 차단해야 함' + ); + } + + /** + * UpdateBoardRequest - 토글·확장자 모두 미전송이면 주입하지 않는다 (케이스 4 인접) + * + * 확장자를 건드리지 않는 부분 수정에 조건 필드를 주입하면, 보내지도 않은 토글이 + * 검증 대상으로 올라온다. + */ + public function test_update_request_does_not_inject_toggle_when_extensions_omitted(): void + { + $request = $this->makeUpdateRequest(boardUseFileUpload: false); + + $input = $this->prepareUpdateInput($request, ['per_page' => 30]); + + $this->assertArrayNotHasKey('use_file_upload', $input); + $this->assertTrue(Validator::make($input, $request->rules())->passes()); + } + /** * UpdateBoardRequest - allowed_extensions 미전송(부분 수정)은 통과해야 함 (회귀 방지) * @@ -1250,8 +1344,6 @@ class BoardRequestTest extends ModuleTestCase * * 0 = NEW 배지 표시 안 함 (Post::isNew() 가 0 을 정상 처리). * - * @dataProvider newDisplayHoursProvider - * * @scenario case=new_display_hours_zero * * @effects new_display_hours_zero_passes, new_display_hours_out_of_range_fails @@ -1259,6 +1351,7 @@ class BoardRequestTest extends ModuleTestCase * @param mixed $value new_display_hours 값 * @param bool $shouldPass 통과 기대 여부 */ + #[DataProvider('newDisplayHoursProvider')] public function test_update_matrix_new_display_hours_boundaries(mixed $value, bool $shouldPass): void { $request = $this->makeUpdateRequest(); @@ -1408,14 +1501,13 @@ class BoardRequestTest extends ModuleTestCase * config('sirsoft-board.limits') 는 min_title_length_min / min_comment_length_min 을 * 0 으로 선언하고 있으므로 하드코딩된 min:1 은 계약 위반이다. * - * @dataProvider settingsZeroLowerBoundProvider - * * @scenario case=zero_lower_bound_length_fields * * @effects zero_lower_bound_length_fields_pass * * @param string $field basic_defaults 하위 필드명 */ + #[DataProvider('settingsZeroLowerBoundProvider')] public function test_settings_matrix_zero_lower_bounds_pass(string $field): void { $request = new StoreBoardSettingsRequest; diff --git a/modules/_bundled/sirsoft-board/tests/Unit/Repositories/BoardRepositorySitemapTest.php b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/BoardRepositorySitemapTest.php index 5e31b53f..609ea855 100644 --- a/modules/_bundled/sirsoft-board/tests/Unit/Repositories/BoardRepositorySitemapTest.php +++ b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/BoardRepositorySitemapTest.php @@ -8,6 +8,7 @@ use Illuminate\Support\LazyCollection; use Modules\Sirsoft\Board\Models\Board; use Modules\Sirsoft\Board\Repositories\Contracts\BoardRepositoryInterface; use Modules\Sirsoft\Board\Tests\BoardTestCase; +use PHPUnit\Framework\Attributes\Group; /** * BoardRepository::streamActiveForSitemap 단위 테스트 @@ -16,11 +17,10 @@ use Modules\Sirsoft\Board\Tests\BoardTestCase; * - 순회자(LazyCollection) 를 반환한다 (전체 적재 금지) * - sitemap 에 필요한 컬럼만 적재한다 (전컬럼 적재 회귀 차단) * - 활성 게시판만 반환한다 - * - * @group board - * @group unit - * @group seo */ +#[Group('board')] +#[Group('unit')] +#[Group('seo')] class BoardRepositorySitemapTest extends BoardTestCase { private BoardRepositoryInterface $repository; diff --git a/modules/_bundled/sirsoft-board/tests/Unit/Repositories/CommentListDeferredJoinTest.php b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/CommentListDeferredJoinTest.php new file mode 100644 index 00000000..1bbcc800 --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/CommentListDeferredJoinTest.php @@ -0,0 +1,196 @@ +repository = app(CommentRepository::class); + } + + /** 쿼리 로그 수집을 시작합니다. */ + private function startCapture(): void + { + $this->queries = []; + + DB::listen(function ($query) { + $this->queries[] = $query->sql; + }); + } + + /** + * 수집된 쿼리 중 조건에 맞는 것만 돌려줍니다. + * + * @param string $needle 포함 문자열 + * @return array 일치 쿼리 목록 + */ + private function queriesContaining(string $needle): array + { + return array_values(array_filter($this->queries, fn (string $sql) => str_contains($sql, $needle))); + } + + /** + * 이 사용자의 댓글을 생성합니다. + * + * @param int $postId 대상 게시글 ID + * @param array $attributes 추가 속성 + * @return int 생성된 댓글 ID + */ + private function createUserComment(int $postId, array $attributes = []): int + { + return $this->createTestComment($postId, array_merge(['user_id' => $this->userId], $attributes)); + } + + public function test_soft_deleted_comments_are_excluded(): void + { + $postId = $this->createTestPost(); + $this->createUserComment($postId, ['content' => '살아있는 댓글']); + $this->createUserComment($postId, ['content' => '삭제된 댓글', 'deleted_at' => now()]); + + $result = $this->repository->getUserComments($this->userId, [], 10); + $contents = collect($result->items())->pluck('content')->all(); + + $this->assertContains('살아있는 댓글', $contents); + $this->assertNotContains('삭제된 댓글', $contents); + $this->assertSame(1, $result->total()); + } + + public function test_comments_of_soft_deleted_posts_are_excluded(): void + { + $livePostId = $this->createTestPost(['title' => '살아있는 글']); + $deletedPostId = $this->createTestPost(['title' => '삭제된 글', 'deleted_at' => now()]); + + $this->createUserComment($livePostId, ['content' => '보여야 하는 댓글']); + $this->createUserComment($deletedPostId, ['content' => '숨겨져야 하는 댓글']); + + $result = $this->repository->getUserComments($this->userId, [], 10); + $contents = collect($result->items())->pluck('content')->all(); + + $this->assertSame(['보여야 하는 댓글'], $contents); + $this->assertSame(1, $result->total(), '삭제된 글의 댓글은 총 건수에서도 빠져야 한다'); + } + + public function test_other_users_comments_are_not_included(): void + { + $postId = $this->createTestPost(); + $this->createUserComment($postId, ['content' => '내 댓글']); + $this->createTestComment($postId, ['user_id' => $this->userId + 1, 'content' => '남의 댓글']); + + $result = $this->repository->getUserComments($this->userId, [], 10); + + $this->assertSame(['내 댓글'], collect($result->items())->pluck('content')->all()); + } + + public function test_pages_do_not_overlap_and_last_page_is_reported(): void + { + $postId = $this->createTestPost(); + + for ($i = 1; $i <= 15; $i++) { + $this->createUserComment($postId, [ + 'content' => "댓글 {$i}", + 'created_at' => now()->subMinutes(20 - $i), + ]); + } + + // 저장소는 페이지 번호를 인자로 받지 않고 Paginator 가 요청에서 해석한다. + // 단위 테스트에는 요청 컨텍스트가 없으므로 리졸버로 현재 페이지를 지정한다. + Paginator::currentPageResolver(fn () => 1); + $page1 = $this->repository->getUserComments($this->userId, [], 10); + + Paginator::currentPageResolver(fn () => 2); + $page2 = $this->repository->getUserComments($this->userId, [], 10); + + Paginator::currentPageResolver(fn () => 1); + + $this->assertSame(15, $page1->total()); + $this->assertSame(2, $page1->lastPage()); + $this->assertCount(10, $page1->items()); + $this->assertCount(5, $page2->items(), '마지막 페이지는 남은 5건만 있어야 한다'); + + $firstIds = collect($page1->items())->pluck('id')->all(); + $secondIds = collect($page2->items())->pluck('id')->all(); + + $this->assertEmpty(array_intersect($firstIds, $secondIds), '페이지 간 중복 행이 없어야 한다'); + $this->assertCount(15, array_unique(array_merge($firstIds, $secondIds)), '누락된 행이 없어야 한다'); + } + + public function test_search_filter_counts_only_matching_rows(): void + { + $postId = $this->createTestPost(); + $this->createUserComment($postId, ['content' => '검색어 포함 댓글 A']); + $this->createUserComment($postId, ['content' => '검색어 포함 댓글 B']); + $this->createUserComment($postId, ['content' => '무관한 댓글']); + + $result = $this->repository->getUserComments($this->userId, ['search' => '검색어'], 10); + + $this->assertSame(2, $result->total(), '검색 조건이 총 건수에 반영되어야 한다'); + $this->assertCount(2, $result->items()); + } + + public function test_cached_total_skips_the_count_query(): void + { + $postId = $this->createTestPost(); + $this->createUserComment($postId, ['content' => '댓글']); + + $this->startCapture(); + $result = $this->repository->getUserComments($this->userId, ['cached_total' => 999], 10); + + $this->assertEmpty($this->queriesContaining('count(*)'), '캐시된 total 이 있으면 COUNT 를 실행하지 않아야 한다'); + $this->assertSame(999, $result->total()); + } + + public function test_offset_scan_does_not_read_comment_content(): void + { + $postId = $this->createTestPost(); + + for ($i = 1; $i <= 25; $i++) { + $this->createUserComment($postId, ['content' => "댓글 본문 {$i}"]); + } + + $this->startCapture(); + $this->repository->getUserComments($this->userId, [], 10); + + $offsetQueries = $this->queriesContaining('offset'); + + $this->assertNotEmpty($offsetQueries, 'inner 는 OFFSET 으로 ID 집합을 구해야 한다'); + + foreach ($offsetQueries as $sql) { + $this->assertStringNotContainsString( + 'content`', + $sql, + 'OFFSET 스캔 구간에서 댓글 본문을 읽으면 안 된다' + ); + } + } +} diff --git a/modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php new file mode 100644 index 00000000..6800e262 --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php @@ -0,0 +1,196 @@ +repository = app(PostRepository::class); + } + + /** 쿼리 로그 수집을 시작합니다. */ + private function startCapture(): void + { + $this->queries = []; + + DB::listen(function ($query) { + $this->queries[] = $query->sql; + }); + } + + /** + * 수집된 쿼리 중 조건에 맞는 것만 돌려줍니다. + * + * @param string $needle 포함 문자열 + * @return array 일치 쿼리 목록 + */ + private function queriesContaining(string $needle): array + { + return array_values(array_filter($this->queries, fn (string $sql) => str_contains($sql, $needle))); + } + + public function test_offset_scan_does_not_read_content_preview(): void + { + for ($i = 1; $i <= 25; $i++) { + $this->createTestPost(['title' => "게시글 {$i}"]); + } + + $this->startCapture(); + $this->repository->paginate($this->board->slug, ['page' => 2], 10); + + $offsetQueries = $this->queriesContaining('offset'); + + $this->assertNotEmpty($offsetQueries, '페이지 조회에는 OFFSET 쿼리가 있어야 한다'); + + foreach ($offsetQueries as $sql) { + // 본문 앞부분을 잘라 읽는 것도 오버플로 페이지를 그대로 읽으므로 프루닝이 아니다. + // 잘라 읽기는 이번 페이지 분량(outer)에서만 일어나야 한다. + $this->assertStringNotContainsString('SUBSTRING', $sql); + $this->assertStringNotContainsString('content', $sql); + } + } + + public function test_page_one_shows_notices_and_page_two_does_not(): void + { + $this->createTestPost(['title' => '공지사항', 'is_notice' => true]); + + for ($i = 1; $i <= 25; $i++) { + $this->createTestPost(['title' => "일반글 {$i}"]); + } + + $page1 = $this->repository->paginate($this->board->slug, ['page' => 1], 10); + $page2 = $this->repository->paginate($this->board->slug, ['page' => 2], 10); + + $titles1 = collect($page1->items())->pluck('title')->all(); + $titles2 = collect($page2->items())->pluck('title')->all(); + + $this->assertSame('공지사항', $titles1[0], '공지는 1페이지 선두에 있어야 한다'); + $this->assertNotContains('공지사항', $titles2, '공지는 2페이지에 반복되지 않아야 한다'); + } + + public function test_replies_follow_their_parent_in_the_same_page(): void + { + $parentId = $this->createTestPost(['title' => '원글']); + $this->createTestPost(['title' => '답글 1', 'parent_id' => $parentId, 'depth' => 1]); + $this->createTestPost(['title' => '답글 2', 'parent_id' => $parentId, 'depth' => 1]); + + $result = $this->repository->paginate($this->board->slug, [], 10); + $titles = collect($result->items())->pluck('title')->all(); + + $this->assertSame(['원글', '답글 1', '답글 2'], $titles); + } + + public function test_reply_content_preview_is_available_like_parent(): void + { + $parentId = $this->createTestPost(['title' => '원글', 'content' => '원글 본문입니다.']); + $this->createTestPost([ + 'title' => '답글', + 'content' => '답글 본문입니다.', + 'parent_id' => $parentId, + 'depth' => 1, + ]); + + $result = $this->repository->paginate($this->board->slug, [], 10); + $items = collect($result->items()); + + // 원글과 답글은 같은 컬럼 목록으로 조회되므로 스키마가 어긋나면 안 된다 + // (한쪽만 바뀌면 화면에서 조용히 빈 값이 된다) + foreach ($items as $item) { + $this->assertNotNull($item->content_preview_raw ?? null); + } + } + + public function test_pages_do_not_overlap(): void + { + for ($i = 1; $i <= 25; $i++) { + $this->createTestPost(['title' => "게시글 {$i}"]); + } + + $page1 = $this->repository->paginate($this->board->slug, ['page' => 1], 10); + $page2 = $this->repository->paginate($this->board->slug, ['page' => 2], 10); + + $ids1 = collect($page1->items())->pluck('id')->all(); + $ids2 = collect($page2->items())->pluck('id')->all(); + + $this->assertCount(10, $ids1); + $this->assertEmpty(array_intersect($ids1, $ids2)); + } + + public function test_last_page_reports_no_more_pages(): void + { + for ($i = 1; $i <= 15; $i++) { + $this->createTestPost(['title' => "게시글 {$i}"]); + } + + $page1 = $this->repository->paginate($this->board->slug, ['page' => 1], 10); + $page2 = $this->repository->paginate($this->board->slug, ['page' => 2], 10); + + $this->assertTrue($page1->hasMorePages(), '1페이지에서는 다음 페이지가 있어야 한다'); + $this->assertFalse($page2->hasMorePages(), '마지막 페이지에서 다음 버튼이 남으면 안 된다'); + } + + public function test_soft_deleted_posts_are_excluded_by_default(): void + { + $this->createTestPost(['title' => '살아있는 글']); + $this->createTestPost(['title' => '삭제된 글', 'deleted_at' => now()]); + + $result = $this->repository->paginate($this->board->slug, [], 10); + $titles = collect($result->items())->pluck('title')->all(); + + $this->assertContains('살아있는 글', $titles); + $this->assertNotContains('삭제된 글', $titles); + } + + public function test_with_trashed_includes_deleted_posts(): void + { + $this->createTestPost(['title' => '살아있는 글']); + $this->createTestPost(['title' => '삭제된 글', 'deleted_at' => now()]); + + $result = $this->repository->paginate($this->board->slug, [], 10, true); + $titles = collect($result->items())->pluck('title')->all(); + + $this->assertContains('삭제된 글', $titles); + } + + public function test_sort_order_is_applied_and_deterministic(): void + { + // 조회수가 모두 같은 상태 — 2차 정렬(id)이 없으면 페이지 경계가 흔들린다 + for ($i = 1; $i <= 12; $i++) { + $this->createTestPost(['title' => "게시글 {$i}", 'view_count' => 5]); + } + + $result = $this->repository->paginate( + $this->board->slug, + ['order_by' => 'view_count', 'order_direction' => 'desc'], + 5 + ); + + $ids = collect($result->items())->pluck('id')->all(); + $sorted = $ids; + rsort($sorted); + + $this->assertSame($sorted, $ids, '동률 정렬에서도 id 내림차순이 유지되어야 한다'); + } +} diff --git a/modules/_bundled/sirsoft-board/tests/Unit/Repositories/UserCommentedActivityDeferredJoinTest.php b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/UserCommentedActivityDeferredJoinTest.php new file mode 100644 index 00000000..336838ae --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/UserCommentedActivityDeferredJoinTest.php @@ -0,0 +1,167 @@ +app->make(PostRepositoryInterface::class); + } + + /** 쿼리 로그 수집을 시작합니다. */ + private function startCapture(): void + { + $this->queries = []; + + DB::listen(function ($query) { + $this->queries[] = $query->sql; + }); + } + + /** + * 수집된 쿼리 중 조건에 맞는 것만 돌려줍니다. + * + * @param string $needle 포함 문자열 + * @return array 일치 쿼리 목록 + */ + private function queriesContaining(string $needle): array + { + return array_values(array_filter($this->queries, fn (string $sql) => str_contains($sql, $needle))); + } + + public function test_offset_scan_excludes_latest_comment_subquery(): void + { + $user = $this->createUser(); + $other = $this->createUser(); + + for ($i = 1; $i <= 25; $i++) { + $postId = $this->createTestPost(['user_id' => $other->id, 'title' => "글 {$i}"]); + $this->createTestComment($postId, ['user_id' => $user->id, 'content' => "댓글 {$i}"]); + } + + $this->startCapture(); + $this->repository()->getUserActivities($user->id, ['activity_type' => 'commented'], 10); + + $offsetQueries = $this->queriesContaining('offset'); + + $this->assertNotEmpty($offsetQueries, 'inner 는 OFFSET 으로 ID 집합을 구해야 한다'); + + foreach ($offsetQueries as $sql) { + $this->assertStringNotContainsString( + 'as `lc`', + $sql, + 'OFFSET 스캔 구간에 최근 댓글 서브쿼리가 남으면 건너뛸 행마다 상관 서브쿼리가 실행된다' + ); + $this->assertStringNotContainsString( + 'substring', + strtolower($sql), + 'OFFSET 스캔 구간에서 본문을 읽으면 안 된다' + ); + } + } + + public function test_latest_comment_columns_are_available_in_result(): void + { + $user = $this->createUser(); + $other = $this->createUser(); + + $postId = $this->createTestPost(['user_id' => $other->id, 'title' => '대상 글']); + $this->createTestComment($postId, [ + 'user_id' => $user->id, + 'content' => '예전 댓글', + 'created_at' => now()->subDay(), + ]); + $this->createTestComment($postId, [ + 'user_id' => $user->id, + 'content' => '가장 최근 댓글', + 'created_at' => now(), + ]); + + $items = $this->repository() + ->getUserActivities($user->id, ['activity_type' => 'commented'], 10) + ->items(); + + $this->assertCount(1, $items, '같은 글에 댓글을 여러 번 달아도 목록에는 글 1건으로 접혀야 한다'); + $this->assertSame(2, $items[0]['activity_count'], '해당 글에 단 댓글 수가 집계되어야 한다'); + } + + public function test_total_counts_posts_not_comments_when_filter_bypasses_cache(): void + { + $user = $this->createUser(); + $other = $this->createUser(); + + // 첫 글에만 댓글 3건 — 그룹 수(3)와 첫 그룹의 행 수(3)가 우연히 같아지지 않도록 + // 나머지 글은 댓글 1건씩 둔다. + $firstPost = $this->createTestPost(['user_id' => $other->id, 'title' => '검색어 글 A']); + for ($i = 0; $i < 3; $i++) { + $this->createTestComment($firstPost, ['user_id' => $user->id, 'content' => '검색어 댓글']); + } + + foreach (['검색어 글 B', '검색어 글 C', '검색어 글 D', '검색어 글 E'] as $title) { + $postId = $this->createTestPost(['user_id' => $other->id, 'title' => $title]); + $this->createTestComment($postId, ['user_id' => $user->id, 'content' => '검색어 댓글']); + } + + // 검색 필터가 걸리면 서비스가 캐시 total 을 담지 않으므로 trait 이 직접 COUNT 한다. + $paginator = $this->repository()->getUserActivities( + $user->id, + ['activity_type' => 'commented', 'search' => '검색어'], + 10 + ); + + $this->assertSame(5, $paginator->total(), '총 건수는 댓글 수가 아니라 글(그룹) 수여야 한다'); + $this->assertCount(5, $paginator->items()); + } + + public function test_pages_do_not_overlap(): void + { + $user = $this->createUser(); + $other = $this->createUser(); + + for ($i = 1; $i <= 15; $i++) { + $postId = $this->createTestPost([ + 'user_id' => $other->id, + 'title' => "글 {$i}", + 'created_at' => now()->subMinutes(20 - $i), + ]); + $this->createTestComment($postId, ['user_id' => $user->id, 'content' => "댓글 {$i}"]); + } + + $paginator = $this->repository()->getUserActivities($user->id, ['activity_type' => 'commented'], 10); + + $this->assertSame(15, $paginator->total()); + $this->assertSame(2, $paginator->lastPage()); + $this->assertCount(10, $paginator->items()); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md index 7831a85f..00a88499 100644 --- a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md +++ b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md @@ -26,6 +26,9 @@ - 실제로 사용되지 않던 설정 항목(상품·주문 목록의 페이지당 개수, 결제 자동취소 기한 중복 항목)을 정리했습니다. 미입금 자동취소 기한은 관리자 환경설정의 값 하나만 사용합니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) - 사이트맵을 만들 때 상품과 카테고리를 한 번에 모두 메모리에 올리지 않고 나눠서 읽도록 바꿨습니다. 상품이 많은 쇼핑몰에서 사이트맵 생성 중 메모리가 부족해 실패하던 문제가 줄어듭니다. +- 상품 리뷰·마일리지 내역·쿠폰 발급 내역 목록도 같은 방식으로 뒤쪽 페이지 조회를 개선했습니다. 상품 리뷰는 작성일 정렬용 인덱스를 추가하고 기간 검색 조건을 인덱스가 활용되는 형태로 바꿔, 리뷰가 많은 쇼핑몰에서 목록과 기간 검색이 함께 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 상품 목록(관리자·쇼핑몰 화면)·상품 문의 관리 목록·추가배송비 목록도 같은 방식으로 개선했습니다. 상품 문의는 기간 검색 조건도 인덱스가 활용되는 형태로 바꿔 기간을 좁혀 찾을 때 더 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 주문 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 주문의 스냅샷·관리자 메모 같은 큰 항목까지 함께 읽어 느려졌지만, 이제 현재 페이지의 주문만 상세 정보를 읽습니다. 주문 10만 건 기준 5,000번째 페이지에서 조회 시간이 약 6분의 1로 줄어듭니다. 화면에 보이는 내용은 이전과 동일합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 개선된 사이트맵 생성 기능을 사용하기 위해 이 모듈은 이제 코어 7.0.6 이상이 필요합니다. ### Fixed @@ -67,6 +70,10 @@ - 관리자 목록에서 검색·필터·정렬을 바꿀 때 표가 다시 그려지는 동안 로딩 표시가 나오지 않던 문제를 수정했습니다. 응답이 늦으면 아무 반응이 없는 것처럼 보여 같은 조작을 반복하게 되던 상황이 사라집니다. - 관리자 화면 일부 아이콘이 의도한 크기보다 크거나 작게 보이던 문제를 수정했습니다. - 관리자 화면 일부 버튼·배지가 다크 모드에서 밝은 색 그대로 표시되던 문제를 수정했습니다. +- 쿠폰을 수정할 때 유효기간을 비운 채로 저장할 수 있던 문제를 수정했습니다. 등록 화면과 동일하게, "기간 지정" 쿠폰은 시작일·종료일이, "발급일 기준" 쿠폰은 유효일수가 있어야 저장됩니다. (#78 @glitter-gim 님께서 제보해주셨습니다.) +- 쿠폰의 다른 항목만 고치는 경우에도 "1인당 사용 제한"을 함께 보내지 않으면 저장이 거절되던 문제를 수정했습니다. (#78 @glitter-gim 님께서 제보해주셨습니다.) +- 배송지를 수정할 때 주소를 통째로 비운 채 저장할 수 있던 문제를 수정했습니다. 국내 배송지는 우편번호와 주소가, 해외 배송지는 주소·도시·우편번호가 갖춰져야 저장됩니다. 기본 배송지 지정처럼 주소를 건드리지 않는 수정은 이전과 동일하게 저장됩니다. (#78 @glitter-gim 님께서 제보해주셨습니다.) +- 해외 배송지를 수정하면 도시·주·우편번호가 안내 없이 비워지던 문제를 수정했습니다. 이번 수정에서 다시 입력하지 않은 항목은 저장된 값이 그대로 유지됩니다. (#78 @glitter-gim 님께서 제보해주셨습니다.) ## [1.0.4] - 2026-07-16 diff --git a/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_07_30_000001_add_created_at_indexes_to_ecommerce_product_reviews_table.php b/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_07_30_000001_add_created_at_indexes_to_ecommerce_product_reviews_table.php new file mode 100644 index 00000000..a460f62d --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_07_30_000001_add_created_at_indexes_to_ecommerce_product_reviews_table.php @@ -0,0 +1,72 @@ +index( + ['product_id', 'status', 'deleted_at', 'created_at'], + 'idx_reviews_product_status_deleted_created' + ); + } + + if (! in_array('idx_reviews_deleted_created', $existingIndexes)) { + $table->index(['deleted_at', 'created_at'], 'idx_reviews_deleted_created'); + } + }); + } + + public function down(): void + { + if (! Schema::hasTable('ecommerce_product_reviews')) { + return; + } + + $existingIndexes = array_column(Schema::getIndexes('ecommerce_product_reviews'), 'name'); + + Schema::table('ecommerce_product_reviews', function (Blueprint $table) use ($existingIndexes) { + $indexes = [ + 'idx_reviews_product_status_deleted_created', + 'idx_reviews_deleted_created', + ]; + + foreach ($indexes as $index) { + if (in_array($index, $existingIndexes)) { + $table->dropIndex($index); + } + } + }); + } +}; diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/addresses.md b/modules/_bundled/sirsoft-ecommerce/docs/api/addresses.md index dd125472..ecee15fa 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/addresses.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/addresses.md @@ -402,6 +402,8 @@ HTTP/1.1 200 | intl_postal_code | body | string | 아니오 | max 20 | 우편번호 (국제 주소) | | is_default | body | boolean | 아니오 | — | 기본값 지정 여부 | +개별 필드는 모두 선택이지만 주소 필수 조합은 "기존 저장값 + 이번 요청" 결과 상태 기준으로 검증됩니다. 국내(`country_code=KR` 또는 미전송 시 기존 국가가 KR)는 `zipcode` + `address`, 해외는 `address_line_1` + `intl_city` + `intl_postal_code` 가 결과적으로 모두 채워져 있어야 하며, 그렇지 않으면 422 로 거절됩니다. 요청에 없는 필드는 저장된 값으로 판정하므로 `is_default` 만 바꾸는 요청이나 도로명 주소만 교체하는 요청은 그대로 통과합니다. 해외 주소의 저장 컬럼은 `city`/`state`/`postal_code` 이므로 요청에 `intl_city`/`intl_postal_code` 가 없으면 그 저장값이 판정 기준이 됩니다. + > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.user_address.update_validation_rules`). **요청 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md b/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md index a5292864..4b9acd42 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md @@ -47,7 +47,7 @@ | user_id | query | integer | 아니오 | — | user 식별자 | | orderer_uuid | query | uuid | 아니오 | — | 특정 회원의 주문만 조회하는 주문자 UUID 필터 (회원 검색 연동용) | | member_type | query | string | 아니오 | `member`, `guest` | 회원 구분 필터 (member 회원 주문 / guest 비회원 주문) | -| sort_by | query | string | 아니오 | `ordered_at`, `paid_at`, `total_amount` | 정렬 기준 필드명 | +| sort_by | query | string | 아니오 | `ordered_at`, `paid_at`, `total_amount` | 정렬 기준 필드명 (그 외 값은 422) | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | | per_page | query | integer | 아니오 | min 10, max 100 | 페이지당 항목 수 | | page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md b/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md index fb590202..cb996aea 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md @@ -701,6 +701,8 @@ HTTP/1.1 200 | products | body | array | 아니오 | — | 적용 상품 목록 (`target_scope=products`), 항목별 `{id, type: include\|exclude}` | | categories | body | array | 아니오 | — | 적용 카테고리 목록 (`target_scope=categories`), 항목별 `{id, type: include\|exclude}` | +> 필수 표기는 "전송했을 때 값이 있어야 함"을 뜻합니다. 이 엔드포인트는 부분 수정을 지원하므로 body 필드는 모두 생략할 수 있습니다 (생략하면 기존 값 유지). 유효기간은 `valid_type` 을 함께 전송할 때만 조건부 필수가 적용됩니다 — `period` 면 `valid_from`/`valid_to`, `days_from_issue` 면 `valid_days` 가 비어 있으면 422 입니다. `valid_type` 을 보내지 않는 부분 수정은 유효기간 필드를 요구하지 않습니다. + > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`sirsoft-ecommerce.coupon.update_validation_rules`). **요청 예시** diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductController.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductController.php index 644e97c0..1cfa5a83 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductController.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductController.php @@ -18,8 +18,8 @@ use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\BulkUpdateStatusRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\BulkUpdateStockRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ProductListRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ProductLogsRequest; +use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ProductShowForCopyRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ReorderProductImagesRequest; -use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ShowProductForCopyRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\StoreProductRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\UpdateProductRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\UploadProductImageRequest; @@ -566,11 +566,11 @@ class ProductController extends AdminBaseController /** * 상품 복사용 데이터를 조회합니다. * - * @param ShowProductForCopyRequest $request 복사 옵션 요청 데이터 + * @param ProductShowForCopyRequest $request 검증된 복사 옵션 요청 * @param Product $product 복사할 상품 모델 * @return JsonResponse 복사용 데이터 JSON 응답 */ - public function showForCopy(ShowProductForCopyRequest $request, Product $product): JsonResponse + public function showForCopy(ProductShowForCopyRequest $request, Product $product): JsonResponse { try { $copyData = $this->productService->getDetailForCopy($product->id, $request->getCopyOptions()); @@ -797,14 +797,14 @@ class ProductController extends AdminBaseController /** * 상품 처리로그(활동 로그) 목록을 조회합니다. * - * @param ProductLogsRequest $request 활동 로그 조회 요청 데이터 + * @param ProductLogsRequest $request 검증된 조회 요청 * @param Product $product 상품 * @return JsonResponse 활동 로그 목록 */ public function logs(ProductLogsRequest $request, Product $product): JsonResponse { try { - // 상품 + 상품옵션 로그 합산 조회는 Repository 위임 (Service 경유) + // 상품 + 상품옵션 로그 합산 조회는 Service → Repository 경유 (컨트롤러 직접 조회 금지) $logs = $this->productService->getActivityLogs($product, $request->getFilters()); return ResponseHelper::moduleSuccess( diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductLogsRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductLogsRequest.php index d643e0ba..191c8063 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductLogsRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductLogsRequest.php @@ -2,11 +2,12 @@ namespace Modules\Sirsoft\Ecommerce\Http\Requests\Admin; +use App\Extension\HookManager; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; /** - * 상품 활동 로그 조회 요청 (관리자) + * 상품 처리로그(활동 로그) 목록 조회 요청 (관리자) * * 권한은 라우트 미들웨어(permission:admin,sirsoft-ecommerce.products.read)에서 처리하며, * 여기서는 페이지 크기·정렬 옵션만 검증합니다. @@ -30,22 +31,27 @@ class ProductLogsRequest extends FormRequest */ public function rules(): array { - return [ + $rules = [ 'per_page' => ['sometimes', 'integer', 'min:1', 'max:100'], 'sort_order' => ['sometimes', 'string', Rule::in(['asc', 'desc'])], ]; + + return HookManager::applyFilters('sirsoft-ecommerce.product.logs_validation_rules', $rules, $this); } /** * 활동 로그 조회 필터(페이지 크기·정렬)를 반환합니다. * - * @return array{per_page: int, sort_order: string} 조회 필터 + * 고정 키만 열거하면 확장이 훅으로 규칙을 얹어도 그 값이 Service 까지 도달하지 못하므로, + * 검증을 통과한 전체 데이터 위에 기본값이 적용된 고정 키를 덮어쓴다. + * + * @return array 조회 필터 (per_page, sort_order + 확장 추가 필드) */ public function getFilters(): array { - return [ + return array_merge($this->validated(), [ 'per_page' => (int) ($this->query('per_page', 10)), 'sort_order' => $this->query('sort_order', 'desc'), - ]; + ]); } } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ShowProductForCopyRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductShowForCopyRequest.php similarity index 71% rename from modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ShowProductForCopyRequest.php rename to modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductShowForCopyRequest.php index f34c2189..aee8696f 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ShowProductForCopyRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductShowForCopyRequest.php @@ -2,6 +2,7 @@ namespace Modules\Sirsoft\Ecommerce\Http\Requests\Admin; +use App\Extension\HookManager; use Illuminate\Foundation\Http\FormRequest; /** @@ -10,7 +11,7 @@ use Illuminate\Foundation\Http\FormRequest; * 권한은 라우트 미들웨어(permission:admin,sirsoft-ecommerce.products.create)에서 처리하며, * 여기서는 어떤 항목을 함께 복사할지 고르는 플래그만 검증합니다. */ -class ShowProductForCopyRequest extends FormRequest +class ProductShowForCopyRequest extends FormRequest { /** * 복사 대상 항목과 미지정 시 기본값 @@ -54,22 +55,29 @@ class ShowProductForCopyRequest extends FormRequest $rules["copy_{$target}"] = ['sometimes', 'boolean']; } - return $rules; + return HookManager::applyFilters('sirsoft-ecommerce.product.show_for_copy_validation_rules', $rules, $this); } /** * 복사 옵션을 반환합니다. * - * @return array 항목별 복사 여부 + * 코어 항목은 `copy_` 접두사를 뗀 키로, 확장이 훅으로 추가한 필드는 검증된 이름 그대로 + * 함께 담는다 — 고정 키만 열거하면 확장이 규칙을 얹어도 값이 Service 에 도달하지 못한다. + * + * @return array 항목별 복사 여부 + 확장 추가 필드 */ public function getCopyOptions(): array { + $coreKeys = []; $options = []; foreach (self::COPY_TARGETS as $target => $default) { + $coreKeys[] = "copy_{$target}"; $options[$target] = $this->boolean("copy_{$target}", $default); } - return $options; + $extra = array_diff_key($this->validated(), array_flip($coreKeys)); + + return array_merge($extra, $options); } } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/UpdateCouponRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/UpdateCouponRequest.php index cfb28897..001aca77 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/UpdateCouponRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/UpdateCouponRequest.php @@ -98,13 +98,17 @@ class UpdateCouponRequest extends FormRequest 'issue_condition' => 'sometimes|required|string|in:'.implode(',', CouponIssueCondition::values()), 'issue_status' => 'sometimes|required|string|in:'.implode(',', CouponIssueStatus::values()), 'total_quantity' => 'nullable|integer|min:1', - 'per_user_limit' => 'required|integer|min:0', + // 같은 파일의 다른 필드와 동일하게 부분 수정을 지원한다. sometimes 가 없으면 + // 다른 탭만 고치는 요청도 1인당 사용 제한을 매번 실어 보내야 저장된다. + 'per_user_limit' => 'sometimes|required|integer|min:0', - // 유효기간 + // 유효기간 — 조건부 규칙은 StoreCouponRequest 와 동일해야 한다. + // required_if 는 조건 필드(valid_type)가 요청에 없으면 발화하지 않으므로 + // 부분 수정을 깨지 않으면서 "기간 지정인데 기간이 비어 있는" 저장만 차단한다. 'valid_type' => 'sometimes|required|string|in:period,days_from_issue', - 'valid_days' => 'nullable|integer|min:1', - 'valid_from' => 'nullable|date', - 'valid_to' => 'nullable|date|after_or_equal:valid_from', + 'valid_days' => 'nullable|required_if:valid_type,days_from_issue|integer|min:1', + 'valid_from' => 'nullable|required_if:valid_type,period|date', + 'valid_to' => 'nullable|required_if:valid_type,period|date|after_or_equal:valid_from', // 발급기간 'issue_from' => 'nullable|date', @@ -195,6 +199,9 @@ class UpdateCouponRequest extends FormRequest ? __('sirsoft-ecommerce::validation.coupon.discount_value_rate_min') : __('sirsoft-ecommerce::validation.coupon.discount_value_fixed_min'), 'discount_value.max' => __('sirsoft-ecommerce::validation.coupon.discount_value_rate_max'), + 'valid_days.required_if' => __('sirsoft-ecommerce::validation.coupon.valid_days_required'), + 'valid_from.required_if' => __('sirsoft-ecommerce::validation.coupon.valid_from_required'), + 'valid_to.required_if' => __('sirsoft-ecommerce::validation.coupon.valid_to_required'), 'valid_to.after_or_equal' => __('sirsoft-ecommerce::validation.coupon.valid_to_after_from'), ]; } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Concerns/MapsAddressBookFields.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Concerns/MapsAddressBookFields.php index 80d2c6c0..19db690b 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Concerns/MapsAddressBookFields.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Concerns/MapsAddressBookFields.php @@ -12,6 +12,10 @@ namespace Modules\Sirsoft\Ecommerce\Http\Requests\Concerns; * 본 트레이트는 validated() 결과를 국가별로 정규화한다: * - KR(국내): zipcode/address/address_detail 만 유지, 해외 필드 제거 * - 그 외(해외): intl_* → city/state/postal_code 변환, 국내 필드 제거 + * + * 변환은 요청에 실려 온 intl_* 키에 대해서만 수행한다. 조회 응답은 해외 주소를 + * city/state/postal_code 로 내보내므로 폼이 intl_* 를 다시 싣지 않는 수정 요청이 존재하며, + * 미전송 키를 null 로 채우면 저장된 도시/주/우편번호가 조용히 유실된다. */ trait MapsAddressBookFields { @@ -51,9 +55,15 @@ trait MapsAddressBookFields ); } else { // 해외: intl_* → city/state/postal_code 변환, 국내 전용 필드 제거 - $data['city'] = $data['intl_city'] ?? null; - $data['state'] = $data['intl_state'] ?? null; - $data['postal_code'] = $data['intl_postal_code'] ?? null; + // + // 전송되지 않은 intl_* 는 매핑 대상에서 제외한다. `?? null` 로 채우면 부분 수정 + // 요청(조회 응답이 city/state/postal_code 키로 내보내므로 폼이 intl_* 를 다시 + // 싣지 않는 경우)에서 저장된 도시/주/우편번호가 null 로 덮어써져 조용히 유실된다. + foreach (['city' => 'intl_city', 'state' => 'intl_state', 'postal_code' => 'intl_postal_code'] as $column => $formKey) { + if (array_key_exists($formKey, $data)) { + $data[$column] = $data[$formKey]; + } + } unset( $data['intl_city'], diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UpdateUserAddressRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UpdateUserAddressRequest.php index c8164347..e027a107 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UpdateUserAddressRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UpdateUserAddressRequest.php @@ -4,7 +4,11 @@ namespace Modules\Sirsoft\Ecommerce\Http\Requests\User; use App\Extension\HookManager; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Facades\Auth; +use Illuminate\Validation\Validator; use Modules\Sirsoft\Ecommerce\Http\Requests\Concerns\MapsAddressBookFields; +use Modules\Sirsoft\Ecommerce\Models\UserAddress; +use Modules\Sirsoft\Ecommerce\Repositories\Contracts\UserAddressRepositoryInterface; /** * 사용자 배송지 수정 요청 @@ -39,6 +43,11 @@ class UpdateUserAddressRequest extends FormRequest /** * 요청에 적용할 검증 규칙 * + * 주소 필수 조합(국내 = 우편번호+주소 / 해외 = 주소+도시+우편번호)은 규칙 배열이 + * 아니라 withValidator() 에서 검증한다. StoreUserAddressRequest 의 + * required_with/required_without 를 그대로 옮기면 부분 수정이 깨지기 때문이다 — + * 상세 근거는 validateAddressPresence() docblock 참조. + * * @return array 주소록 수정 검증 규칙 */ public function rules(): array @@ -55,18 +64,26 @@ class UpdateUserAddressRequest extends FormRequest 'country_code' => 'nullable|string|size:2', // 국내 배송 주소 + // audit:allow formrequest-store-update-conditional-symmetry reason: 주소 필수 조합은 + // 페이로드 단독이 아니라 "기존 레코드 + 페이로드" 결과 상태 기준으로 판정한다 + // (validateAddressPresence). Store 의 required_without 를 그대로 옮기면 배송지명만 + // 바꾸는 요청과 해외 배송지 수정 요청이 422 가 된다. 'zipcode' => 'nullable|string|max:10', 'province_code' => 'nullable|string|max:10', 'city' => 'nullable|string|max:100', + // audit:allow formrequest-store-update-conditional-symmetry reason: 결과 상태 기준 판정 (validateAddressPresence) 'address' => 'nullable|string|max:255', 'address_detail' => 'nullable|string|max:255', 'address_type_code' => 'nullable|string|in:R,J', - // 해외 배송 주소 + // 해외 배송 주소 (필수 조합은 위와 동일하게 validateAddressPresence 가 담당) + // audit:allow formrequest-store-update-conditional-symmetry reason: 결과 상태 기준 판정 (validateAddressPresence) 'address_line_1' => 'nullable|string|max:255', 'address_line_2' => 'nullable|string|max:255', + // audit:allow formrequest-store-update-conditional-symmetry reason: 결과 상태 기준 판정 (validateAddressPresence) 'intl_city' => 'nullable|string|max:100', 'intl_state' => 'nullable|string|max:100', + // audit:allow formrequest-store-update-conditional-symmetry reason: 결과 상태 기준 판정 (validateAddressPresence) 'intl_postal_code' => 'nullable|string|max:20', // 기본 배송지 여부 @@ -76,6 +93,129 @@ class UpdateUserAddressRequest extends FormRequest return HookManager::applyFilters('sirsoft-ecommerce.user_address.update_validation_rules', $rules, $this); } + /** + * 검증기에 주소 필수 조합 검증을 추가합니다. + * + * @param Validator $validator 검증기 + */ + public function withValidator(Validator $validator): void + { + $validator->after(function (Validator $validator) { + $this->validateAddressPresence($validator); + }); + } + + /** + * 주소 필수 조합을 "기존 레코드 + 요청 페이로드" 결과 상태 기준으로 검증합니다. + * + * StoreUserAddressRequest 는 required_with/required_without 로 같은 정책을 강제하지만, + * 그 규칙을 수정 요청에 그대로 쓰면 두 가지 정상 경로가 막힌다. + * + * 1) 부분 수정 — 기본 배송지 토글처럼 주소를 건드리지 않는 요청도 required_without 이 + * "상대 필드 부재" 자체를 발화 조건으로 삼아 422 가 된다. + * 2) 해외 배송지 수정 — 조회 응답은 해외 주소를 city/state/postal_code 로 내보내고 + * 폼은 intl_* 로 제출하므로, 사용자가 도시/우편번호를 다시 입력하지 않으면 요청에 + * intl_city/intl_postal_code 가 없다. 페이로드만 보면 값이 비어 보인다. + * + * 따라서 요청에 없는 필드는 저장된 값으로 메워 판정한다. 판정 기준 국가는 + * MapsAddressBookFields 의 매핑 기준과 동일하게 country_code(미전송 시 기존 값)다. + * + * @param Validator $validator 검증기 + */ + protected function validateAddressPresence(Validator $validator): void + { + $payload = $this->all(); + + // 주소를 정의하는 필드도 국가도 건드리지 않는 요청은 판정 대상이 아니다. + $addressKeys = ['zipcode', 'address', 'address_line_1', 'intl_city', 'intl_postal_code', 'country_code']; + + if (! array_intersect($addressKeys, array_keys($payload))) { + return; + } + + $existing = $this->findExistingAddress(); + + // 저장된 컬럼명 → 요청 키 매핑 (해외는 city/state/postal_code 컬럼에 저장된다) + $fallback = [ + 'zipcode' => $existing?->zipcode, + 'address' => $existing?->address, + 'address_line_1' => $existing?->address_line_1, + 'intl_city' => $existing?->city, + 'intl_postal_code' => $existing?->postal_code, + ]; + + $effective = []; + + foreach ($fallback as $key => $storedValue) { + $effective[$key] = array_key_exists($key, $payload) ? $payload[$key] : $storedValue; + } + + $country = array_key_exists('country_code', $payload) + ? strtoupper((string) $payload['country_code']) + : strtoupper((string) ($existing?->country_code ?? 'KR')); + + if ($country === '') { + $country = 'KR'; + } + + if ($country === 'KR') { + if (! $this->isFilled($effective['zipcode'])) { + $validator->errors()->add('zipcode', __('sirsoft-ecommerce::validation.user_address.zipcode_required')); + } + + if (! $this->isFilled($effective['address'])) { + $validator->errors()->add('address', __('sirsoft-ecommerce::validation.user_address.address_required')); + } + + return; + } + + if (! $this->isFilled($effective['address_line_1'])) { + $validator->errors()->add('address_line_1', __('sirsoft-ecommerce::validation.user_address.address_line_1_required')); + } + + if (! $this->isFilled($effective['intl_city'])) { + $validator->errors()->add('intl_city', __('sirsoft-ecommerce::validation.user_address.intl_city_required')); + } + + if (! $this->isFilled($effective['intl_postal_code'])) { + $validator->errors()->add('intl_postal_code', __('sirsoft-ecommerce::validation.user_address.intl_postal_code_required')); + } + } + + /** + * 수정 대상 배송지를 조회합니다. (본인 소유 스코프) + * + * @return UserAddress|null 배송지 (없으면 null — 404 는 컨트롤러가 판정) + */ + protected function findExistingAddress(): ?UserAddress + { + $userId = Auth::id(); + $addressId = (int) $this->route('id'); + + if ($userId === null || $addressId <= 0) { + return null; + } + + // Repository 인터페이스 경유 — FormRequest 의 Model 직접 호출 금지 규정 준수 + return app(UserAddressRepositoryInterface::class)->findByUserIdAndId($userId, $addressId); + } + + /** + * 값이 실제로 채워져 있는지 판정합니다. (공백만 있는 문자열은 빈 값) + * + * @param mixed $value 판정 대상 값 + * @return bool 채워져 있으면 true + */ + private function isFilled(mixed $value): bool + { + if ($value === null) { + return false; + } + + return trim((string) $value) !== ''; + } + /** * 검증 오류 메시지 커스터마이징 * diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/Contracts/ProductRepositoryInterface.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/Contracts/ProductRepositoryInterface.php index 07831061..4eed0e64 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/Contracts/ProductRepositoryInterface.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/Contracts/ProductRepositoryInterface.php @@ -258,7 +258,7 @@ interface ProductRepositoryInterface * 상품과 그 옵션의 활동 로그를 합쳐 페이지네이션으로 조회 * * @param Product $product 대상 상품 - * @param array $filters 조회 필터 (per_page, sort_order) + * @param array $filters 조회 필터 (per_page, sort_order + 확장이 훅으로 추가한 필드) * @return LengthAwarePaginator 활동 로그 페이지네이터 */ public function getActivityLogsForProduct(Product $product, array $filters = []): LengthAwarePaginator; diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php index fc67823d..25cc4be9 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php @@ -2,8 +2,10 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; use Carbon\Carbon; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; use Modules\Sirsoft\Ecommerce\Enums\CouponIssueRecordStatus; use Modules\Sirsoft\Ecommerce\Models\CouponIssue; use Modules\Sirsoft\Ecommerce\Models\Product; @@ -14,6 +16,8 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\CouponIssueRepositoryInterf */ class CouponIssueRepository implements CouponIssueRepositoryInterface { + use PaginatesWithDeferredJoin; + public function __construct( protected CouponIssue $model ) {} @@ -161,14 +165,19 @@ class CouponIssueRepository implements CouponIssueRepositoryInterface } } - return $query->orderByDesc('created_at') - ->paginate($perPage); + // 쿠폰 발급 내역은 발급량에 비례해 계속 늘어난다 + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [['column' => 'created_at', 'direction' => 'desc']], + perPage: $perPage, + ); } /** * {@inheritDoc} */ - public function findByIdsForUser(array $couponIssueIds, int $userId): \Illuminate\Support\Collection + public function findByIdsForUser(array $couponIssueIds, int $userId): Collection { if (empty($couponIssueIds)) { return collect(); diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php index c2ffec0e..b0f1d298 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php @@ -3,6 +3,8 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; use App\Helpers\PermissionHelper; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -18,6 +20,12 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\CouponRepositoryInterface; */ class CouponRepository implements CouponRepositoryInterface { + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + /** 허용 정렬 컬럼 (CouponListRequest 와 동일 집합) */ + private const SORTABLE_COLUMNS = ['created_at', 'name', 'discount_value', 'issued_count']; + public function __construct( protected Coupon $model, protected CouponIssue $issueModel @@ -89,10 +97,9 @@ class CouponRepository implements CouponRepositoryInterface */ private function finalizeListQuery(Builder $query, array $filters, array $with, int $perPage): LengthAwarePaginator { - // 정렬 - $sortBy = $filters['sort_by'] ?? 'created_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy($sortBy, $sortOrder)->orderBy('id', $sortOrder); + // 정렬 (허용 컬럼 화이트리스트로 해석) + $sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at')[0]; + $query->orderBy($sort['column'], $sort['direction'])->orderBy('id', $sort['direction']); // Eager loading if (! empty($with)) { @@ -102,6 +109,8 @@ class CouponRepository implements CouponRepositoryInterface // 기본 관계 로드 $query->with(['creator:id,uuid,name,email']); + // audit:allow repository-paginate-column-pruning reason: 쿠폰 "정의" 목록 — + // 행 수가 운영자가 만든 쿠폰 수에 묶인다(발급 이력은 별도 테이블). description 외에 넓은 컬럼이 없다 return $query->paginate($perPage); } @@ -178,13 +187,19 @@ class CouponRepository implements CouponRepositoryInterface $query->where('status', $filters['status']); } - // 기본 정렬: 발급일 최신순 - $query->orderBy('issued_at', 'desc')->orderBy('id', 'desc'); - - // 관계 로드 (사용처 주문번호 표시를 위해 order 도 로드) - $query->with(['user:id,uuid,name', 'order:id,order_number']); - - return $query->paginate($perPage); + // 지연 조인: 쿠폰 1건의 발급 이력은 대량 발급 시 수십만 건이 될 수 있어 + // 뒤쪽 페이지에서 OFFSET 비용이 그대로 누적된다. inner 는 id 만 훑는다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [ + ['column' => 'issued_at', 'direction' => 'desc'], + ['column' => 'id', 'direction' => 'desc'], + ], + perPage: $perPage, + // 관계 로드 (사용처 주문번호 표시를 위해 order 도 로드) + relations: ['user:id,uuid,name', 'order:id,order_number'], + ); } /** @@ -249,6 +264,8 @@ class CouponRepository implements CouponRepositoryInterface ->orderBy('created_at', 'desc'); if ($perPage !== null) { + // audit:allow repository-paginate-column-pruning reason: 다운로드 가능 쿠폰 "정의" 목록 — + // 발급중 상태의 쿠폰만 남는 좁은 집합이라 OFFSET 이 깊어질 수 없다 return $query->paginate($perPage); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ExtraFeeTemplateRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ExtraFeeTemplateRepository.php index abf4f4eb..177d9d27 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ExtraFeeTemplateRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ExtraFeeTemplateRepository.php @@ -2,9 +2,10 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Ecommerce\Models\ExtraFeeTemplate; use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ExtraFeeTemplateRepositoryInterface; @@ -13,6 +14,12 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ExtraFeeTemplateRepositoryI */ class ExtraFeeTemplateRepository implements ExtraFeeTemplateRepositoryInterface { + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + /** 허용 정렬 컬럼 (ExtraFeeTemplate::scopeOrderByField 와 동일 집합) */ + private const SORTABLE_COLUMNS = ['id', 'zipcode', 'fee', 'region', 'is_active', 'created_at', 'updated_at']; + public function __construct( protected ExtraFeeTemplate $model ) {} @@ -56,12 +63,17 @@ class ExtraFeeTemplateRepository implements ExtraFeeTemplateRepositoryInterface $query->where('is_active', $isActive); } - // 정렬 - $sortBy = $filters['sort_by'] ?? 'zipcode'; - $sortOrder = $filters['sort_order'] ?? 'asc'; - $query->orderByField($sortBy, $sortOrder); + // 정렬 (모델 scopeOrderByField 와 동일한 허용 집합) + $sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'zipcode', 'asc'); - return $query->paginate($perPage); + // 지연 조인: 우편번호 단위 행이라 건수가 커질 수 있고 description(text)이 있어 + // OFFSET 이 훑는 구간에서 넓은 컬럼까지 읽힌다. inner 는 id 만 훑는다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $sort, + perPage: $perPage, + ); } /** @@ -228,12 +240,12 @@ class ExtraFeeTemplateRepository implements ExtraFeeTemplateRepositoryInterface * ID 목록으로 조회하고 ID 키 맵으로 반환합니다 (bulk activity log lookup). * * @param array $ids ID 목록 - * @return \Illuminate\Database\Eloquent\Collection + * @return Collection */ - public function findByIdsKeyed(array $ids): \Illuminate\Database\Eloquent\Collection + public function findByIdsKeyed(array $ids): Collection { if (empty($ids)) { - return new \Illuminate\Database\Eloquent\Collection(); + return new Collection; } return ExtraFeeTemplate::whereIn('id', $ids)->get()->keyBy('id'); diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageBalanceRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageBalanceRepository.php index b235addd..4417e2bc 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageBalanceRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageBalanceRepository.php @@ -7,6 +7,8 @@ use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Ecommerce\Enums\MileageTransactionTypeEnum; use Modules\Sirsoft\Ecommerce\Models\MileageBalance; use Modules\Sirsoft\Ecommerce\Models\MileageTransaction; +use Modules\Sirsoft\Ecommerce\Models\Order; +use Modules\Sirsoft\Ecommerce\Models\OrderOption; use Modules\Sirsoft\Ecommerce\Repositories\Contracts\MileageBalanceRepositoryInterface; /** @@ -202,15 +204,21 @@ class MileageBalanceRepository implements MileageBalanceRepositoryInterface */ private function computePending(int $userId, string $currency): float { - return (float) DB::table('ecommerce_order_options as opt') - ->join('ecommerce_orders as ord', 'opt.order_id', '=', 'ord.id') + // 테이블명은 모델에서 얻는다 (문자열 하드코딩 금지). 별칭은 빌더가 붙이므로 + // 프리픽스 처리도 빌더가 맡는다. + $optionsTable = (new OrderOption)->getTable(); + $ordersTable = (new Order)->getTable(); + $transactionsTable = (new MileageTransaction)->getTable(); + + return (float) DB::table("{$optionsTable} as opt") + ->join("{$ordersTable} as ord", 'opt.order_id', '=', 'ord.id') ->where('ord.user_id', $userId) ->where('ord.currency', $currency) ->whereNotIn('opt.option_status', self::CANCELLED_STATUSES) ->where('opt.subtotal_earned_points_amount', '>', 0) - ->whereNotExists(function ($q) { + ->whereNotExists(function ($q) use ($transactionsTable) { $q->select(DB::raw(1)) - ->from('ecommerce_mileage_transactions as tx') + ->from("{$transactionsTable} as tx") ->whereColumn('tx.order_option_id', 'opt.id') ->whereIn('tx.type', self::EARN_TYPES); }) diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageTransactionRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageTransactionRepository.php index f847dc1c..8e836d88 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageTransactionRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageTransactionRepository.php @@ -3,6 +3,7 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; use App\Models\User; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; use Carbon\Carbon; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; @@ -10,6 +11,8 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Ecommerce\Enums\MileageTransactionTypeEnum; use Modules\Sirsoft\Ecommerce\Models\MileageTransaction; +use Modules\Sirsoft\Ecommerce\Models\Order; +use Modules\Sirsoft\Ecommerce\Models\OrderOption; use Modules\Sirsoft\Ecommerce\Repositories\Contracts\MileageTransactionRepositoryInterface; /** @@ -17,6 +20,8 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\MileageTransactionRepositor */ class MileageTransactionRepository implements MileageTransactionRepositoryInterface { + use PaginatesWithDeferredJoin; + /** * 적립(잔액 증가) 유형 목록 * @@ -170,17 +175,20 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf public function paginateWithFilters(array $filters, int $perPage = 20): LengthAwarePaginator { // 적립 lot 의 소멸 합 eager 집계 (N+1 회피) — 이 lot 을 source 로 하는 expired 거래 amount 합(음수). - // 상관 서브쿼리는 self-reference + alias 가 빌더 prefix 처리와 충돌하므로 raw 로 일관 작성한다 - // (prefix 는 실제 테이블명에 한 번만 직접 적용). - $prefixedTable = DB::connection()->getTablePrefix().(new MileageTransaction)->getTable(); - $expiredType = MileageTransactionTypeEnum::EXPIRED->value; - $expiredSumSub = "(select COALESCE(SUM(ABS(exp.amount)), 0) from `{$prefixedTable}` as exp" - ." where exp.type = '{$expiredType}' and exp.source_transaction_id = `{$prefixedTable}`.id)"; + // 소멸분 합계는 같은 테이블을 다시 보는 상관 서브쿼리다. 별칭은 빌더가 만들고 + // (grammar 가 프리픽스를 별칭에도 붙인다), raw 는 집계 함수 한 곳만 남긴다. + // 서브쿼리 FROM 이 한 테이블뿐이라 집계 대상 컬럼은 한정 없이 써도 모호하지 않다. + $table = (new MileageTransaction)->getTable(); + + $expiredAmount = DB::table("{$table} as exp") + ->select(DB::raw('COALESCE(SUM(ABS(amount)), 0)')) + ->where('exp.type', MileageTransactionTypeEnum::EXPIRED->value) + ->whereColumn('exp.source_transaction_id', "{$table}.id"); $query = MileageTransaction::query() ->with(['user', 'grantedByUser', 'order']) ->addSelect('*') - ->addSelect(DB::raw("{$expiredSumSub} as expired_amount")); + ->addSelect(['expired_amount' => $expiredAmount]); if (! empty($filters['user_id'])) { $query->where('user_id', $filters['user_id']); @@ -233,8 +241,12 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf }); } + // 적립/사용 이력은 계속 쌓이므로 지연 조인으로 뒤쪽 페이지 비용을 고정한다. + // 정렬은 applySort 가 닫힌 슬러그 집합으로 해석해 이미 쿼리에 적용돼 있다. $this->applySort($query, $filters['sort'] ?? 'created_at_desc'); + // audit:allow repository-paginate-column-pruning reason: 정렬이 applySort 로 쿼리에 직접 + // 적용돼 지연 조인 계약(정렬 미적용 쿼리)을 만족하지 않는다. 컬럼 프루닝은 후속 정리 대상. return $query->paginate($perPage); } @@ -278,7 +290,13 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf $query->where('currency', $filters['currency']); } - return $query->orderByDesc('id')->paginate($perPage); + // 회원 마일리지 내역 — 계속 쌓이는 이력이라 지연 조인으로 뒤쪽 페이지 비용을 고정한다 + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [['column' => 'id', 'direction' => 'desc']], + perPage: $perPage, + ); } /** @@ -360,8 +378,9 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf { $threshold = $now->copy()->subDays($delayDays); - $query = DB::table('ecommerce_order_options as opt') - ->join('ecommerce_orders as ord', 'opt.order_id', '=', 'ord.id') + // 테이블명은 모델에서 얻는다 (문자열 하드코딩 금지) + $query = DB::table((new OrderOption)->getTable().' as opt') + ->join((new Order)->getTable().' as ord', 'opt.order_id', '=', 'ord.id') ->whereNotNull('ord.user_id') ->where('opt.option_status', $triggerStatus) ->whereNotNull("opt.{$triggerColumn}") @@ -371,7 +390,7 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf // (나눠 확정·병합으로 목표액이 늘어난 옵션의 잔여분까지 스케줄러가 포착) // 빌더 서브쿼리로 표현 — raw SQL/테이블 별칭은 prefix 자동 적용을 받지 못하므로 별칭 없이 컬럼만 참조. ->where('opt.subtotal_earned_points_amount', '>', function ($sub) { - $sub->from('ecommerce_mileage_transactions') + $sub->from((new MileageTransaction)->getTable()) ->selectRaw('COALESCE(SUM(amount), 0)') ->whereColumn('order_option_id', 'opt.id') ->where('type', MileageTransactionTypeEnum::PURCHASE_EARN->value); diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php index b69e239f..a2aee2dc 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php @@ -5,6 +5,8 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; use App\Helpers\PermissionHelper; use App\Models\ActivityLog; use App\Models\User; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -23,6 +25,63 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\OrderRepositoryInterface; */ class OrderRepository implements OrderRepositoryInterface { + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + /** + * 주문 목록이 실제로 사용하는 컬럼 + * + * `ecommerce_orders` 는 스냅샷 mediumText 5종 + 관리자 메모 + 통화별 금액 text 16종을 갖는다. + * 목록에서 쓰지 않는 이 컬럼들까지 읽으면 뒤쪽 페이지에서 건너뛸 행의 넓은 컬럼까지 함께 + * 읽혀 비용이 선형으로 커진다. 관리자 목록(OrderListResource)과 회원 주문내역 + * (UserOrderListResource)이 참조하는 컬럼의 합집합만 남긴다. + * + * @var array + */ + private const LIST_COLUMNS = [ + 'id', + 'user_id', + 'order_number', + 'order_status', + 'order_device', + 'is_first_order', + 'currency', + 'currency_snapshot', + 'total_amount', + 'total_shipping_amount', + 'total_paid_amount', + 'total_cancelled_amount', + 'total_refunded_amount', + 'total_points_used_amount', + 'total_earned_points_amount', + 'mc_total_amount', + 'mc_total_shipping_amount', + 'ordered_at', + 'created_at', + 'updated_at', + 'deleted_at', + ]; + + /** + * 주문 목록 정렬 허용 컬럼 + * + * 요청 값을 그대로 orderBy 에 넘기면 없는 컬럼으로 SQL 오류가 나거나 인덱스 없는 넓은 + * 컬럼 정렬을 강제할 수 있다. + * + * @var array + */ + private const SORTABLE_COLUMNS = [ + 'id', + 'order_number', + 'order_status', + 'total_amount', + 'total_shipping_amount', + 'total_paid_amount', + 'ordered_at', + 'paid_at', + 'created_at', + ]; + public function __construct( protected Order $model ) {} @@ -90,14 +149,9 @@ class OrderRepository implements OrderRepositoryInterface */ public function getListWithFilters(array $filters, int $perPage = 20): LengthAwarePaginator { - $query = $this->model->newQuery() - ->with([ - 'user', - 'options', - 'shippingAddress', - 'payment', - 'shippings', - ]); + // 관계는 지연 조인의 outer 에서만 로드한다 (inner 에 붙으면 관계 쿼리가 두 번 실행되고, + // inner 는 키 컬럼만 조회하므로 eager load 가 성립하지도 않는다). + $query = $this->model->newQuery(); // 권한 스코프 필터링 PermissionHelper::applyPermissionScope($query, 'sirsoft-ecommerce.orders.read'); @@ -119,8 +173,8 @@ class OrderRepository implements OrderRepositoryInterface if ($ordererUser) { $query->where('user_id', $ordererUser->id); } else { - // UUID에 해당하는 회원이 없으면 결과 없음 - $query->whereRaw('1 = 0'); + // UUID에 해당하는 회원이 없으면 결과 없음. 빈 whereIn 은 `0 = 1` 로 컴파일된다 + $query->whereIn($query->getModel()->getKeyName(), []); } } @@ -285,12 +339,22 @@ class OrderRepository implements OrderRepositoryInterface $query->whereIn('order_device', $devices); } - // 정렬 - $sortBy = $filters['sort_by'] ?? 'ordered_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy($sortBy, $sortOrder); + // 정렬 — 요청 값은 허용 목록으로만 해석한다 + $sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'ordered_at'); - return $query->paginate($perPage); + return $this->paginateWithDeferredJoin( + query: $query, + columns: self::LIST_COLUMNS, + sort: $sort, + perPage: $perPage, + relations: [ + 'user', + 'options', + 'shippingAddress', + 'payment', + 'shippings', + ], + ); } /** @@ -496,10 +560,10 @@ class OrderRepository implements OrderRepositoryInterface $this->applyFiltersToQuery($query, $filters); } - // 정렬 - $sortBy = $filters['sort_by'] ?? 'ordered_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; - $query->orderBy($sortBy, $sortOrder); + // 정렬 (목록 조회와 동일한 허용 컬럼 화이트리스트) + foreach ($this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'ordered_at') as $sort) { + $query->orderBy($sort['column'], $sort['direction']); + } return $query->get(); } @@ -639,7 +703,7 @@ class OrderRepository implements OrderRepositoryInterface $optionIds = $order->options()->pluck('id')->toArray(); $addressIds = $order->addresses()->pluck('id')->toArray(); - return ActivityLog::where(function (Builder $q) use ($order, $optionIds, $addressIds) { + $query = ActivityLog::where(function (Builder $q) use ($order, $optionIds, $addressIds) { // 주문 자체 로그 $q->where(function (Builder $sub) use ($order) { $sub->where('loggable_type', $order->getMorphClass()) @@ -661,6 +725,15 @@ class OrderRepository implements OrderRepositoryInterface ->whereIn('loggable_id', $addressIds); }); } - })->orderBy('created_at', $sortOrder)->paginate($perPage); + }); + + // 활동 로그는 변경 내역(mediumText)을 리소스가 그대로 노출하므로 컬럼은 좁히지 않고 + // 지연 조인으로 넓은 컬럼을 읽는 행 수만 이번 페이지 분량으로 고정한다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [['column' => 'created_at', 'direction' => $sortOrder]], + perPage: $perPage, + ); } } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductCommonInfoRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductCommonInfoRepository.php index 2dce4a26..e8c0df83 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductCommonInfoRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductCommonInfoRepository.php @@ -87,6 +87,8 @@ class ProductCommonInfoRepository implements ProductCommonInfoRepositoryInterfac $query->with($with); } }) + // audit:allow repository-paginate-column-pruning reason: 상품 공통정보 "템플릿" 목록 — + // 행 수가 운영자가 만든 템플릿 수에 묶여 OFFSET 이 깊어질 수 없다 ->paginate($perPage); } @@ -113,6 +115,8 @@ class ProductCommonInfoRepository implements ProductCommonInfoRepositoryInterfac $query->with($with); } + // audit:allow repository-paginate-column-pruning reason: 상품 공통정보 "템플릿" 목록 — + // 행 수가 운영자가 만든 템플릿 수에 묶여 OFFSET 이 깊어질 수 없다 return $query->paginate($perPage); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductInquiryRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductInquiryRepository.php index aedd75cb..6eb5605a 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductInquiryRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductInquiryRepository.php @@ -2,8 +2,10 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Carbon; use Modules\Sirsoft\Ecommerce\Models\ProductInquiry; use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ProductInquiryRepositoryInterface; @@ -12,6 +14,8 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ProductInquiryRepositoryInt */ class ProductInquiryRepository implements ProductInquiryRepositoryInterface { + use PaginatesWithDeferredJoin; + public function __construct( protected ProductInquiry $model ) {} @@ -43,6 +47,8 @@ class ProductInquiryRepository implements ProductInquiryRepositoryInterface return $this->model->newQuery() ->where('product_id', $productId) ->orderBy('created_at', 'desc') + // audit:allow repository-paginate-column-pruning reason: 상품 1건에 종속된 문의 목록 — + // where(product_id) 로 이미 좁혀져 OFFSET 이 깊어질 수 없고, 목록이 본문을 그대로 쓴다 ->paginate($perPage); } @@ -86,6 +92,8 @@ class ProductInquiryRepository implements ProductInquiryRepositoryInterface }); } + // audit:allow repository-paginate-column-pruning reason: 사용자 1명에 종속된 내 문의 목록 — + // where(user_id) 로 이미 좁혀져 OFFSET 이 깊어질 수 없다 return $query->paginate($perPage); } @@ -94,9 +102,7 @@ class ProductInquiryRepository implements ProductInquiryRepositoryInterface */ public function getListWithFilters(array $filters, int $perPage = 20): LengthAwarePaginator { - $query = $this->model->newQuery() - ->with(['product', 'user']) - ->orderBy('created_at', 'desc'); + $query = $this->model->newQuery(); // 상품명 검색 if (! empty($filters['search_keyword'])) { @@ -116,15 +122,23 @@ class ProductInquiryRepository implements ProductInquiryRepositoryInterface $query->where('is_answered', (bool) $filters['is_answered']); } - // 기간 필터 + // 기간 필터 — whereDate 는 컬럼에 함수를 씌워 인덱스를 무력화하므로 범위 조건으로 준다 if (! empty($filters['date_from'])) { - $query->whereDate('created_at', '>=', $filters['date_from']); + $query->where('created_at', '>=', Carbon::parse($filters['date_from'])->startOfDay()); } if (! empty($filters['date_to'])) { - $query->whereDate('created_at', '<=', $filters['date_to']); + $query->where('created_at', '<=', Carbon::parse($filters['date_to'])->endOfDay()); } - return $query->paginate($perPage); + // 지연 조인: 전역 문의 목록은 무제한 성장하고 content(text)가 있어 + // OFFSET 이 훑는 구간에서 넓은 컬럼까지 읽힌다. inner 는 id 만 훑는다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [['column' => 'created_at', 'direction' => 'desc']], + perPage: $perPage, + relations: ['product', 'user'], + ); } /** diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductNoticeTemplateRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductNoticeTemplateRepository.php index acbb48b5..0f279755 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductNoticeTemplateRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductNoticeTemplateRepository.php @@ -90,6 +90,8 @@ class ProductNoticeTemplateRepository implements ProductNoticeTemplateRepository $query->with($with); } + // audit:allow repository-paginate-column-pruning reason: 상품 안내 "템플릿" 목록 — + // 행 수가 운영자가 만든 템플릿 수에 묶여 OFFSET 이 깊어질 수 없다 return $query->paginate($perPage); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php index 6bddf8c0..980d1699 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php @@ -4,6 +4,8 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; use App\Helpers\PermissionHelper; use App\Models\ActivityLog; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use App\Search\Engines\DatabaseFulltextEngine; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; @@ -21,6 +23,12 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ProductRepositoryInterface; */ class ProductRepository implements ProductRepositoryInterface { + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + /** 허용 정렬 컬럼 (ProductListRequest 와 동일 집합) */ + private const ADMIN_SORTABLE_COLUMNS = ['created_at', 'updated_at', 'selling_price', 'stock_quantity', 'name']; + public function __construct( protected Product $model ) {} @@ -69,11 +77,14 @@ class ProductRepository implements ProductRepositoryInterface */ public function getListWithFilters(array $filters, int $perPage = 20): LengthAwarePaginator { - $query = $this->model->newQuery()->with(['options', 'categories', 'images', 'brand', 'shippingPolicy']); + $query = $this->model->newQuery(); // 권한 스코프 필터링 PermissionHelper::applyPermissionScope($query, 'sirsoft-ecommerce.products.read'); + // 지연 조인 outer 전용 관계 (inner 는 id 만 훑으므로 여기서 with() 를 걸지 않는다) + $listRelations = ['options', 'categories', 'images', 'brand', 'shippingPolicy']; + // 문자열 검색 if (! empty($filters['search_keyword'])) { $keyword = $filters['search_keyword']; @@ -110,11 +121,16 @@ class ProductRepository implements ProductRepositoryInterface // 매칭 ID 로 조건 한정 (빈 매칭은 존재하지 않는 ID 로 빈 결과 → total=0 이 정상) $query->whereIn('id', $matchedIds ?: [0]); - // 필터 + 정렬 적용 + // 필터 적용 $this->applyAdminFilters($query, $filters); - $this->applyAdminSorting($query, $filters); - return $query->paginate($perPage); + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $this->resolveAdminSortSpec($filters), + perPage: $perPage, + relations: $listRelations, + ); } // FULLTEXT 미대상 필드 (product_code, sku, barcode) → LIKE 직접 @@ -134,10 +150,13 @@ class ProductRepository implements ProductRepositoryInterface // 필터 적용 $this->applyAdminFilters($query, $filters); - // 정렬 - $this->applyAdminSorting($query, $filters); - - return $query->paginate($perPage); + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $this->resolveAdminSortSpec($filters), + perPage: $perPage, + relations: $listRelations, + ); } /** @@ -398,11 +417,13 @@ class ProductRepository implements ProductRepositoryInterface public function getPublicList(array $filters, int $perPage = 20): LengthAwarePaginator { $query = $this->model->newQuery() - ->with(['images', 'categories', 'brand', 'activeLabelAssignments.label']) ->withCount('visibleReviews as review_count') ->withAvg('visibleReviews as rating_avg', 'rating') ->where('display_status', 'visible'); + // 지연 조인 outer 전용 관계 (inner 는 id 만 훑으므로 with() 를 걸지 않는다) + $listRelations = ['images', 'categories', 'brand', 'activeLabelAssignments.label']; + // 카테고리 필터 (ID) — 선택 카테고리 + 모든 하위 카테고리 포함 if (! empty($filters['category_id'])) { $categoryIds = Category::selfAndDescendantIds((int) $filters['category_id']); @@ -420,8 +441,8 @@ class ProductRepository implements ProductRepositoryInterface $q->whereIn('ecommerce_product_categories.category_id', $categoryIds); }); } else { - // 존재하지 않는 slug → 빈 결과 (500 아님) - $query->whereRaw('1 = 0'); + // 존재하지 않는 slug → 빈 결과 (500 아님). 빈 whereIn 은 `0 = 1` 로 컴파일된다 + $query->whereIn($query->getModel()->getKeyName(), []); } } @@ -446,18 +467,35 @@ class ProductRepository implements ProductRepositoryInterface // 정렬 $sort = $filters['sort'] ?? 'latest'; - match ($sort) { - 'sales' => $query + + // 판매량 정렬은 집계 서브쿼리(total_sold) 를 select 에 올려 두고 그 별칭으로 정렬한다. + // 지연 조인의 inner 는 select 를 id 하나로 교체하므로 별칭이 사라져 성립하지 않는다. + if ($sort === 'sales') { + $query ->addSelect(['total_sold' => OrderOption::selectRaw('COALESCE(SUM(quantity), 0)') ->whereColumn('product_id', 'ecommerce_products.id'), ]) - ->orderByDesc('total_sold'), - 'price_asc' => $query->orderBy('selling_price', 'asc'), - 'price_desc' => $query->orderBy('selling_price', 'desc'), - default => $query->orderBy('created_at', 'desc'), // latest + ->orderByDesc('total_sold') + ->with($listRelations); + + // audit:allow repository-paginate-column-pruning reason: 판매량 정렬은 집계 서브쿼리 + // 별칭 기준이라 지연 조인 inner(=id 단일 select)로 옮길 수 없다. 나머지 정렬은 지연 조인 적용 + return $query->paginate($perPage); + } + + $sortSpec = match ($sort) { + 'price_asc' => [['column' => 'selling_price', 'direction' => 'asc']], + 'price_desc' => [['column' => 'selling_price', 'direction' => 'desc']], + default => [['column' => 'created_at', 'direction' => 'desc']], // latest }; - return $query->paginate($perPage); + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $sortSpec, + perPage: $perPage, + relations: $listRelations, + ); } /** @@ -536,6 +574,10 @@ class ProductRepository implements ProductRepositoryInterface ->withAvg('visibleReviews as rating_avg', 'rating') ->when($categoryId !== null, fn ($q) => $q->whereHas('categories', fn ($c) => $c->where('ecommerce_categories.id', $categoryId))) ->orderBy($orderBy, $direction) + // audit:allow repository-paginate-column-pruning reason: 통합검색 — + // whereIn(id, $matchedIds) 로 FULLTEXT 매칭 ID 집합에 먼저 한정되므로 스캔 대상이 + // 상품 전체가 아니라 매칭 건수로 묶인다(OFFSET 이 훑는 범위도 그 안). 지연 조인의 + // inner 가 하는 일을 매칭 ID 산출이 이미 수행하는 구조다 ->paginate($limit, ['*'], 'page', $page); return ['total' => $paginator->total(), 'items' => $paginator->getCollection()]; @@ -672,23 +714,25 @@ class ProductRepository implements ProductRepositoryInterface } /** - * 관리자 상품 목록 정렬을 쿼리에 적용합니다. + * 관리자 상품 목록 정렬 스펙을 해석합니다. + * + * 지연 조인은 정렬을 inner/outer 양쪽에 동일하게 적용해야 하므로, 쿼리에 직접 orderBy 를 + * 거는 대신 스펙 배열을 돌려준다. * - * @param Builder $query Eloquent 쿼리 빌더 * @param array $filters 필터 배열 + * @return array 정렬 스펙 */ - private function applyAdminSorting($query, array $filters): void + private function resolveAdminSortSpec(array $filters): array { - $sortBy = $filters['sort_by'] ?? 'created_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; + // 허용 컬럼 화이트리스트로 해석 (없는 컬럼 정렬은 기본값으로 흡수) + $sort = $this->resolveSortSpec($filters, self::ADMIN_SORTABLE_COLUMNS, 'created_at')[0]; // 다국어 이름 정렬 처리 - if ($sortBy === 'name') { - $locale = app()->getLocale(); - $query->orderBy("name->{$locale}", $sortOrder); - } else { - $query->orderBy($sortBy, $sortOrder); + if ($sort['column'] === 'name') { + $sort['column'] = 'name->'.app()->getLocale(); } + + return [$sort]; } /** @@ -755,7 +799,7 @@ class ProductRepository implements ProductRepositoryInterface $optionIds = $product->options()->pluck('id')->toArray(); - return ActivityLog::where(function (Builder $q) use ($product, $optionIds) { + $query = ActivityLog::where(function (Builder $q) use ($product, $optionIds) { // 상품 자체 로그 $q->where(function (Builder $sub) use ($product) { $sub->where('loggable_type', $product->getMorphClass()) @@ -769,6 +813,15 @@ class ProductRepository implements ProductRepositoryInterface ->whereIn('loggable_id', $optionIds); }); } - })->orderBy('created_at', $sortOrder)->paginate($perPage); + }); + + // 활동 로그는 변경 내역(mediumText)을 리소스가 그대로 노출하므로 컬럼은 좁히지 않고 + // 지연 조인으로 넓은 컬럼을 읽는 행 수만 이번 페이지 분량으로 고정한다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: [['column' => 'created_at', 'direction' => $sortOrder]], + perPage: $perPage, + ); } } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php index 63230662..26d9fbdf 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php @@ -2,10 +2,14 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use Carbon\Carbon; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Ecommerce\Enums\ReviewStatus; +use Modules\Sirsoft\Ecommerce\Models\OrderOption; +use Modules\Sirsoft\Ecommerce\Models\ProductOption; use Modules\Sirsoft\Ecommerce\Models\ProductReview; use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ProductReviewRepositoryInterface; @@ -14,6 +18,8 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ProductReviewRepositoryInte */ class ProductReviewRepository implements ProductReviewRepositoryInterface { + use PaginatesWithDeferredJoin; + public function __construct( protected ProductReview $model ) {} @@ -96,24 +102,31 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface $query->where('status', $filters['status']); } - // 기간 필터 + // 기간 필터 — whereDate 는 컬럼에 DATE() 를 씌워 인덱스를 못 쓰게 만든다. + // 같은 결과를 내는 범위 조건으로 바꿔 created_at 인덱스를 살린다 + // (종료일은 그날 23:59:59.999999 까지 포함해야 whereDate 와 동일한 경계를 갖는다). if (! empty($filters['start_date'])) { - $query->whereDate('created_at', '>=', $filters['start_date']); + $query->where('created_at', '>=', Carbon::parse($filters['start_date'])->startOfDay()); } if (! empty($filters['end_date'])) { - $query->whereDate('created_at', '<=', $filters['end_date']); + $query->where('created_at', '<=', Carbon::parse($filters['end_date'])->endOfDay()); } - // 정렬 - $sort = $filters['sort'] ?? 'created_at_desc'; - match ($sort) { - 'created_at_asc' => $query->orderBy('created_at'), - 'rating_desc' => $query->orderByDesc('rating'), - 'rating_asc' => $query->orderBy('rating'), - default => $query->orderByDesc('created_at'), + // 정렬 — 선택지가 닫힌 집합이라 match 로 충분하다 + $sort = match ($filters['sort'] ?? 'created_at_desc') { + 'created_at_asc' => [['column' => 'created_at', 'direction' => 'asc']], + 'rating_desc' => [['column' => 'rating', 'direction' => 'desc']], + 'rating_asc' => [['column' => 'rating', 'direction' => 'asc']], + default => [['column' => 'created_at', 'direction' => 'desc']], }; - return $query->paginate($perPage); + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $sort, + perPage: $perPage, + relations: ['user', 'product', 'images', 'orderOption.order', 'replyAdmin'], + ); } /** @@ -143,7 +156,7 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface if ($value === '' || $value === null) { continue; } - $optionIds = DB::table('ecommerce_product_options') + $optionIds = DB::table((new ProductOption)->getTable()) ->where('product_id', $productId) ->whereRaw( "JSON_CONTAINS(option_values, JSON_OBJECT('key', JSON_OBJECT('ko', ?), 'value', JSON_OBJECT('ko', ?)))", @@ -153,7 +166,8 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface ->toArray(); if (empty($optionIds)) { - $query->whereRaw('0 = 1'); + // 일치 옵션 없음 → 빈 결과. 빈 whereIn 은 `0 = 1` 로 컴파일된다 + $query->whereIn($query->getModel()->getKeyName(), []); break; } @@ -163,15 +177,20 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface } } - // 정렬 - $sort = $filters['sort'] ?? 'created_at_desc'; - match ($sort) { - 'rating_desc' => $query->orderByDesc('rating'), - 'rating_asc' => $query->orderBy('rating'), - default => $query->orderByDesc('created_at'), + // 정렬 — 선택지가 닫힌 집합이라 match 로 충분하다 + $sort = match ($filters['sort'] ?? 'created_at_desc') { + 'rating_desc' => [['column' => 'rating', 'direction' => 'desc']], + 'rating_asc' => [['column' => 'rating', 'direction' => 'asc']], + default => [['column' => 'created_at', 'direction' => 'desc']], }; - return $query->paginate($perPage); + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $sort, + perPage: $perPage, + relations: ['user', 'images'], + ); } /** @@ -222,7 +241,7 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface public function getOptionFilters(int $productId): array { // 상품의 모든 옵션 조회 (기준: product_options 전체) - $options = DB::table('ecommerce_product_options') + $options = DB::table((new ProductOption)->getTable()) ->where('product_id', $productId) ->orderBy('sort_order') ->pluck('option_values', 'id') @@ -233,8 +252,9 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface } // 옵션별 리뷰 건수 집계 (option_id → review count) - $reviewCounts = DB::table('ecommerce_product_reviews as r') - ->join('ecommerce_order_options as oo', 'r.order_option_id', '=', 'oo.id') + // 테이블명은 모델에서 얻는다 (문자열 하드코딩 금지) + $reviewCounts = DB::table((new ProductReview)->getTable().' as r') + ->join((new OrderOption)->getTable().' as oo', 'r.order_option_id', '=', 'oo.id') ->where('r.product_id', $productId) ->where('r.status', ReviewStatus::VISIBLE->value) ->whereNull('r.deleted_at') diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php index fcb8412f..0b0dfe9b 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php @@ -59,6 +59,8 @@ class ProductWishlistRepository implements ProductWishlistRepositoryInterface ->where('user_id', $userId) ->with(['product.brand', 'product.categories', 'product.activeLabelAssignments.label']) ->orderByDesc('created_at') + // audit:allow repository-paginate-column-pruning reason: 사용자 1명에 종속된 찜 목록 — + // where(user_id) 로 이미 좁혀지고, 피벗 성격의 테이블이라 넓은 컬럼이 없다 ->paginate($perPage); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php index f73af9c9..1136e207 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php @@ -3,6 +3,7 @@ namespace Modules\Sirsoft\Ecommerce\Repositories; use App\Helpers\PermissionHelper; +use App\Repositories\Concerns\ResolvesSortSpec; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; @@ -15,6 +16,11 @@ use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ShippingPolicyRepositoryInt */ class ShippingPolicyRepository implements ShippingPolicyRepositoryInterface { + use ResolvesSortSpec; + + /** 허용 정렬 컬럼 (ShippingPolicyListRequest 와 동일 집합) */ + private const SORTABLE_COLUMNS = ['id', 'name', 'is_active', 'sort_order', 'created_at', 'updated_at']; + public function __construct( protected ShippingPolicy $model ) {} @@ -75,18 +81,19 @@ class ShippingPolicyRepository implements ShippingPolicyRepositoryInterface $query->where('is_active', $isActive); } - // 정렬 - $sortBy = $filters['sort_by'] ?? 'created_at'; - $sortOrder = $filters['sort_order'] ?? 'desc'; + // 정렬 (허용 컬럼 화이트리스트로 해석) + $sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at')[0]; // 다국어 이름 정렬 처리 - if ($sortBy === 'name') { + if ($sort['column'] === 'name') { $locale = app()->getLocale(); - $query->orderBy("name->{$locale}", $sortOrder); + $query->orderBy("name->{$locale}", $sort['direction']); } else { - $query->orderBy($sortBy, $sortOrder); + $query->orderBy($sort['column'], $sort['direction']); } + // audit:allow repository-paginate-column-pruning reason: 배송정책 "정의" 목록 — + // 행 수가 운영자가 만든 정책 수에 묶여 OFFSET 이 깊어질 수 없다 return $query->paginate($perPage); } diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/CouponUpdateConditionalValidationTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/CouponUpdateConditionalValidationTest.php new file mode 100644 index 00000000..426b07a5 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/CouponUpdateConditionalValidationTest.php @@ -0,0 +1,172 @@ +setLocale('ko'); + + $this->adminUser = $this->createAdminUser([ + 'sirsoft-ecommerce.promotion-coupon.read', + 'sirsoft-ecommerce.promotion-coupon.create', + 'sirsoft-ecommerce.promotion-coupon.update', + ]); + } + + /** + * 쿠폰 생성 데이터 + * + * @param array $overrides 오버라이드할 속성 + * @return array 생성 요청 본문 + */ + private function validCouponData(array $overrides = []): array + { + return array_merge([ + 'name' => ['ko' => '유효기간 쿠폰', 'en' => 'Validity Coupon'], + 'target_type' => CouponTargetType::PRODUCT_AMOUNT->value, + 'discount_type' => CouponDiscountType::FIXED->value, + 'discount_value' => 1000, + 'min_order_amount' => 5000, + 'issue_method' => CouponIssueMethod::DIRECT->value, + 'issue_condition' => CouponIssueCondition::MANUAL->value, + 'issue_status' => CouponIssueStatus::ISSUING->value, + 'per_user_limit' => 0, + 'valid_type' => 'period', + 'valid_from' => now()->format('Y-m-d'), + 'valid_to' => now()->addMonth()->format('Y-m-d'), + ], $overrides); + } + + /** + * 테스트용 쿠폰을 생성하고 ID를 반환합니다. + * + * @param array $overrides 생성 데이터 오버라이드 + * @return int 생성된 쿠폰 ID + */ + private function createCoupon(array $overrides = []): int + { + $response = $this->actingAs($this->adminUser) + ->postJson($this->baseUrl, $this->validCouponData($overrides)); + + $response->assertStatus(201); + + return (int) $response->json('data.id'); + } + + /** + * 기간 지정 쿠폰의 시작일/종료일을 비운 수정은 거부됩니다. + */ + public function test_update_period_type_without_dates_fails(): void + { + $couponId = $this->createCoupon(); + + $response = $this->actingAs($this->adminUser) + ->putJson("{$this->baseUrl}/{$couponId}", [ + 'valid_type' => 'period', + 'valid_from' => null, + 'valid_to' => null, + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['valid_from', 'valid_to']); + } + + /** + * 발급일 기준 쿠폰의 유효일수를 비운 수정은 거부됩니다. + */ + public function test_update_days_from_issue_type_without_valid_days_fails(): void + { + $couponId = $this->createCoupon([ + 'valid_type' => 'days_from_issue', + 'valid_days' => 30, + 'valid_from' => null, + 'valid_to' => null, + ]); + + $response = $this->actingAs($this->adminUser) + ->putJson("{$this->baseUrl}/{$couponId}", [ + 'valid_type' => 'days_from_issue', + 'valid_days' => null, + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['valid_days']); + } + + /** + * 유효기간을 함께 보내는 정상 수정은 통과합니다. + */ + public function test_update_period_type_with_dates_passes(): void + { + $couponId = $this->createCoupon(); + + $response = $this->actingAs($this->adminUser) + ->putJson("{$this->baseUrl}/{$couponId}", [ + 'valid_type' => 'period', + 'valid_from' => now()->addDay()->format('Y-m-d'), + 'valid_to' => now()->addMonths(2)->format('Y-m-d'), + ]); + + $response->assertStatus(200); + } + + /** + * valid_type 을 보내지 않는 부분 수정은 유효기간 필드를 요구하지 않습니다. + * + * required_if 는 조건 필드가 요청에 없으면 발화하지 않으므로, 다른 탭만 수정하는 + * 요청이 유효기간 때문에 막히지 않아야 합니다. + */ + public function test_update_without_valid_type_does_not_require_dates(): void + { + $couponId = $this->createCoupon(); + + $response = $this->actingAs($this->adminUser) + ->putJson("{$this->baseUrl}/{$couponId}", [ + 'min_order_amount' => 7000, + ]); + + $response->assertStatus(200); + } + + /** + * per_user_limit 을 보내지 않는 부분 수정이 통과합니다. + * + * 같은 파일의 다른 필드는 모두 sometimes 를 갖는데 per_user_limit 만 누락돼 있어 + * 부분 수정 요청이 "1인당 사용 제한" 미전송으로 422 가 되던 결함입니다. + */ + public function test_update_without_per_user_limit_passes(): void + { + $couponId = $this->createCoupon(); + + $response = $this->actingAs($this->adminUser) + ->putJson("{$this->baseUrl}/{$couponId}", [ + 'min_order_amount' => 8000, + ]); + + $response->assertStatus(200); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php new file mode 100644 index 00000000..01891fd2 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php @@ -0,0 +1,199 @@ +adminUser = $this->createAdminUser(['sirsoft-ecommerce.orders.read']); + } + + /** 쿼리 로그 수집을 시작합니다. */ + private function startCapture(): void + { + $this->queries = []; + + DB::listen(function ($query) { + $this->queries[] = $query->sql; + }); + } + + /** + * 수집된 쿼리 중 조건에 맞는 것만 돌려줍니다. + * + * @param string $needle 포함 문자열 + * @return array 일치 쿼리 목록 + */ + private function queriesContaining(string $needle): array + { + return array_values(array_filter($this->queries, fn (string $sql) => str_contains($sql, $needle))); + } + + /** + * 주문 목록을 조회합니다. + * + * 페이지 번호는 페이지네이터가 요청에서 해석하므로(HTTP 경로와 동일), 저장소를 직접 + * 호출하는 테스트에서는 resolver 로 지정한다. + * + * @param array $filters 조회 필터 + * @param int $perPage 페이지당 건수 + * @param int $page 조회할 페이지 + * @return LengthAwarePaginator 조회 결과 + */ + private function listOrders(array $filters = [], int $perPage = 10, int $page = 1) + { + Paginator::currentPageResolver(fn () => $page); + + try { + return app(OrderRepositoryInterface::class)->getListWithFilters($filters, $perPage); + } finally { + Paginator::currentPageResolver(fn () => 1); + } + } + + public function test_offset_scan_reads_only_id_column(): void + { + OrderFactory::new()->count(25)->create(); + + $this->startCapture(); + $this->listOrders(page: 2); + + $offsetQueries = $this->queriesContaining('offset'); + + $this->assertNotEmpty($offsetQueries, '페이지 조회에는 OFFSET 쿼리가 있어야 한다'); + + foreach ($offsetQueries as $sql) { + // 넓은 컬럼이 OFFSET 스캔에 실리면 뒤쪽 페이지 비용이 선형으로 커진다 + $this->assertStringNotContainsString('admin_memo', $sql); + $this->assertStringNotContainsString('order_meta', $sql); + $this->assertStringNotContainsString('promotions_applied_snapshot', $sql); + $this->assertStringNotContainsString('mc_subtotal_amount', $sql); + } + } + + public function test_list_query_does_not_select_unused_wide_columns(): void + { + OrderFactory::new()->count(3)->create(); + + $this->startCapture(); + $this->listOrders(); + + $selectQueries = $this->queriesContaining('from `g7_ecommerce_orders`'); + + $this->assertNotEmpty($selectQueries); + + foreach ($selectQueries as $sql) { + $this->assertStringNotContainsString('admin_memo', $sql, '목록은 관리자 메모를 읽지 않는다'); + $this->assertStringNotContainsString('mileage_policy_snapshot', $sql); + } + } + + public function test_relation_queries_run_once_for_the_page(): void + { + OrderFactory::new()->count(5)->create(); + + $this->startCapture(); + $result = $this->listOrders(); + + // 관계는 outer 에서만 로드된다 — inner 에도 붙으면 같은 관계 쿼리가 2회 실행된다 + $this->assertCount(1, $this->queriesContaining('from `g7_ecommerce_order_options`')); + $this->assertTrue($result->getCollection()->first()->relationLoaded('options')); + $this->assertTrue($result->getCollection()->first()->relationLoaded('shippingAddress')); + } + + public function test_pages_do_not_overlap_and_follow_sort_order(): void + { + // ordered_at 이 같은 주문이 섞여 있어도 페이지 경계가 흔들리면 안 된다 + $sharedTime = now()->subDay(); + OrderFactory::new()->count(12)->create(['ordered_at' => $sharedTime]); + OrderFactory::new()->count(8)->create(); + + $page1 = $this->listOrders([], 10, 1); + $page2 = $this->listOrders([], 10, 2); + + $firstIds = $page1->getCollection()->pluck('id')->all(); + $secondIds = $page2->getCollection()->pluck('id')->all(); + + $this->assertCount(10, $firstIds); + $this->assertEmpty(array_intersect($firstIds, $secondIds), '페이지 간 중복 주문이 없어야 한다'); + $this->assertSame(20, $page1->total()); + } + + public function test_sort_by_falls_back_when_column_is_not_allowed(): void + { + OrderFactory::new()->count(3)->create(); + + // 예전에는 요청 값이 그대로 orderBy 로 흘러 SQL 오류가 났다 + $result = $this->listOrders(['sort_by' => 'admin_memo; drop table users', 'sort_order' => 'sideways']); + + $this->assertSame(3, $result->total()); + } + + public function test_filters_still_narrow_the_result(): void + { + OrderFactory::new()->count(4)->create(['order_device' => 'pc']); + OrderFactory::new()->count(3)->create(['order_device' => 'mobile']); + + $result = $this->listOrders(['order_device' => 'mobile']); + + $this->assertSame(3, $result->total()); + $this->assertCount(3, $result->getCollection()); + } + + public function test_soft_deleted_orders_stay_excluded(): void + { + $orders = OrderFactory::new()->count(5)->create(); + $orders->first()->delete(); + + $result = $this->listOrders(); + + $this->assertSame(4, $result->total()); + $this->assertNotContains($orders->first()->id, $result->getCollection()->pluck('id')->all()); + } + + public function test_admin_list_endpoint_still_returns_expected_payload(): void + { + OrderFactory::new()->count(3)->create(); + + $response = $this->actingAs($this->adminUser) + ->getJson('/api/modules/sirsoft-ecommerce/admin/orders'); + + $response->assertOk() + ->assertJsonStructure([ + 'data' => [ + 'data' => [ + '*' => ['id', 'order_number', 'order_status', 'total_amount', 'ordered_at'], + ], + 'pagination', + ], + ]); + + $this->assertCount(3, $response->json('data.data')); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserAddressUpdatePresenceValidationTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserAddressUpdatePresenceValidationTest.php new file mode 100644 index 00000000..19b9042e --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserAddressUpdatePresenceValidationTest.php @@ -0,0 +1,187 @@ +create(); + $address = UserAddress::factory()->create(['user_id' => $user->id]); + + $response = $this->actingAs($user) + ->putJson("{$this->baseUrl}/{$address->id}", ['is_default' => true]); + + $response->assertStatus(200); + } + + /** + * 국내 주소를 통째로 비우는 수정은 거부됩니다. + */ + public function test_update_blanking_domestic_address_fails(): void + { + $user = User::factory()->create(); + $address = UserAddress::factory()->create(['user_id' => $user->id]); + + $response = $this->actingAs($user) + ->putJson("{$this->baseUrl}/{$address->id}", [ + 'zipcode' => '', + 'address' => '', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['zipcode', 'address']); + } + + /** + * 우편번호만 비우는 수정은 거부됩니다. + */ + public function test_update_blanking_zipcode_only_fails(): void + { + $user = User::factory()->create(); + $address = UserAddress::factory()->create(['user_id' => $user->id]); + + $response = $this->actingAs($user) + ->putJson("{$this->baseUrl}/{$address->id}", ['zipcode' => '']); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['zipcode']); + } + + /** + * 도로명 주소만 바꾸는 수정은 기존 우편번호를 근거로 통과합니다. + */ + public function test_update_address_only_passes_with_existing_zipcode(): void + { + $user = User::factory()->create(); + $address = UserAddress::factory()->create([ + 'user_id' => $user->id, + 'zipcode' => '06236', + ]); + + $response = $this->actingAs($user) + ->putJson("{$this->baseUrl}/{$address->id}", [ + 'address' => '서울시 강남구 테헤란로 500', + ]); + + $response->assertStatus(200); + } + + /** + * 해외 배송지 수정 요청(조회 응답 형태 그대로 PUT)이 통과합니다. + * + * 배송지 조회 응답은 해외 주소를 city/state/postal_code 키로 내보내고 폼은 intl_* + * 키로 제출하므로, 해외 주소를 수정할 때 요청에 intl_city/intl_postal_code 가 없는 + * 경우가 정상 경로에 존재합니다. 페이로드만 보고 required_with 를 걸면 이 경로가 + * 전부 422 가 됩니다. + */ + public function test_update_overseas_address_without_intl_keys_passes(): void + { + $user = User::factory()->create(); + $address = UserAddress::factory()->create([ + 'user_id' => $user->id, + 'country_code' => 'US', + 'zipcode' => null, + 'address' => null, + 'address_detail' => null, + 'address_line_1' => '350 5th Ave', + 'address_line_2' => 'Suite 1000', + 'city' => 'New York', + 'state' => 'NY', + 'postal_code' => '10118', + ]); + + $response = $this->actingAs($user) + ->putJson("{$this->baseUrl}/{$address->id}", [ + 'name' => '뉴욕 사무실', + 'recipient_name' => 'John Doe', + 'recipient_phone' => '010-1234-5678', + 'country_code' => 'US', + 'address_line_1' => '350 5th Ave', + 'address_line_2' => 'Suite 2000', + ]); + + $response->assertStatus(200); + + // 전송되지 않은 해외 도시/주/우편번호는 저장된 값이 유지되어야 한다. + // (intl_* 미전송 시 null 로 덮어써져 조용히 유실되던 회귀 방지) + $this->assertDatabaseHas('ecommerce_user_addresses', [ + 'id' => $address->id, + 'city' => 'New York', + 'state' => 'NY', + 'postal_code' => '10118', + 'address_line_2' => 'Suite 2000', + ]); + } + + /** + * 국내 주소를 해외로 전환하면서 도시/우편번호를 빠뜨리면 거부됩니다. + */ + public function test_update_switching_to_overseas_without_city_fails(): void + { + $user = User::factory()->create(); + $address = UserAddress::factory()->create(['user_id' => $user->id]); + + $response = $this->actingAs($user) + ->putJson("{$this->baseUrl}/{$address->id}", [ + 'country_code' => 'US', + 'zipcode' => '', + 'address' => '', + 'address_line_1' => '350 5th Ave', + ]); + + $response->assertStatus(422) + ->assertJsonValidationErrors(['intl_city', 'intl_postal_code']); + } + + /** + * 해외 주소 3필드를 모두 채운 전환은 통과합니다. + */ + public function test_update_switching_to_overseas_with_intl_fields_passes(): void + { + $user = User::factory()->create(); + $address = UserAddress::factory()->create(['user_id' => $user->id]); + + $response = $this->actingAs($user) + ->putJson("{$this->baseUrl}/{$address->id}", [ + 'country_code' => 'US', + 'zipcode' => '', + 'address' => '', + 'address_line_1' => '350 5th Ave', + 'intl_city' => 'New York', + 'intl_state' => 'NY', + 'intl_postal_code' => '10118', + ]); + + $response->assertStatus(200); + + $this->assertDatabaseHas('ecommerce_user_addresses', [ + 'id' => $address->id, + 'country_code' => 'US', + 'city' => 'New York', + 'postal_code' => '10118', + ]); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/02_AddProductReviewSortIndexes.php b/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/02_AddProductReviewSortIndexes.php new file mode 100644 index 00000000..5a84ab61 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/02_AddProductReviewSortIndexes.php @@ -0,0 +1,78 @@ + 컬럼 목록) + * + * @var array> + */ + private const INDEXES = [ + 'idx_reviews_product_status_deleted_created' => ['product_id', 'status', 'deleted_at', 'created_at'], + // 관리자 목록: status 는 선택적 필터라 선행 컬럼으로 두면 필터 없는 기본 진입에서 + // created_at 정렬을 못 덮는다. 항상 IS NULL 로 붙는 deleted_at 을 선행에 둔다. + 'idx_reviews_deleted_created' => ['deleted_at', 'created_at'], + ]; + + /** + * 마이그레이션 식별자를 반환합니다. + * + * @return string 마이그레이션 이름 + */ + public function name(): string + { + return 'AddProductReviewSortIndexes'; + } + + /** + * 정렬 인덱스를 추가합니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + */ + public function run(UpgradeContext $context): void + { + if (! Schema::hasTable(self::TABLE)) { + $context->logger->info('[ecommerce:1.0.5] 상품 리뷰 테이블 부재 — 정렬 인덱스 추가 스킵'); + + return; + } + + $existing = array_column(Schema::getIndexes(self::TABLE), 'name'); + + foreach (self::INDEXES as $name => $columns) { + if (in_array($name, $existing, true)) { + $context->logger->info("[ecommerce:1.0.5] 이미 존재하는 인덱스 — 스킵: {$name}"); + + continue; + } + + $context->logger->info("[ecommerce:1.0.5] 정렬 인덱스 추가 시작: {$name} (리뷰가 많으면 수 분 걸릴 수 있고 그동안 리뷰 쓰기가 대기합니다)"); + + Schema::table(self::TABLE, function ($table) use ($columns, $name) { + $table->index($columns, $name); + }); + + $context->logger->info("[ecommerce:1.0.5] 정렬 인덱스 추가 완료: {$name}"); + } + } +} diff --git a/modules/_bundled/sirsoft-page/CHANGELOG.md b/modules/_bundled/sirsoft-page/CHANGELOG.md index 8d663bb7..f8ef96fa 100644 --- a/modules/_bundled/sirsoft-page/CHANGELOG.md +++ b/modules/_bundled/sirsoft-page/CHANGELOG.md @@ -18,6 +18,7 @@ ### Changed - 사이트맵을 만들 때 페이지를 한 번에 모두 메모리에 올리지 않고 나눠서 읽도록 바꿨습니다. 페이지가 많은 사이트에서 사이트맵 생성 중 메모리가 부족해 실패하던 문제가 줄어듭니다. +- 페이지 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 항목의 본문까지 함께 읽어 느려졌지만, 이제 현재 페이지의 항목만 본문을 읽습니다. 화면에 보이는 내용은 이전과 동일합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 개선된 사이트맵 생성 기능을 사용하기 위해 이 모듈은 이제 코어 7.0.6 이상이 필요합니다. - 첨부파일 저장 디스크 설정 항목을 제거했습니다. 설정 파일 자체가 그 디스크에 저장되는 구조라 값을 바꿔도 반영될 수 없는 항목이었습니다(기존에도 항상 기본값으로 동작). 저장 위치는 서버 파일 저장소 설정으로 관리합니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) diff --git a/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php b/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php index be5c37f5..053da489 100644 --- a/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php +++ b/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php @@ -4,6 +4,8 @@ namespace Modules\Sirsoft\Page\Repositories; use App\Helpers\PermissionHelper; use App\Repositories\Concerns\HasMultipleSearchFilters; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; use App\Search\Engines\DatabaseFulltextEngine; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -20,6 +22,11 @@ use Modules\Sirsoft\Page\Repositories\Contracts\PageRepositoryInterface; class PageRepository implements PageRepositoryInterface { use HasMultipleSearchFilters; + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + + /** 허용 정렬 컬럼 (PageListRequest 와 동일 집합) */ + private const SORTABLE_COLUMNS = ['created_at', 'published_at']; /** * 페이지 목록을 페이지네이션하여 조회합니다. @@ -39,15 +46,22 @@ class PageRepository implements PageRepositoryInterface // - slug : 슬러그만 // - all : 제목 + 슬러그 (본문 content 은 검색 대상 아님 — UI '제목 또는 슬러그로 검색') // (Scout 콜백에 slug orWhere 를 넣으면 total 카운트가 부풀려지는 회귀가 있어 #225 에서 제거됨) - $query = Page::with(['creator', 'updater']); + $query = Page::query(); // 권한 스코프 필터링 PermissionHelper::applyPermissionScope($query, 'sirsoft-page.pages.read'); $this->applyFilters($query, $filters); - $this->applySorting($query, $filters); - return $query->paginate($perPage); + // 지연 조인: 본문 content(longText)는 목록에 쓰이지 않는데도 OFFSET 이 훑는 + // 모든 행에서 함께 읽힌다. inner 는 id 만 훑고 본문은 이번 페이지에서만 읽는다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $this->resolveListSortSpec($filters), + perPage: $perPage, + relations: ['creator', 'updater'], + ); } /** @@ -170,6 +184,9 @@ class PageRepository implements PageRepositoryInterface { $results = $this->buildKeywordQuery($keyword) ->orderBy($orderBy, $direction) + // audit:allow repository-paginate-column-pruning reason: 통합검색 전용 진입점 — + // 유일한 호출자(SearchPagesListener)가 전체 탭은 limit=5, 페이지 탭은 limit=PHP_INT_MAX 로 + // 1페이지에 결과를 받아가므로 OFFSET 이 발생하지 않는다 ->paginate($limit); return [ @@ -224,14 +241,20 @@ class PageRepository implements PageRepositoryInterface */ private function applySorting($query, array $filters): void { - $sortBy = $filters['sort_by'] ?? 'created_at'; - $sortOrder = strtolower($filters['sort_order'] ?? 'desc'); - - if (! in_array($sortOrder, ['asc', 'desc'])) { - $sortOrder = 'desc'; + foreach ($this->resolveListSortSpec($filters) as $sort) { + $query->orderBy($sort['column'], $sort['direction']); } + } - $query->orderBy($sortBy, $sortOrder); + /** + * 목록 정렬 스펙을 허용 컬럼 화이트리스트로 해석합니다. + * + * @param array $filters 필터 조건 + * @return array 정렬 스펙 + */ + private function resolveListSortSpec(array $filters): array + { + return $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at'); } /** diff --git a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md index 4ff710e5..2d159329 100644 --- a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md @@ -4,7 +4,7 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. -## [1.0.2] - 2026-07-27 +## [1.0.2] - 2026-07-30 ### Added @@ -23,6 +23,9 @@ - 거부 시 안내 문구를 「동의하지 않고 계속합니다」로 바로잡았습니다 (이전에는 「동의가 저장되었습니다」로 잘못 표시). - 관리자 환경설정의 「쿠키 배너 노출」 안내 문구를 실제 동작에 맞게 바로잡았습니다 — 이 토글은 배너와 자동 차단만 제어하며, 마이페이지 「쿠키 동의 관리」 카드는 동의·철회 이력이 있는 회원에게 이 토글과 무관하게 항상 노출됩니다. - 사이트 언어를 바꾼 뒤 쿠키 동의를 저장하면 차단 해제·재차단이 즉시 반영되지 않던 문제를 수정했습니다. 페이지를 새로 고쳐야 반영되던 증상입니다. +- 관리자 「GDPR 동의 이력」 목록이 뒤쪽 페이지로 갈수록 느려지던 문제를 개선했습니다. 이력이 많이 쌓인 사이트에서 특히 체감됩니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 정책 버전 이력 목록에서 화면에 쓰지 않는 발행 시점 스냅샷 본문을 더 이상 함께 읽지 않습니다. 목록 조회가 가벼워집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 코어 최소 요구 버전을 7.0.6 으로 상향했습니다. 이 플러그인을 업데이트하기 전에 코어를 먼저 최신으로 업데이트하세요. ### Fixed diff --git a/plugins/_bundled/sirsoft-gdpr/plugin.json b/plugins/_bundled/sirsoft-gdpr/plugin.json index a21d784b..0c8f2fe3 100644 --- a/plugins/_bundled/sirsoft-gdpr/plugin.json +++ b/plugins/_bundled/sirsoft-gdpr/plugin.json @@ -11,7 +11,7 @@ "en": "GDPR-ready plugin: cookie consent banner, auto-blocking, consent history, and mypage consent withdrawal." }, "license": "MIT", - "g7_version": ">=7.0.5", + "g7_version": ">=7.0.6", "dependencies": { "modules": {}, "plugins": {} diff --git a/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprPolicyVersionRepository.php b/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprPolicyVersionRepository.php index 2b9533b9..78c04fcd 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprPolicyVersionRepository.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprPolicyVersionRepository.php @@ -28,7 +28,7 @@ class GdprPolicyVersionRepository implements GdprPolicyVersionRepositoryInterfac * * createdBy 관계를 eager load 하여 admin snapshot 모달의 발행자 표시에 활용. * - * @param int $version 조회할 정책 버전 정수 + * @param int $version 조회할 정책 버전 정수 * @return GdprPolicyVersion|null 해당 버전 row 가 없으면 null */ public function getByVersion(int $version): ?GdprPolicyVersion @@ -54,7 +54,7 @@ class GdprPolicyVersionRepository implements GdprPolicyVersionRepositoryInterfac /** * 관리자 화면용 페이지네이션 조회 (version DESC). * - * @param int $perPage 페이지당 행 수 (1~100 사이로 clamp) + * @param int $perPage 페이지당 행 수 (1~100 사이로 clamp) * @return LengthAwarePaginator */ public function paginate(int $perPage): LengthAwarePaginator @@ -62,13 +62,19 @@ class GdprPolicyVersionRepository implements GdprPolicyVersionRepositoryInterfac return GdprPolicyVersion::query() ->with('createdBy') ->orderByDesc('version') - ->paginate(max(1, min(100, $perPage))); + // audit:allow repository-paginate-column-pruning reason: 발행 이력은 정책 변경 시에만 + // 늘어나는 정의성 테이블이라 깊은 OFFSET 이 성립하지 않는다. 넓은 컬럼(snapshot JSON)은 + // 아래 컬럼 목록에서 제외했고, 본문은 상세 조회(getByVersion)에서만 읽는다. + ->paginate( + max(1, min(100, $perPage)), + ['id', 'version', 'change_type', 'memo', 'created_by', 'created_at'], + ); } /** * 새 정책 버전 행을 생성합니다. * - * @param array $data ['version', 'change_type', 'memo', 'snapshot', 'created_by'] + * @param array $data ['version', 'change_type', 'memo', 'snapshot', 'created_by'] * @return GdprPolicyVersion */ public function create(array $data): GdprPolicyVersion diff --git a/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprUserConsentHistoryRepository.php b/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprUserConsentHistoryRepository.php index 84efb4b8..04ad376a 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprUserConsentHistoryRepository.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprUserConsentHistoryRepository.php @@ -2,6 +2,7 @@ namespace Plugins\Sirsoft\Gdpr\Repositories; +use App\Repositories\Concerns\PaginatesWithDeferredJoin; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use Plugins\Sirsoft\Gdpr\Models\GdprUserConsentHistory; @@ -12,10 +13,31 @@ use Plugins\Sirsoft\Gdpr\Repositories\Contracts\GdprUserConsentHistoryRepository */ class GdprUserConsentHistoryRepository implements GdprUserConsentHistoryRepositoryInterface { + use PaginatesWithDeferredJoin; + + /** + * 관리자 목록이 실제로 표시하는 컬럼 (GdprConsentLogResource 와 1:1). + * + * @var array + */ + private const LIST_COLUMNS = [ + 'id', + 'user_id', + 'session_id', + 'consent_key', + 'action', + 'source', + 'policy_version', + 'categories', + 'ip_address', + 'user_agent', + 'created_at', + ]; + /** * 동의 이력 레코드를 생성합니다. * - * @param array $data 이력 데이터 + * @param array $data 이력 데이터 * @return GdprUserConsentHistory */ public function record(array $data): GdprUserConsentHistory @@ -32,10 +54,8 @@ class GdprUserConsentHistoryRepository implements GdprUserConsentHistoryReposito */ public function paginateForAdmin(array $filters, int $perPage): LengthAwarePaginator { - $query = GdprUserConsentHistory::query() - ->with('user') - ->orderByDesc('created_at') - ->orderByDesc('id'); + // 정렬/관계는 지연 조인이 담당한다 (inner 는 키 컬럼만 조회) + $query = GdprUserConsentHistory::query(); if (! empty($filters['email'])) { $email = (string) $filters['email']; @@ -63,13 +83,22 @@ class GdprUserConsentHistoryRepository implements GdprUserConsentHistoryReposito $query->whereIn('source', $filters['sources']); } - return $query->paginate(max(1, min(100, $perPage))); + // 동의 이력은 방문자의 동의 변경마다 한 행씩 쌓여 상한이 없다. 뒤쪽 페이지에서 + // OFFSET 이 커져도 넓은 컬럼(categories JSON / user_agent)을 읽는 행 수가 페이지 + // 크기로 고정되도록 지연 조인을 쓴다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: self::LIST_COLUMNS, + sort: [['column' => 'created_at', 'direction' => 'desc']], + perPage: max(1, min(100, $perPage)), + relations: ['user'], + ); } /** * 사용자 ID로 동의 이력을 조회합니다 (최신순). * - * @param int $userId 사용자 ID + * @param int $userId 사용자 ID * @return Collection */ public function getByUserId(int $userId): Collection @@ -83,7 +112,7 @@ class GdprUserConsentHistoryRepository implements GdprUserConsentHistoryReposito /** * 게스트 세션 ID로 동의 이력을 조회합니다 (최신순). * - * @param string $sessionId 게스트 세션 ID + * @param string $sessionId 게스트 세션 ID * @return Collection */ public function getBySessionId(string $sessionId): Collection @@ -97,7 +126,7 @@ class GdprUserConsentHistoryRepository implements GdprUserConsentHistoryReposito /** * 사용자 식별 정보를 NULL로 익명화합니다 (사용자 완전 삭제 시 감사 추적용). * - * @param int $userId 사용자 ID + * @param int $userId 사용자 ID * @return int 영향받은 행 수 */ public function anonymizeForUser(int $userId): int diff --git a/templates/_bundled/sirsoft-basic/CHANGELOG.md b/templates/_bundled/sirsoft-basic/CHANGELOG.md index bf69da51..e62aea59 100644 --- a/templates/_bundled/sirsoft-basic/CHANGELOG.md +++ b/templates/_bundled/sirsoft-basic/CHANGELOG.md @@ -37,6 +37,8 @@ - 장바구니에서 옵션을 변경할 때 옵션 목록이 아직 준비되지 않은 상태면 화면이 비어 보이던 문제를 수정했습니다. - 모달 창의 닫기 버튼 안내 라벨이 번역되지 않아 화면 낭독기에서 내부 식별자가 그대로 읽히던 문제를 수정했습니다. - 본인확인 코드 입력 화면에서 "남은 시도" 안내를 제거했습니다. 외부 인증 화면을 거쳐 돌아온 경우 실제보다 많은 횟수가 표시될 수 있어, 잘못된 숫자를 보여주는 대신 표시하지 않도록 했습니다. 시도 횟수 제한 자체는 종전대로 적용됩니다. +- 해외 배송지를 수정할 때 도시·주·우편번호 입력칸이 비어 보이고, 그대로 저장하면 저장돼 있던 값이 사라지던 문제를 수정했습니다. 마이페이지 배송지 관리와 주문서의 배송지 관리 모두에 적용됩니다. (#78 @glitter-gim 님께서 제보해주셨습니다.) +- 마이페이지 배송지 목록에서 해외 배송지가 주소 대신 "[]" 로만 표시되던 문제를 수정했습니다. 이제 국가에 맞는 전체 주소가 표시됩니다. (#78 @glitter-gim 님께서 제보해주셨습니다.) ### Changed diff --git a/templates/_bundled/sirsoft-basic/layouts/partials/mypage/addresses/_list.json b/templates/_bundled/sirsoft-basic/layouts/partials/mypage/addresses/_list.json index 0471ce95..01c3c182 100644 --- a/templates/_bundled/sirsoft-basic/layouts/partials/mypage/addresses/_list.json +++ b/templates/_bundled/sirsoft-basic/layouts/partials/mypage/addresses/_list.json @@ -32,7 +32,7 @@ } }, "children": [ - { "type": "basic", "name": "Icon", "props": { "name": "plus", "className": "w-4 h-4" } }, + { "type": "basic", "name": "Icon", "props": { "name": "plus", "className": "text-base" } }, { "type": "basic", "name": "Span", "text": "$t:mypage.addresses.add" } ], "actions": [ @@ -65,7 +65,10 @@ "type": "basic", "name": "Div", "iteration": { "source": "{{addresses?.data?.addresses?.data ?? []}}", "item_var": "address", "index_var": "addressIdx" }, - "props": { "className": "border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-white dark:bg-gray-800" }, + "props": { + "className": "border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-white dark:bg-gray-800", + "data-testid": "address-card" + }, "responsive": { "portable": { "props": { "className": "border border-gray-200 dark:border-gray-700 rounded-lg p-3 bg-white dark:bg-gray-800" } @@ -96,7 +99,7 @@ "if": "{{address.is_default}}", "props": { "className": "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded font-medium" }, "children": [ - { "type": "basic", "name": "Icon", "props": { "name": "star", "className": "w-3 h-3" } }, + { "type": "basic", "name": "Icon", "props": { "name": "star", "className": "text-xs" } }, { "type": "basic", "name": "Span", "text": "$t:mypage.addresses.default" } ] } @@ -123,7 +126,30 @@ "type": "click", "handler": "sequence", "actions": [ - { "handler": "setState", "params": { "target": "global", "editingAddress": "{{address}}" } }, + { + "comment": "조회 응답은 해외 주소를 city/state/postal_code 로 내려주고 폼은 intl_* 키로 제출한다. 원본 객체를 그대로 넣으면 해외 도시/주/우편번호 입력칸이 빈 값으로 보인다 (체크아웃 배송지 매핑과 동일 규칙).", + "handler": "setState", + "params": { + "target": "global", + "editingAddress": { + "id": "{{address.id}}", + "name": "{{address.name ?? ''}}", + "recipient_name": "{{address.recipient_name ?? ''}}", + "recipient_phone": "{{address.recipient_phone ?? ''}}", + "country_code": "{{address.country_code ?? 'KR'}}", + "zipcode": "{{address.zipcode ?? ''}}", + "address": "{{address.address ?? ''}}", + "address_detail": "{{address.address_detail ?? ''}}", + "city": "{{address.city ?? ''}}", + "address_line_1": "{{address.address_line_1 ?? ''}}", + "address_line_2": "{{address.address_line_2 ?? ''}}", + "intl_city": "{{address.city ?? ''}}", + "intl_state": "{{address.state ?? ''}}", + "intl_postal_code": "{{address.postal_code ?? ''}}", + "is_default": "{{address.is_default ?? false}}" + } + } + }, { "handler": "openModal", "target": "addressModal" } ] } @@ -162,11 +188,20 @@ "text": "{{address.recipient_name}} | {{address.recipient_phone}}" }, { - "comment": "주소", + "comment": "주소 (국내: [우편번호] 도로명 상세)", "type": "basic", "name": "P", + "if": "{{(address.country_code ?? 'KR') === 'KR'}}", "props": { "className": "text-sm text-gray-600 dark:text-gray-400 break-words" }, "text": "[{{address.zipcode}}] {{address.address}} {{address.address_detail}}" + }, + { + "comment": "주소 (해외: 서버가 조립한 full_address — 국내 전용 형식으로 찍으면 '[]' 만 보인다)", + "type": "basic", + "name": "P", + "if": "{{(address.country_code ?? 'KR') !== 'KR'}}", + "props": { "className": "text-sm text-gray-600 dark:text-gray-400 break-words" }, + "text": "{{address.full_address ?? ''}}" } ] } @@ -187,10 +222,10 @@ { "type": "basic", "name": "Icon", - "props": { "name": "map-pin", "className": "w-16 h-16 mx-auto text-gray-300 dark:text-gray-600 mb-4" }, + "props": { "name": "map-pin", "className": "text-6xl mx-auto text-gray-300 dark:text-gray-600 mb-4" }, "responsive": { "portable": { - "props": { "name": "map-pin", "className": "w-12 h-12 mx-auto text-gray-300 dark:text-gray-600 mb-3" } + "props": { "name": "map-pin", "className": "text-5xl mx-auto text-gray-300 dark:text-gray-600 mb-3" } } } }, @@ -205,7 +240,7 @@ "name": "Button", "props": { "className": "inline-flex items-center gap-2 px-4 py-2 bg-gray-900 dark:bg-white text-white dark:text-gray-900 rounded-lg text-sm font-medium hover:bg-gray-800 dark:hover:bg-gray-100 transition-colors" }, "children": [ - { "type": "basic", "name": "Icon", "props": { "name": "plus", "className": "w-4 h-4" } }, + { "type": "basic", "name": "Icon", "props": { "name": "plus", "className": "text-base" } }, { "type": "basic", "name": "Span", "text": "$t:mypage.addresses.add" } ], "actions": [ diff --git a/templates/_bundled/sirsoft-basic/layouts/partials/shop/_modal_address_manage.json b/templates/_bundled/sirsoft-basic/layouts/partials/shop/_modal_address_manage.json index b010d77c..8814fffd 100644 --- a/templates/_bundled/sirsoft-basic/layouts/partials/shop/_modal_address_manage.json +++ b/templates/_bundled/sirsoft-basic/layouts/partials/shop/_modal_address_manage.json @@ -270,7 +270,23 @@ "handler": "setState", "params": { "target": "local", - "editingAddress": "{{addr}}", + "editingAddress": { + "id": "{{addr.id}}", + "name": "{{addr.name ?? ''}}", + "recipient_name": "{{addr.recipient_name ?? ''}}", + "recipient_phone": "{{addr.recipient_phone ?? ''}}", + "country_code": "{{addr.country_code ?? 'KR'}}", + "zipcode": "{{addr.zipcode ?? ''}}", + "address": "{{addr.address ?? ''}}", + "address_detail": "{{addr.address_detail ?? ''}}", + "city": "{{addr.city ?? ''}}", + "address_line_1": "{{addr.address_line_1 ?? ''}}", + "address_line_2": "{{addr.address_line_2 ?? ''}}", + "intl_city": "{{addr.city ?? ''}}", + "intl_state": "{{addr.state ?? ''}}", + "intl_postal_code": "{{addr.postal_code ?? ''}}", + "is_default": "{{addr.is_default ?? false}}" + }, "addressModalMode": "form", "addressErrors": null } diff --git a/templates/_bundled/sirsoft-basic/src/__tests__/layouts/address-edit-intl-field-mapping.test.tsx b/templates/_bundled/sirsoft-basic/src/__tests__/layouts/address-edit-intl-field-mapping.test.tsx new file mode 100644 index 00000000..a47f460e --- /dev/null +++ b/templates/_bundled/sirsoft-basic/src/__tests__/layouts/address-edit-intl-field-mapping.test.tsx @@ -0,0 +1,112 @@ +/** + * @file address-edit-intl-field-mapping.test.tsx + * @description 배송지 수정 진입 시 해외 주소 필드가 폼 키(intl_*)로 매핑되는지 검증 + * + * 배경: 배송지 조회 응답은 해외 주소를 city/state/postal_code 로 내려주지만 주소 폼의 + * 입력칸 이름은 intl_city/intl_state/intl_postal_code 다. 수정 진입에서 조회 응답 + * 객체를 그대로 editingAddress 에 넣으면 해외 도시/주/우편번호 입력칸이 빈 값으로 + * 보이고, 그 상태로 저장하면 저장돼 있던 값이 함께 유실된다. + * 체크아웃 배송지 선택(_checkout_shipping.json)은 이미 같은 매핑을 하고 있었고, + * 마이페이지 목록과 체크아웃 배송지 관리 모달의 "수정" 진입만 누락돼 있었다. + * + * @vitest-environment jsdom + */ + +import { describe, it, expect } from 'vitest'; + +import mypageListPartial from '../../../layouts/partials/mypage/addresses/_list.json'; +import checkoutAddressModal from '../../../layouts/partials/shop/_modal_address_manage.json'; + +/** 레이아웃 트리를 순회하며 조건에 맞는 노드를 모두 수집한다 */ +function collectNodes(node: unknown, predicate: (value: Record) => boolean): Record[] { + const found: Record[] = []; + + const walk = (current: unknown): void => { + if (Array.isArray(current)) { + current.forEach(walk); + + return; + } + + if (current === null || typeof current !== 'object') { + return; + } + + const value = current as Record; + + if (predicate(value)) { + found.push(value); + } + + Object.values(value).forEach(walk); + }; + + walk(node); + + return found; +} + +/** editingAddress 를 객체로 설정하는 setState 액션을 찾는다 */ +function findEditingAddressAssignments(layout: unknown): Record[] { + return collectNodes(layout, (node) => { + if (node.handler !== 'setState') { + return false; + } + + const params = node.params as Record | undefined; + + return Boolean(params && typeof params.editingAddress === 'object' && params.editingAddress !== null); + }); +} + +/** + * 기존 배송지를 채워 넣는 "수정" 진입 액션만 추린다. + * + * 같은 레이아웃의 "새 배송지 추가" 액션도 editingAddress 를 객체로 설정하므로 + * (`{ is_default: false }`) 필드 매핑 검사 대상에서 제외해야 한다. + */ +function findEditAssignments(layout: unknown): Record[] { + return findEditingAddressAssignments(layout) + .map((assignment) => (assignment.params as Record).editingAddress as Record) + .filter((editingAddress) => typeof editingAddress.intl_city === 'string'); +} + +describe('배송지 수정 진입 — 해외 주소 필드 매핑', () => { + it('마이페이지 목록의 수정 액션이 intl_* 키를 city/state/postal_code 에서 채운다', () => { + const editAssignments = findEditAssignments(mypageListPartial); + + expect(editAssignments.length).toBeGreaterThan(0); + + const editingAddress = editAssignments[0]; + + expect(editingAddress.intl_city).toContain('address.city'); + expect(editingAddress.intl_state).toContain('address.state'); + expect(editingAddress.intl_postal_code).toContain('address.postal_code'); + }); + + it('마이페이지 목록의 수정 액션이 폼이 요구하는 나머지 키도 함께 채운다', () => { + const editingAddress = findEditAssignments(mypageListPartial)[0]; + + // id 가 없으면 저장이 POST(신규 등록)로 나가 수정이 등록으로 바뀐다 + expect(editingAddress.id).toContain('address.id'); + + ['name', 'recipient_name', 'recipient_phone', 'country_code', 'zipcode', 'address', 'address_detail', 'address_line_1', 'address_line_2'].forEach( + (key) => { + expect(editingAddress[key]).toBeDefined(); + } + ); + }); + + it('체크아웃 배송지 관리 모달의 수정 액션도 동일하게 매핑한다', () => { + const editAssignments = findEditAssignments(checkoutAddressModal); + + expect(editAssignments.length).toBeGreaterThan(0); + + const editingAddress = editAssignments[0]; + + expect(editingAddress.intl_city).toContain('addr.city'); + expect(editingAddress.intl_state).toContain('addr.state'); + expect(editingAddress.intl_postal_code).toContain('addr.postal_code'); + expect(editingAddress.id).toContain('addr.id'); + }); +}); diff --git a/templates/_bundled/sirsoft-basic/tests/Playwright/specs/mypage-address-overseas-edit.spec.ts b/templates/_bundled/sirsoft-basic/tests/Playwright/specs/mypage-address-overseas-edit.spec.ts new file mode 100644 index 00000000..ec1137da --- /dev/null +++ b/templates/_bundled/sirsoft-basic/tests/Playwright/specs/mypage-address-overseas-edit.spec.ts @@ -0,0 +1,158 @@ +/** + * 마이페이지 배송지 수정 — 해외 주소 필드 표시/보존 E2E. + * 템플릿 sirsoft-basic (유저 화면). + * + * @scenario address-overseas-edit + * @effects overseas_edit_form_prefills_intl_fields, + * overseas_edit_save_preserves_city_state_postal_code + * + * 배경: 배송지 조회 응답은 해외 주소를 city/state/postal_code 로 내려주지만 주소 폼의 + * 입력칸 이름은 intl_city/intl_state/intl_postal_code 다. 수정 진입에서 조회 응답 + * 객체를 그대로 editingAddress 에 넣던 탓에 해외 도시/주/우편번호 입력칸이 빈 값으로 + * 보였고, 그대로 저장하면 서버가 그 세 값을 null 로 덮어써 조용히 유실됐다. + * (백엔드는 MapsAddressBookFields 가 미전송 intl_* 를 덮어쓰지 않도록 함께 수정) + * + * 매트릭스: + * T1 해외 배송지 수정 진입 → intl_city/intl_state/intl_postal_code 입력칸에 저장값이 채워져 있다 + * T2 도시/주/우편번호를 건드리지 않고 저장 → 저장된 값이 그대로 유지된다 + * T3 목록 카드가 해외 주소를 국내 전용 형식('[] ')이 아니라 전체 주소로 표시한다 + * + * 라벨 매칭은 로케일 비의존으로 둔다 (사이트 기본 언어가 ko/en 어느 쪽이든 동작). + * + * 실행: + * $env:PLAYWRIGHT_BASE_URL='https://example.com' + * npx playwright test --config templates/_bundled/sirsoft-basic/tests/Playwright/playwright.config.ts mypage-address-overseas-edit + */ +import { test, expect, type APIRequestContext } from '@playwright/test'; +import { issueToken, authenticatePage } from '../../../../../../tests/Playwright/fixtures/auth'; + +const ADDRESS_API = '/api/modules/sirsoft-ecommerce/user/addresses'; + +/** 검증용 해외 배송지를 API 로 생성하고 ID 를 돌려준다 */ +async function createOverseasAddress(request: APIRequestContext, token: string, name: string): Promise { + const response = await request.post(ADDRESS_API, { + headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, + data: { + name, + recipient_name: 'John Doe', + recipient_phone: '010-1234-5678', + country_code: 'US', + address_line_1: '350 5th Ave', + address_line_2: 'Suite 1000', + intl_city: 'New York', + intl_state: 'NY', + intl_postal_code: '10118', + }, + }); + + expect(response.status(), await response.text()).toBe(201); + + return response.json().then((body) => body.data.address.id); +} + +/** 배송지 단건을 API 로 조회한다 */ +async function fetchAddress(request: APIRequestContext, token: string, id: number): Promise> { + const response = await request.get(`${ADDRESS_API}/${id}`, { + headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, + }); + + expect(response.ok(), await response.text()).toBe(true); + + const body = await response.json(); + + return body.data.address; +} + +/** 로케일 비의존 라벨 매처 */ +const EDIT_LABEL = /^(수정|Edit)$/; +const SAVE_LABEL = /^(저장|Save)$/; + +/** 배송지명으로 카드 전체를 집는다 (카드 루트에 data-testid="address-card") */ +function addressCard(page: import('@playwright/test').Page, addressName: string) { + return page.getByTestId('address-card').filter({ hasText: addressName }); +} + +/** GDPR 쿠키 배너가 떠 있으면 클릭을 가로채므로 먼저 닫는다 */ +async function dismissCookieNotice(page: import('@playwright/test').Page): Promise { + const necessaryOnly = page.getByRole('button', { name: /Necessary Only|필수만/ }); + + if (await necessaryOnly.isVisible().catch(() => false)) { + await necessaryOnly.click(); + } +} + +test.describe('마이페이지 해외 배송지 수정', () => { + let token: string; + + test.beforeAll(() => { + // 유저 화면 흐름이라 관리자 권한은 필요 없다 (인증만 필요). + token = issueToken('sirsoft-ecommerce.user.addresses.self'); + }); + + test('T1 수정 진입 시 해외 도시/주/우편번호 입력칸에 저장값이 채워진다', async ({ page, request }) => { + const addressName = `E2E 해외 ${Date.now()}`; + await createOverseasAddress(request, token, addressName); + + await authenticatePage(page, token); + await page.goto('/mypage/addresses'); + await dismissCookieNotice(page); + + const card = addressCard(page, addressName); + + await card.getByRole('button', { name: EDIT_LABEL }).click(); + + await expect(page.locator('input[name="intl_city"]')).toHaveValue('New York'); + await expect(page.locator('input[name="intl_state"]')).toHaveValue('NY'); + await expect(page.locator('input[name="intl_postal_code"]')).toHaveValue('10118'); + }); + + test('T2 도시/주/우편번호를 건드리지 않고 저장해도 값이 유지된다', async ({ page, request }) => { + const addressName = `E2E 해외 보존 ${Date.now()}`; + const addressId = await createOverseasAddress(request, token, addressName); + + await authenticatePage(page, token); + await page.goto('/mypage/addresses'); + await dismissCookieNotice(page); + + const card = addressCard(page, addressName); + + await card.getByRole('button', { name: EDIT_LABEL }).click(); + await expect(page.locator('input[name="intl_city"]')).toHaveValue('New York'); + + // 주소 2행만 바꾸고 저장 — 도시/주/우편번호는 손대지 않는다 + await page.locator('input[name="address_line_2"]').fill('Suite 2000'); + + const [response] = await Promise.all([ + page.waitForResponse((r) => r.url().includes(ADDRESS_API) && r.request().method() === 'PUT'), + page.getByRole('button', { name: SAVE_LABEL }).click(), + ]); + + expect(response.status()).toBe(200); + + const saved = await fetchAddress(request, token, addressId); + + expect(saved.city).toBe('New York'); + expect(saved.state).toBe('NY'); + expect(saved.postal_code).toBe('10118'); + expect(saved.address_line_2).toBe('Suite 2000'); + }); + + test('T3 목록 카드가 해외 주소를 전체 주소로 표시한다', async ({ page, request }) => { + const addressName = `E2E 해외 표시 ${Date.now()}`; + await createOverseasAddress(request, token, addressName); + + await authenticatePage(page, token); + await page.goto('/mypage/addresses'); + await dismissCookieNotice(page); + + // 클릭 단계가 없는 검사라 목록 하이드레이션 완료를 명시적으로 기다린다 + await expect(page.getByTestId('address-card').first()).toBeVisible({ timeout: 15000 }); + + const card = addressCard(page, addressName); + + await expect(card).toContainText('350 5th Ave'); + await expect(card).toContainText('New York'); + // 국내 전용 형식으로 찍히면 우편번호 자리만 남아 "[]" 가 노출된다 + await expect(card).not.toContainText('[]'); + }); +}); diff --git a/tests/Playwright/specs/pagination/deep-offset-list.spec.ts b/tests/Playwright/specs/pagination/deep-offset-list.spec.ts new file mode 100644 index 00000000..4c44a614 --- /dev/null +++ b/tests/Playwright/specs/pagination/deep-offset-list.spec.ts @@ -0,0 +1,235 @@ +/** + * E2E: 지연 조인 전환 후 목록 페이지네이션 종단 확인 + * + * 목록 조회가 "이번 페이지의 ID 를 먼저 구하고, 그 ID 에 대해서만 목록 컬럼과 관계를 읽는" + * 2단계로 바뀌었다. 단위 테스트는 쿼리 구조와 ID 시퀀스를 잠그지만, 실제 화면에서 드러나는 + * 증상(마지막 페이지에서 "다음" 이 계속 눌리거나, 페이지 이동 후 목록이 비어 보이거나, + * 콘솔에 렌더 에러가 남는 것)은 브라우저 레벨에서만 잡힌다. + * + * 계획서의 브라우저 확인 항목을 화면별로 잠근다: + * (1) 목록이 렌더된다 (관계/컬럼 프루닝으로 화면이 비지 않는다) + * (2) 마지막 페이지에서 "다음" 이 비활성이다 (총 건수 계산 정합) + * (3) 콘솔/페이지 에러가 없다 + * + * 데이터 의존 스킵: 목록이 한 페이지 분량이면 페이지네이션 컨트롤이 없으므로 (2)를 건너뛴다. + * 스킵 사유는 실행 로그에 남는다 — 조용한 통과가 아니다. + * + * @scenario case=deep_offset_list_browser + * + * @effects pages_do_not_overlap_or_drop_rows, simple_mode_reports_last_page_without_next + */ +import type { Page } from '@playwright/test'; +import { test, expect, authenticatePage, issueToken } from '../../fixtures/auth'; + +/** 점검 대상 화면 — 계획서가 지목한 4개 목록 */ +const LIST_SCREENS = [ + { name: '활동 로그', path: '/admin/activity-logs', permissions: ['core.activities.read'] }, + { name: '알림 발송 이력', path: '/admin/notification-logs', permissions: ['core.notification-logs.read'] }, + { name: '관리자 주문 목록', path: '/admin/ecommerce/orders', permissions: ['sirsoft-ecommerce.orders.read'] }, + { + name: '게시판 게시글 목록', + path: '/admin/board/notice', + permissions: ['sirsoft-board.notice.admin.posts.read', 'sirsoft-board.notice.admin.manage'], + }, +] as const; + +/** + * 관리자 화면 진입 공통 대기 (기존 admin spec 과 동일 패턴). + * + * `networkidle` 은 폴링/실시간 연결이 있는 화면에서 영영 idle 이 되지 않아 쓰지 않는다. + * + * @param page 대상 페이지 + * @param path 이동할 경로 + */ +async function gotoAdmin(page: Page, path: string): Promise { + await page.goto(path); + await page.waitForLoadState('domcontentloaded', { timeout: 30_000 }); + await page.waitForFunction( + () => !window.location.pathname.includes('/admin/login') || document.readyState === 'complete', + { timeout: 15_000 }, + ); +} + +/** + * 페이지에서 수집한 콘솔/페이지 에러를 담을 배열을 부착합니다. + * + * @param page 대상 페이지 + * @returns 수집된 에러 메시지 배열 (테스트 진행에 따라 채워진다) + */ +function collectPageErrors(page: Page): string[] { + const errors: string[] = []; + + page.on('console', (message) => { + if (message.type() === 'error') { + errors.push(message.text()); + } + }); + page.on('pageerror', (error) => { + errors.push(error.message); + }); + + return errors; +} + +/** + * 목록 데이터가 도착할 때까지 기다립니다. + * + * 진입 직후에는 목록이 비어 있어 페이지네이션이 아직 렌더되지 않는다. 데이터 로드를 + * 기다리지 않고 컨트롤 개수를 세면 "컨트롤 없음" 으로 잘못 판정해 조용히 스킵된다. + * + * @param page 대상 페이지 + * @returns 행이 하나라도 렌더되면 true + */ +async function waitForListRows(page: Page): Promise { + return page + .locator('tbody tr, [data-testid$="-row"]') + .first() + .waitFor({ state: 'attached', timeout: 20_000 }) + .then(() => true) + .catch(() => false); +} + +/** + * 페이지네이션의 "다음" 컨트롤을 찾습니다. + * + * `aria-label` 은 앱 로케일에 따라 한국어("다음 페이지")로도, 언어팩이 아직 붙지 않은 + * 시점에는 기본 영문("Next page")으로도 렌더된다. 한쪽만 보면 컨트롤이 있는데도 + * "없음" 으로 판정해 조용히 스킵되므로 양쪽을 모두 매칭한다. + * + * @param page 대상 페이지 + * @returns 다음 페이지 컨트롤 locator + */ +function nextPageControl(page: Page) { + return page + .locator( + [ + '[aria-label="다음 페이지"]', + '[aria-label="Next page"]', + '[data-testid="pagination-next"]', + 'button:has-text("다음")', + ].join(', '), + ) + .first(); +} + +/** + * 페이지네이션의 "마지막 페이지" 컨트롤을 찾습니다. + * + * @param page 대상 페이지 + * @returns 마지막 페이지 컨트롤 locator + */ +function lastPageControl(page: Page) { + return page.locator('[aria-label="마지막 페이지"], [aria-label="Last page"]').first(); +} + +/** + * "다음" 컨트롤이 비활성인지 판정합니다. + * + * @param page 대상 페이지 + * @returns 비활성이면 true + */ +async function isNextDisabled(page: Page): Promise { + const next = nextPageControl(page); + + if ((await next.count()) === 0) { + return true; + } + + if (await next.isDisabled().catch(() => false)) { + return true; + } + + return (await next.getAttribute('aria-disabled')) === 'true'; +} + +test.describe('목록 페이지네이션 (지연 조인)', () => { + for (const screen of LIST_SCREENS) { + // @scenario case=deep_offset_list_browser + // @effects pages_do_not_overlap_or_drop_rows + test(`${screen.name} — 목록이 렌더되고 페이지 에러가 없다`, async ({ page }) => { + const errors = collectPageErrors(page); + + await authenticatePage(page, issueToken(...screen.permissions)); + await gotoAdmin(page, screen.path); + + if (/\/admin\/login/.test(page.url())) { + test.skip(true, `${screen.name} 접근 불가 — 확장 미설치 또는 권한 미부여`); + + return; + } + + // 목록 컨테이너가 떠야 한다 — 렌더 자체가 깨지면 여기서 잡힌다. + await expect( + page.locator('table, [role="table"], [data-testid$="-list"]').first(), + ).toBeAttached({ timeout: 20_000 }); + + await waitForListRows(page); + + expect(errors, `페이지 에러: ${errors.join(' | ')}`).toHaveLength(0); + }); + + // @scenario case=deep_offset_list_browser + // @effects simple_mode_reports_last_page_without_next + test(`${screen.name} — 마지막 페이지에서 "다음" 이 비활성이다`, async ({ page }) => { + await authenticatePage(page, issueToken(...screen.permissions)); + await gotoAdmin(page, screen.path); + + if (/\/admin\/login/.test(page.url())) { + test.skip(true, `${screen.name} 접근 불가 — 확장 미설치 또는 권한 미부여`); + + return; + } + + if (! (await waitForListRows(page))) { + test.skip(true, `${screen.name} 목록 행 없음 — 데이터 미존재`); + + return; + } + + const next = nextPageControl(page); + + // 목록 행과 페이지네이션은 렌더 시점이 다르다. 행이 떴다고 바로 세면 아직 없는 + // 컨트롤을 "없음" 으로 오판해 조용히 스킵된다 — 컨트롤이 붙을 때까지 기다린다. + const hasControl = await next + .waitFor({ state: 'attached', timeout: 15_000 }) + .then(() => true) + .catch(() => false); + + if (! hasControl) { + test.skip(true, `${screen.name} 페이지네이션 컨트롤 없음 — 데이터가 한 페이지 분량`); + + return; + } + + // 마지막 페이지로 한 번에 이동한다. 컨트롤이 없는 템플릿이면 "다음" 을 반복 클릭한다. + // 이미 마지막 페이지면(한 페이지 분량) 이동 없이 곧바로 판정한다 — 비활성 버튼을 + // 클릭하려 들면 Playwright 가 enabled 를 기다리다 타임아웃한다. + const last = lastPageControl(page); + const alreadyLast = await isNextDisabled(page); + + if (alreadyLast) { + // 이동 불필요 + } else if ((await last.count()) > 0 && (await last.isEnabled().catch(() => false))) { + await last.click(); + } else { + for (let hop = 0; hop < 10; hop += 1) { + if (await isNextDisabled(page)) { + break; + } + + await next.click(); + await page.waitForTimeout(1_000); + } + } + + // 고정 대기 대신 상태가 바뀔 때까지 폴링한다 — 병렬 실행에서 서버 응답이 늦어도 + // 결과 판정이 흔들리지 않는다. + await expect + .poll(() => isNextDisabled(page), { + message: '마지막 페이지에서 "다음" 이 계속 활성이면 총 건수 계산이 어긋난 것이다', + timeout: 20_000, + }) + .toBe(true); + }); + } +}); diff --git a/tests/Unit/Repositories/Concerns/DeferredJoinCallSiteContractTest.php b/tests/Unit/Repositories/Concerns/DeferredJoinCallSiteContractTest.php new file mode 100644 index 00000000..31b2d4e5 --- /dev/null +++ b/tests/Unit/Repositories/Concerns/DeferredJoinCallSiteContractTest.php @@ -0,0 +1,161 @@ + [파일, 줄번호, 인자 텍스트] + */ + public static function callSiteProvider(): array + { + // tests/Unit/Repositories/Concerns → 저장소 루트 + $root = dirname(__DIR__, 4); + $cases = []; + + foreach (self::SCAN_ROOTS as $scanRoot) { + $base = $root.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $scanRoot); + + if (! is_dir($base)) { + continue; + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($base, \FilesystemIterator::SKIP_DOTS) + ); + + foreach ($iterator as $file) { + if (! $file->isFile() || $file->getExtension() !== 'php') { + continue; + } + + $path = str_replace('\\', '/', $file->getPathname()); + + // 트레이트 본체와 테스트 코드는 호출처가 아니다 + if (str_contains($path, '/tests/') || str_contains($path, '/Concerns/PaginatesWithDeferredJoin.php')) { + continue; + } + + $content = file_get_contents($path); + + if (! str_contains($content, 'paginateWithDeferredJoin(')) { + continue; + } + + foreach (self::extractCallSites($content) as [$line, $args]) { + $relative = substr($path, strlen(str_replace('\\', '/', $root)) + 1); + $cases["{$relative}:{$line}"] = [$relative, $line, $args]; + } + } + } + + return $cases; + } + + /** + * 파일 내용에서 호출처의 인자 텍스트를 추출합니다. + * + * @param string $content 파일 내용 + * @return array [줄번호, 인자 텍스트] + */ + private static function extractCallSites(string $content): array + { + $sites = []; + $offset = 0; + + while (($pos = strpos($content, 'paginateWithDeferredJoin(', $offset)) !== false) { + $open = $pos + strlen('paginateWithDeferredJoin(') - 1; + $depth = 0; + $end = null; + + for ($i = $open; $i < strlen($content); $i++) { + $ch = $content[$i]; + + if ($ch === '(' || $ch === '[') { + $depth++; + } elseif ($ch === ')' || $ch === ']') { + $depth--; + + if ($depth === 0) { + $end = $i; + break; + } + } + } + + if ($end === null) { + break; + } + + $line = substr_count(substr($content, 0, $pos), "\n") + 1; + $sites[] = [$line, substr($content, $open + 1, $end - $open - 1)]; + $offset = $end; + } + + return $sites; + } + + #[DataProvider('callSiteProvider')] + public function test_call_site_declares_columns(string $file, int $line, string $args): void + { + $this->assertMatchesRegularExpression( + '/\bcolumns\s*:/', + $args, + "{$file}:{$line} — columns: 를 명시해야 한다 (미지정 시 outer 가 읽는 범위가 호출처에서 보이지 않는다)" + ); + + $this->assertDoesNotMatchRegularExpression( + '/\bcolumns\s*:\s*\[\s*\]/', + $args, + "{$file}:{$line} — 빈 컬럼 목록은 어떤 컬럼도 선택하지 않는다" + ); + } + + #[DataProvider('callSiteProvider')] + public function test_call_site_declares_sort_spec(string $file, int $line, string $args): void + { + $this->assertMatchesRegularExpression( + '/\bsort\s*:/', + $args, + "{$file}:{$line} — sort: 를 명시해야 한다 (미지정 시 키 컬럼 정렬만 남는다)" + ); + + $this->assertDoesNotMatchRegularExpression( + '/\bsort\s*:\s*\[\s*\]/', + $args, + "{$file}:{$line} — 빈 정렬 스펙은 화면 정렬을 잃는다" + ); + } + + public function test_scan_finds_call_sites(): void + { + // 스캔이 0건이면 위 두 테스트가 조용히 통과해 계약 검사가 무력화된다 + $this->assertNotEmpty(self::callSiteProvider(), '지연 조인 호출처를 하나도 찾지 못했다 — 스캔 경로를 확인할 것'); + } +} diff --git a/tests/Unit/Repositories/Concerns/PaginatesWithDeferredJoinTest.php b/tests/Unit/Repositories/Concerns/PaginatesWithDeferredJoinTest.php new file mode 100644 index 00000000..62ee9f0f --- /dev/null +++ b/tests/Unit/Repositories/Concerns/PaginatesWithDeferredJoinTest.php @@ -0,0 +1,690 @@ +id(); + $table->unsignedBigInteger('group_id')->default(1); + $table->string('title', 100); + $table->unsignedInteger('view_count')->default(0); + $table->longText('body')->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + + Schema::create('deferred_join_sample_comments', function ($table) { + $table->id(); + $table->unsignedBigInteger('sample_id'); + $table->string('content', 100); + $table->timestamps(); + }); + } + + protected function tearDown(): void + { + Schema::dropIfExists('deferred_join_sample_comments'); + Schema::dropIfExists('deferred_join_samples'); + + parent::tearDown(); + } + + /** + * 샘플 행을 생성합니다. + * + * @param int $count 생성 건수 + * @param int $viewCount 조회수 (동률 tie-break 검증용 고정값) + */ + private function seedSamples(int $count, int $viewCount = 0): void + { + $rows = []; + + for ($i = 1; $i <= $count; $i++) { + $rows[] = [ + 'group_id' => 1, + 'title' => "제목 {$i}", + 'view_count' => $viewCount, + 'body' => str_repeat('본문 ', 200), + 'created_at' => now(), + 'updated_at' => now(), + ]; + } + + DB::table('deferred_join_samples')->insert($rows); + } + + /** 쿼리 로그 수집을 시작합니다. */ + private function startCapture(): void + { + $this->queries = []; + + DB::listen(function ($query) { + $this->queries[] = $query->sql; + }); + } + + /** + * 수집된 쿼리 중 조건에 맞는 것만 돌려줍니다. + * + * @param string $needle 포함 문자열 + * @return array 일치 쿼리 목록 + */ + private function queriesContaining(string $needle): array + { + return array_values(array_filter($this->queries, fn (string $sql) => str_contains($sql, $needle))); + } + + /** 테스트용 리포지토리를 생성합니다. */ + private function repository(): object + { + return new class + { + use PaginatesWithDeferredJoin; + + /** + * 지연 조인 페이지네이션을 그대로 노출합니다. (Trait 메서드는 protected) + * + * @param Builder $query 대상 쿼리 + * @param array $args 나머지 인자 + */ + public function paginate(Builder $query, array $columns, array $sort, int $perPage, ...$args) + { + return $this->paginateWithDeferredJoin($query, $columns, $sort, $perPage, ...$args); + } + }; + } + + /** + * @scale n=200000 asserts=id_only_inner_query + */ + public function test_inner_query_selects_only_key_column_without_wide_columns(): void + { + $this->seedSamples(30); + $this->startCapture(); + + $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 2, + ); + + $offsetQueries = $this->queriesContaining('offset'); + + $this->assertCount(1, $offsetQueries, 'OFFSET 스캔은 정확히 한 번(inner)만 일어나야 한다'); + // 테이블 프리픽스는 환경 설정값이므로 패턴으로 판정한다 + $this->assertMatchesRegularExpression('/^select `[^`]*deferred_join_samples`\.`id` from/', $offsetQueries[0]); + $this->assertStringNotContainsString('body', $offsetQueries[0], 'inner 는 넓은 컬럼을 읽지 않아야 한다'); + $this->assertStringNotContainsString('title', $offsetQueries[0]); + } + + /** + * @scale n=200000 asserts=no_wide_column_in_offset_scan + */ + public function test_outer_query_filters_by_id_set_without_offset(): void + { + $this->seedSamples(30); + $this->startCapture(); + + $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 2, + ); + + $outer = $this->queriesContaining('`id` in ('); + + $this->assertNotEmpty($outer, 'outer 는 ID 집합으로 조회해야 한다'); + $this->assertStringNotContainsString('offset', $outer[0], 'outer 에는 OFFSET 이 없어야 한다'); + } + + public function test_relations_are_not_eager_loaded_for_inner_query(): void + { + $this->seedSamples(5); + DB::table('deferred_join_sample_comments')->insert([ + ['sample_id' => 1, 'content' => '댓글', 'created_at' => now(), 'updated_at' => now()], + ]); + + $this->startCapture(); + + $result = $this->repository()->paginate( + DeferredJoinSample::query()->with('comments'), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 1, + ['comments'], + ); + + $commentQueries = $this->queriesContaining('deferred_join_sample_comments'); + + $this->assertCount(1, $commentQueries, '관계 쿼리는 outer 에서 한 번만 실행되어야 한다'); + $this->assertTrue($result->getCollection()->first()->relationLoaded('comments')); + } + + public function test_count_query_is_skipped_when_total_is_injected(): void + { + $this->seedSamples(30); + $this->startCapture(); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 1, + [], + [], + 'id', + 30, + ); + + $this->assertEmpty($this->queriesContaining('count(*)'), '주입된 total 이 있으면 COUNT 를 실행하지 않아야 한다'); + $this->assertInstanceOf(LengthAwarePaginator::class, $result); + $this->assertSame(30, $result->total()); + } + + public function test_simple_mode_skips_count_and_detects_next_page(): void + { + $this->seedSamples(25); + $this->startCapture(); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 1, + [], + [], + 'id', + null, + true, + ); + + $this->assertEmpty($this->queriesContaining('count(*)')); + $this->assertInstanceOf(Paginator::class, $result); + $this->assertCount(10, $result->items(), 'perPage + 1 건을 읽어도 페이지 항목은 perPage 건이어야 한다'); + $this->assertTrue($result->hasMorePages()); + } + + public function test_simple_mode_reports_last_page_correctly(): void + { + $this->seedSamples(15); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 2, + [], + [], + 'id', + null, + true, + ); + + $this->assertCount(5, $result->items()); + $this->assertFalse($result->hasMorePages(), '마지막 페이지에서 다음 페이지가 있다고 보고하면 안 된다'); + } + + public function test_tie_breaking_key_is_appended_to_sort_spec(): void + { + // 모든 행의 view_count 가 동일 — 키 컬럼 tie-break 가 없으면 페이지 경계가 흔들린다 + $this->seedSamples(20, viewCount: 7); + + $page1 = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'view_count'], + [['column' => 'view_count', 'direction' => 'desc']], + 10, + 1, + ); + $page2 = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'view_count'], + [['column' => 'view_count', 'direction' => 'desc']], + 10, + 2, + ); + + $firstIds = $page1->getCollection()->pluck('id')->all(); + $secondIds = $page2->getCollection()->pluck('id')->all(); + + $this->assertSame([20, 19, 18, 17, 16, 15, 14, 13, 12, 11], $firstIds); + $this->assertSame([10, 9, 8, 7, 6, 5, 4, 3, 2, 1], $secondIds); + $this->assertEmpty(array_intersect($firstIds, $secondIds), '페이지 간 중복 행이 없어야 한다'); + } + + public function test_outer_result_order_matches_inner_order(): void + { + $this->seedSamples(12); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'title', 'direction' => 'asc']], + 5, + 1, + ); + + $ids = $result->getCollection()->pluck('id')->all(); + $sorted = $result->getCollection()->sortBy('title')->pluck('id')->all(); + + $this->assertSame(array_values($sorted), $ids, 'outer 결과 순서가 정렬 스펙과 일치해야 한다'); + } + + public function test_soft_deleted_rows_stay_excluded(): void + { + $this->seedSamples(10); + DeferredJoinSample::query()->whereIn('id', [3, 7])->delete(); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 20, + 1, + ); + + $ids = $result->getCollection()->pluck('id')->all(); + + $this->assertNotContains(3, $ids); + $this->assertNotContains(7, $ids); + $this->assertSame(8, $result->total()); + } + + public function test_with_trashed_scope_is_preserved(): void + { + $this->seedSamples(10); + DeferredJoinSample::query()->whereIn('id', [3, 7])->delete(); + + $result = $this->repository()->paginate( + DeferredJoinSample::query()->withTrashed(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 20, + 1, + ); + + $this->assertSame(10, $result->total()); + $this->assertContains(3, $result->getCollection()->pluck('id')->all()); + } + + public function test_filters_applied_to_query_are_preserved(): void + { + $this->seedSamples(10); + DB::table('deferred_join_samples')->whereIn('id', [2, 4, 6])->update(['group_id' => 9]); + + $result = $this->repository()->paginate( + DeferredJoinSample::query()->where('group_id', 9), + ['id', 'group_id'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 1, + ); + + $this->assertSame([2, 4, 6], $result->getCollection()->pluck('id')->all()); + $this->assertSame(3, $result->total()); + } + + public function test_preserve_id_order_restores_inner_sequence(): void + { + $this->seedSamples(6); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'desc']], + 3, + 1, + [], + [], + 'id', + null, + false, + true, + ); + + $this->assertSame([6, 5, 4], $result->getCollection()->pluck('id')->all()); + } + + public function test_with_count_is_applied_on_outer_only(): void + { + $this->seedSamples(3); + DB::table('deferred_join_sample_comments')->insert([ + ['sample_id' => 1, 'content' => 'a', 'created_at' => now(), 'updated_at' => now()], + ['sample_id' => 1, 'content' => 'b', 'created_at' => now(), 'updated_at' => now()], + ]); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 1, + [], + ['comments'], + ); + + $this->assertSame(2, (int) $result->getCollection()->firstWhere('id', 1)->comments_count); + } + + public function test_grouped_query_total_counts_groups_not_first_group_rows(): void + { + // 조인으로 행이 불어난 뒤 groupBy 로 접히는 형태 — 조인 행 7건, 그룹 5건 + $this->seedSamples(5); + DB::table('deferred_join_sample_comments')->insert([ + ['sample_id' => 1, 'content' => 'a', 'created_at' => now(), 'updated_at' => now()], + ['sample_id' => 1, 'content' => 'b', 'created_at' => now(), 'updated_at' => now()], + ['sample_id' => 1, 'content' => 'c', 'created_at' => now(), 'updated_at' => now()], + ['sample_id' => 2, 'content' => 'd', 'created_at' => now(), 'updated_at' => now()], + ['sample_id' => 3, 'content' => 'e', 'created_at' => now(), 'updated_at' => now()], + ['sample_id' => 4, 'content' => 'f', 'created_at' => now(), 'updated_at' => now()], + ['sample_id' => 5, 'content' => 'g', 'created_at' => now(), 'updated_at' => now()], + ]); + + $result = $this->repository()->paginate( + DeferredJoinSample::query() + ->join('deferred_join_sample_comments as c', 'c.sample_id', '=', 'deferred_join_samples.id') + ->groupBy('deferred_join_samples.id'), + ['deferred_join_samples.id'], + [['column' => 'deferred_join_samples.id', 'direction' => 'asc']], + 10, + 1, + ); + + // 집계 쿼리를 서브쿼리로 감싸지 않으면 첫 그룹의 행 수(3)가 총 건수로 잡힌다 + $this->assertSame(5, $result->total(), '그룹 쿼리의 총 건수는 그룹 수여야 한다'); + $this->assertCount(5, $result->items()); + $this->assertSame(1, $result->lastPage()); + } + + public function test_grouped_query_paginates_across_pages_with_correct_total(): void + { + $this->seedSamples(12); + + $rows = []; + for ($sampleId = 1; $sampleId <= 12; $sampleId++) { + // 1번 샘플만 댓글 5건 — 첫 그룹 행 수와 그룹 수를 다르게 만든다 + $repeat = $sampleId === 1 ? 5 : 1; + for ($i = 0; $i < $repeat; $i++) { + $rows[] = ['sample_id' => $sampleId, 'content' => "c{$sampleId}-{$i}", 'created_at' => now(), 'updated_at' => now()]; + } + } + DB::table('deferred_join_sample_comments')->insert($rows); + + $grouped = fn () => DeferredJoinSample::query() + ->join('deferred_join_sample_comments as c', 'c.sample_id', '=', 'deferred_join_samples.id') + ->groupBy('deferred_join_samples.id'); + + $page2 = $this->repository()->paginate( + $grouped(), + ['deferred_join_samples.id'], + [['column' => 'deferred_join_samples.id', 'direction' => 'asc']], + 5, + 2, + ); + + $this->assertSame(12, $page2->total()); + $this->assertSame(3, $page2->lastPage()); + $this->assertSame([6, 7, 8, 9, 10], $page2->getCollection()->pluck('id')->all()); + } + + public function test_returned_paginator_supports_query_string_appending(): void + { + // 필터 조건을 페이지 링크에 보존하는 책임은 호출자의 appends()/withQueryString() 에 있다. + // 지연 조인 전환 후에도 그 계약이 유지되는지 확인한다 (Laravel paginate() 도 동일). + $this->seedSamples(30); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 1, + ); + + $url = $result->appends(['search' => '검색어', 'sort' => 'latest'])->nextPageUrl(); + + $this->assertStringContainsString('search=', (string) $url); + $this->assertStringContainsString('sort=latest', (string) $url); + $this->assertStringContainsString('page=2', (string) $url); + } + + public function test_custom_page_name_is_reflected_in_paginator_links(): void + { + $this->seedSamples(30); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 1, + [], + [], + 'id', + null, + false, + false, + 'p', + ); + + $this->assertStringContainsString('p=2', (string) $result->nextPageUrl()); + } + + /** + * 깊은 OFFSET 의 비용 증가가 유계인지 확인합니다. + * + * 절대 시간 임계값(`assertLessThan(50, $ms)`)은 CI 머신 성능에 좌우되므로 쓰지 않는다. + * 대신 **같은 머신 안에서의 비율**만 단언한다 — 뒤쪽 페이지 소요 시간이 첫 페이지의 몇 배인가. + * 지연 조인 이전에는 이 비율이 20배를 넘었고(OFFSET 이 커질수록 넓은 컬럼을 그만큼 더 읽는다), + * 이후에는 넓은 컬럼을 읽는 행 수가 페이지 크기로 고정되므로 배수가 상수에 가깝게 붙는다. + * 상한 5배는 실측(21.6배 → ~2배)에 여유를 둔 값이다. + * + * @scale n=200000 asserts=deep_offset_bounded_growth + */ + public function test_deep_offset_cost_growth_is_bounded(): void + { + $rows = 20000; + $perPage = 20; + + // 넓은 컬럼(longText)이 있어야 OFFSET 구간의 읽기 비용 차이가 드러난다 + $body = str_repeat('본문 ', 250); + $now = now(); + + for ($chunk = 0; $chunk < $rows / 1000; $chunk++) { + $batch = []; + + for ($i = 1; $i <= 1000; $i++) { + $batch[] = [ + 'group_id' => 1, + 'title' => '제목 '.($chunk * 1000 + $i), + 'view_count' => 0, + 'body' => $body, + 'created_at' => $now, + 'updated_at' => $now, + ]; + } + + DB::table('deferred_join_samples')->insert($batch); + } + + $measure = function (int $page) use ($perPage): float { + $run = fn () => $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title', 'body'], + [['column' => 'id', 'direction' => 'asc']], + $perPage, + $page, + ); + + $run(); // 버퍼 풀 워밍 — 첫 회는 버린다 + + $samples = []; + + for ($i = 0; $i < 5; $i++) { + $start = microtime(true); + $run(); + $samples[] = (microtime(true) - $start) * 1000; + } + + sort($samples); + + return $samples[2]; // 중앙값 + }; + + $firstPage = $measure(1); + $lastPage = $measure((int) ($rows / $perPage)); + + $this->assertGreaterThan(0.0, $firstPage, '측정값이 0 이면 비율 판정이 무의미하다'); + + $ratio = $lastPage / $firstPage; + + $this->assertLessThanOrEqual( + 5.0, + $ratio, + sprintf( + '마지막 페이지가 첫 페이지의 %.1f배 — 깊은 OFFSET 비용이 유계가 아니다 (첫 %.2fms / 마지막 %.2fms)', + $ratio, + $firstPage, + $lastPage + ) + ); + } + + /** + * 지연 조인 결과가 기존 `paginate()` 와 **같은 ID 시퀀스**인지 확인합니다. + * + * 정적 골든 fixture 를 파일로 떠 두면 기대값을 사람이 다시 만들어야 해서, 정렬 규칙이 + * 바뀔 때 fixture 를 함께 고쳐 회귀를 덮어버리기 쉽다. 여기서는 **교체 전 경로 자체** + * (`Builder::paginate`)를 오라클로 두고 매 실행마다 기대값을 다시 얻는다. 순서까지 + * 비교하므로 tie-break 회귀도 잡힌다. + */ + public function test_id_sequence_matches_plain_paginate_oracle(): void + { + // 조회수가 겹치도록 만들어 tie-break 가 개입하게 한다 + for ($i = 1; $i <= 47; $i++) { + DB::table('deferred_join_samples')->insert([ + 'group_id' => $i % 3, + 'title' => sprintf('제목 %03d', $i), + 'view_count' => $i % 5, + 'body' => str_repeat('본문 ', 50), + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + $perPage = 10; + $sort = [ + ['column' => 'view_count', 'direction' => 'desc'], + ['column' => 'id', 'direction' => 'asc'], + ]; + + foreach ([1, 2, 3, 5] as $page) { + $oracle = DeferredJoinSample::query() + ->where('group_id', '!=', 2) + ->orderBy('view_count', 'desc') + ->orderBy('id', 'asc') + ->paginate($perPage, ['id'], 'page', $page); + + $actual = $this->repository()->paginate( + DeferredJoinSample::query()->where('group_id', '!=', 2), + ['id', 'title'], + $sort, + $perPage, + $page, + ); + + $this->assertSame( + $oracle->getCollection()->pluck('id')->all(), + $actual->getCollection()->pluck('id')->all(), + "{$page}페이지의 ID 시퀀스가 기존 paginate() 결과와 달라졌다" + ); + $this->assertSame($oracle->total(), $actual->total(), "{$page}페이지의 총 건수가 달라졌다"); + $this->assertSame($oracle->lastPage(), $actual->lastPage()); + } + } + + public function test_empty_result_does_not_run_outer_query(): void + { + $this->startCapture(); + + $result = $this->repository()->paginate( + DeferredJoinSample::query(), + ['id', 'title'], + [['column' => 'id', 'direction' => 'asc']], + 10, + 1, + ); + + $this->assertTrue($result->getCollection()->isEmpty()); + $this->assertEmpty($this->queriesContaining('`id` in ('), '빈 결과에서는 outer 쿼리를 실행하지 않아야 한다'); + } +} + +/** + * 지연 조인 테스트용 샘플 모델 + */ +class DeferredJoinSample extends Model +{ + use SoftDeletes; + + protected $table = 'deferred_join_samples'; + + protected $guarded = []; + + /** + * 샘플 댓글 관계 + * + * @return HasMany 댓글 관계 + */ + public function comments() + { + return $this->hasMany(DeferredJoinSampleComment::class, 'sample_id'); + } +} + +/** + * 지연 조인 테스트용 샘플 댓글 모델 + */ +class DeferredJoinSampleComment extends Model +{ + protected $table = 'deferred_join_sample_comments'; + + protected $guarded = []; +} diff --git a/tests/Unit/Repositories/Concerns/ResolvesSortSpecTest.php b/tests/Unit/Repositories/Concerns/ResolvesSortSpecTest.php new file mode 100644 index 00000000..10ab7013 --- /dev/null +++ b/tests/Unit/Repositories/Concerns/ResolvesSortSpecTest.php @@ -0,0 +1,143 @@ +resolveSortSpec(...$args); + } + }; + } + + public function test_allowed_column_and_direction_pass_through(): void + { + $spec = $this->repository()->resolve( + ['sort_by' => 'created_at', 'sort_order' => 'asc'], + ['id', 'created_at', 'status'], + 'id', + ); + + $this->assertSame([['column' => 'created_at', 'direction' => 'asc']], $spec); + } + + public function test_unknown_column_falls_back_to_default(): void + { + $spec = $this->repository()->resolve( + ['sort_by' => 'admin_memo', 'sort_order' => 'desc'], + ['id', 'created_at'], + 'id', + ); + + $this->assertSame('id', $spec[0]['column']); + } + + public function test_invalid_direction_falls_back_to_default(): void + { + $spec = $this->repository()->resolve( + ['sort_by' => 'created_at', 'sort_order' => 'sideways'], + ['created_at'], + 'created_at', + 'asc', + ); + + $this->assertSame('asc', $spec[0]['direction']); + } + + public function test_column_alias_is_applied_before_whitelist_check(): void + { + $spec = $this->repository()->resolve( + ['sort_by' => 'author', 'sort_order' => 'desc'], + ['id', 'author_name'], + 'id', + 'desc', + ['author' => 'author_name'], + ); + + $this->assertSame('author_name', $spec[0]['column']); + } + + public function test_custom_filter_keys_are_honored(): void + { + $spec = $this->repository()->resolve( + ['order_by' => 'view_count', 'order_direction' => 'asc'], + ['id', 'view_count'], + 'id', + 'desc', + [], + 'order_by', + 'order_direction', + ); + + $this->assertSame([['column' => 'view_count', 'direction' => 'asc']], $spec); + } + + public function test_backed_enum_values_are_normalized(): void + { + $spec = $this->repository()->resolve( + ['sort_by' => SampleSortColumn::CreatedAt, 'sort_order' => SampleSortDirection::Asc], + ['id', 'created_at'], + 'id', + ); + + $this->assertSame([['column' => 'created_at', 'direction' => 'asc']], $spec); + } + + public function test_missing_and_blank_values_fall_back(): void + { + $spec = $this->repository()->resolve( + ['sort_by' => ' '], + ['id', 'created_at'], + 'created_at', + 'desc', + ); + + $this->assertSame([['column' => 'created_at', 'direction' => 'desc']], $spec); + } + + public function test_array_value_is_rejected(): void + { + $spec = $this->repository()->resolve( + ['sort_by' => ['created_at'], 'sort_order' => ['asc']], + ['id', 'created_at'], + 'id', + ); + + $this->assertSame([['column' => 'id', 'direction' => 'desc']], $spec); + } +} + +/** 테스트용 정렬 컬럼 Enum */ +enum SampleSortColumn: string +{ + case CreatedAt = 'created_at'; +} + +/** 테스트용 정렬 방향 Enum */ +enum SampleSortDirection: string +{ + case Asc = 'asc'; +} diff --git a/tests/Unit/Repositories/Concerns/SortableColumnsGateContractTest.php b/tests/Unit/Repositories/Concerns/SortableColumnsGateContractTest.php new file mode 100644 index 00000000..8a726ce5 --- /dev/null +++ b/tests/Unit/Repositories/Concerns/SortableColumnsGateContractTest.php @@ -0,0 +1,248 @@ + + */ + private const GATE_MAP = [ + 'app/Repositories/ActivityLogRepository.php' => 'app/Http/Requests/ActivityLog/ActivityLogIndexRequest.php', + 'app/Repositories/IdentityMessageDefinitionRepository.php' => 'app/Http/Requests/Admin/Identity/AdminIdentityMessageDefinitionIndexRequest.php', + 'app/Repositories/MenuRepository.php' => 'app/Http/Requests/Menu/MenuListRequest.php', + 'app/Repositories/NotificationDefinitionRepository.php' => 'app/Http/Requests/NotificationDefinition/NotificationDefinitionIndexRequest.php', + 'app/Repositories/NotificationLogRepository.php' => 'app/Http/Requests/NotificationLog/NotificationLogIndexRequest.php', + 'app/Repositories/ScheduleHistoryRepository.php' => 'app/Http/Requests/Schedule/ScheduleHistoryListRequest.php', + 'app/Repositories/ScheduleRepository.php' => 'app/Http/Requests/Schedule/ScheduleListRequest.php', + 'app/Repositories/UserRepository.php' => 'app/Http/Requests/User/UserListRequest.php', + 'modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php' => 'modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/CouponListRequest.php', + 'modules/_bundled/sirsoft-ecommerce/src/Repositories/ExtraFeeTemplateRepository.php' => 'modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ExtraFeeTemplateListRequest.php', + 'modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php' => 'modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/OrderListRequest.php', + 'modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php' => 'modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ProductListRequest.php', + 'modules/_bundled/sirsoft-ecommerce/src/Repositories/ShippingPolicyRepository.php' => 'modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ShippingPolicyListRequest.php', + 'modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php' => 'modules/_bundled/sirsoft-page/src/Http/Requests/PageListRequest.php', + ]; + + /** + * 저장소 루트 절대경로를 돌려줍니다. + * + * @return string 저장소 루트 경로 + */ + private static function repositoryRoot(): string + { + // tests/Unit/Repositories/Concerns → 저장소 루트 + return dirname(__DIR__, 4); + } + + /** + * resolveSortSpec 을 호출하는 파일 목록을 스캔합니다. + * + * @return array 저장소 상대 경로 목록 + */ + private static function scanCallSites(): array + { + $root = self::repositoryRoot(); + $found = []; + + foreach (self::SCAN_ROOTS as $scanRoot) { + $base = $root.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $scanRoot); + + if (! is_dir($base)) { + continue; + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($base, \FilesystemIterator::SKIP_DOTS) + ); + + foreach ($iterator as $file) { + if (! $file->isFile() || $file->getExtension() !== 'php') { + continue; + } + + $path = str_replace('\\', '/', $file->getPathname()); + $relative = ltrim(str_replace(str_replace('\\', '/', $root), '', $path), '/'); + + // 트레이트 본체와 테스트 코드는 호출처가 아니다 + if (str_contains($relative, '/tests/') || str_contains($relative, 'Concerns/ResolvesSortSpec.php')) { + continue; + } + + if (str_contains((string) file_get_contents($path), '$this->resolveSortSpec(')) { + $found[] = $relative; + } + } + } + + sort($found); + + return $found; + } + + /** + * 게이트 대조 대상 케이스를 제공합니다. + * + * @return array [저장소 파일, 요청 파일] + */ + public static function gatePairProvider(): array + { + $cases = []; + + foreach (self::GATE_MAP as $repository => $request) { + if ($request === null) { + continue; + } + + $cases[$repository] = [$repository, $request]; + } + + return $cases; + } + + /** + * 파일에서 정렬 허용 컬럼 집합을 추출합니다. (상수 참조 해석 포함) + * + * @param string $content 파일 내용 + * @return array 허용 컬럼 목록 + */ + private function extractRepositoryColumns(string $content): array + { + $columns = []; + + // resolveSortSpec 의 두 번째 인자 — self::CONST 또는 인라인 배열 + preg_match_all('/resolveSortSpec\(\s*\$\w+\s*,\s*(self::(\w+)|\[[^\]]*\])/s', $content, $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + $literal = $match[1]; + + if (str_starts_with($literal, 'self::')) { + $constName = $match[2]; + + if (! preg_match('/const\s+'.preg_quote($constName, '/').'\s*=\s*\[(.*?)\];/s', $content, $constMatch)) { + continue; + } + + $literal = $constMatch[1]; + } + + preg_match_all("/'([a-zA-Z0-9_]+)'/", $literal, $columnMatches); + $columns = array_merge($columns, $columnMatches[1]); + } + + return array_values(array_unique($columns)); + } + + /** + * FormRequest 에서 sort_by 허용 값을 추출합니다. + * + * @param string $content 파일 내용 + * @return array 허용 값 목록 + */ + private function extractGateColumns(string $content): array + { + // 'sort_by' => ... 한 줄(또는 Rule::in 블록) 안의 in: / Rule::in([...]) 를 읽는다 + if (! preg_match("/'sort_by'\s*=>\s*(.*?)(?:\],|\],\s*$|\n\s*'[a-z_]+'\s*=>)/s", $content, $match)) { + return []; + } + + $rule = $match[1]; + $columns = []; + + if (preg_match('/Rule::in\(\[(.*?)\]\)/s', $rule, $ruleInMatch)) { + preg_match_all("/'([a-zA-Z0-9_]+)'/", $ruleInMatch[1], $columnMatches); + $columns = $columnMatches[1]; + } elseif (preg_match('/in:([a-zA-Z0-9_,]+)/', $rule, $inMatch)) { + $columns = explode(',', $inMatch[1]); + } + + return array_values(array_filter(array_unique($columns))); + } + + /** + * 모든 resolveSortSpec 호출처가 매핑에 등록되어 있는지 검사합니다. + */ + public function test_every_call_site_is_mapped(): void + { + $callSites = self::scanCallSites(); + + $this->assertNotEmpty($callSites, 'resolveSortSpec 호출처를 하나도 찾지 못했습니다 — 스캔 경로를 확인하세요.'); + + $unmapped = array_diff($callSites, array_keys(self::GATE_MAP)); + + $this->assertSame( + [], + array_values($unmapped), + "GATE_MAP 에 등록되지 않은 resolveSortSpec 호출처가 있습니다:\n ".implode("\n ", $unmapped) + ."\n대응 FormRequest 를 매핑에 추가하세요 (요청 경로가 없으면 null)." + ); + + $stale = array_diff(array_keys(self::GATE_MAP), $callSites); + + $this->assertSame( + [], + array_values($stale), + "GATE_MAP 에 남아 있으나 더 이상 resolveSortSpec 을 호출하지 않는 항목:\n ".implode("\n ", $stale) + ); + } + + /** + * Repository 정렬 허용 목록이 FormRequest 게이트를 모두 덮는지 검사합니다. + * + * @param string $repositoryPath 저장소 상대 경로 (Repository) + * @param string $requestPath 저장소 상대 경로 (FormRequest) + */ + #[DataProvider('gatePairProvider')] + public function test_repository_whitelist_covers_gate(string $repositoryPath, string $requestPath): void + { + $root = self::repositoryRoot(); + $repositoryFile = $root.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $repositoryPath); + $requestFile = $root.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $requestPath); + + $this->assertFileExists($repositoryFile, "매핑된 Repository 파일이 없습니다: {$repositoryPath}"); + $this->assertFileExists($requestFile, "매핑된 FormRequest 파일이 없습니다: {$requestPath}"); + + $repositoryColumns = $this->extractRepositoryColumns((string) file_get_contents($repositoryFile)); + $gateColumns = $this->extractGateColumns((string) file_get_contents($requestFile)); + + $this->assertNotEmpty($repositoryColumns, "정렬 허용 컬럼을 추출하지 못했습니다: {$repositoryPath}"); + $this->assertNotEmpty($gateColumns, "sort_by 허용 값을 추출하지 못했습니다: {$requestPath}"); + + $missing = array_diff($gateColumns, $repositoryColumns); + + $this->assertSame( + [], + array_values($missing), + "{$repositoryPath} 의 정렬 허용 목록이 게이트({$requestPath})보다 좁습니다. " + ."검증을 통과한 정렬이 조용히 기본값으로 되돌아갑니다.\n 누락: ".implode(', ', $missing) + ); + } +} diff --git a/tests/scenarios/deep-offset-pagination.yaml b/tests/scenarios/deep-offset-pagination.yaml new file mode 100644 index 00000000..d93326a7 --- /dev/null +++ b/tests/scenarios/deep-offset-pagination.yaml @@ -0,0 +1,91 @@ +# audit:allow test-scenario-coverage reason: 이슈 #492(공개 #74) 깊은 OFFSET 목록 페이지네이션 SSoT. axes cross product(수백 조합 × 26개 호출처)는 docblock 전수 @scenario 매핑이 비현실적이라 면제하고, 회귀 가드는 test_files(트레이트 단위 + 호출 계약 전수 스캔 + 도메인별 저장소 회귀 + Playwright E2E)로 커버한다. 대용량 축(scale)은 scenario-scale-axis-required 룰이 별도 강제(면제 대상 아님) — scale.assert 는 @scale 마킹으로 전수 커버. + +feature: deep_offset_pagination + +description: | + 목록 페이지네이션이 뒤쪽 페이지로 갈수록 선형으로 느려지던 것을, 지연 조인(deferred join) + 2단계 조회로 재설계한다. `OFFSET n` 은 건너뛸 n 건도 실제로 읽으며, 목록 SELECT 에 넓은 + 컬럼(text/longText/JSON)이 있으면 그 n 건의 넓은 컬럼까지 읽힌다. InnoDB 는 행 밖 오버플로 + 페이지에 저장된 값을 앞부분만 필요해도 페이지째 읽으므로 비용이 그대로 누적된다. + + 핵심 설계: + - inner: 필터/정렬만으로 **키 컬럼만** 조회해 이번 페이지의 ID 집합을 구한다. + - outer: 그 ID 집합에 대해서만 목록 컬럼과 관계를 조회한다 — 넓은 컬럼을 읽는 행 수가 + OFFSET 과 무관하게 페이지 크기로 고정된다. + - 정렬은 닫힌 집합(ResolvesSortSpec)이어야 한다. 인덱스 없는 컬럼 정렬은 inner 도 전체 + 스캔하므로 개선 폭이 보장되지 않는다. + - 키 컬럼을 정렬 말미에 자동으로 덧붙여 전순서를 만든다 — 동률 정렬에서 페이지 경계가 + 흔들려 행이 중복/누락되는 것을 막는다. + - 총 건수는 Laravel paginate() 와 같은 집계 경로(getCountForPagination)를 쓴다. count() 는 + groupBy/having 쿼리에서 첫 그룹의 행 수를 총 건수로 돌려준다. + - `SUBSTRING(col, 1, N)` 은 프루닝으로 인정하지 않는다 — 오버플로 페이지 읽기가 그대로 + 발생하므로 잘라내기는 outer 에서만 수행한다. + +axes: + page_position: [first, middle, last] + sort_column: [key, indexed_non_key, tied_values] + total_source: [counted, injected_cache, simple_no_count] + soft_delete: [default_excluded, with_trashed] + query_shape: [flat, joined, grouped] + relations: [none, eager_load, with_count] + filter: [none, search, scoped] + columns: [pruned, all] + +exclusions: + - { total_source: simple_no_count, page_position: last, reason: "simplePaginate 는 총 건수를 세지 않아 마지막 페이지 개념이 없다 — hasMorePages 판정으로 대체" } + - { query_shape: grouped, total_source: simple_no_count, reason: "그룹 쿼리의 다음 페이지 판정은 총 건수 경로와 무관 — 그룹 수 정확성이 검증 대상" } + - { soft_delete: with_trashed, query_shape: grouped, reason: "그룹 목록(회원 활동)에 휴지통 조회 진입점이 없다" } + +effects: + # ── 조회 구조 (P0) ── + - inner_selects_key_column_only + - inner_excludes_wide_columns_from_offset_scan + - outer_filters_by_id_set_without_offset + - relations_eager_loaded_on_outer_only + - with_count_applied_on_outer_only + - empty_id_set_skips_outer_query + # ── 총 건수 (P0) ── + - injected_total_skips_count_query + - simple_mode_skips_count_query + - grouped_query_total_counts_groups_not_first_group_rows + - filtered_query_total_reflects_filters + # ── 페이지 경계 정합 (P0) ── + - key_column_appended_to_sort_for_total_order + - pages_do_not_overlap_or_drop_rows + - outer_result_order_matches_inner_order + - preserve_id_order_restores_inner_sequence + - simple_mode_reports_last_page_without_next + # ── 스코프 보존 (P1) ── + - soft_deleted_rows_stay_excluded + - with_trashed_scope_is_preserved + - query_filters_are_preserved_on_both_stages + - self_scoped_list_returns_only_own_rows + - comments_of_soft_deleted_parents_stay_excluded + # ── 페이지네이터 계약 (P1) ── + - paginator_supports_query_string_appending + - custom_page_name_is_reflected_in_links + # ── 호출 계약 (P1) ── + - every_call_site_declares_columns + - every_call_site_declares_non_empty_sort + # ── 정렬 화이트리스트 (P0) ── + - sort_column_resolved_from_closed_set + - unknown_sort_column_falls_back_to_default + +# scale: 대용량 회귀 축. block-array of flow objects (내장 fallback 파서 제약 — flow 객체만 중첩 파싱) +scale: + - { n: 200000, dataset: board_posts, assert: [deep_offset_bounded_growth, id_only_inner_query, no_wide_column_in_offset_scan] } + +test_files: + # 코어 — 트레이트 단위 (조회 구조 / 총 건수 / 페이지 경계 / 스코프) + - tests/Unit/Repositories/Concerns/PaginatesWithDeferredJoinTest.php + - tests/Unit/Repositories/Concerns/ResolvesSortSpecTest.php + # 코어 — 호출 계약 전수 스캔 (호출처가 늘어나도 자동 편입) + - tests/Unit/Repositories/Concerns/DeferredJoinCallSiteContractTest.php + # 게시판 — 공지 병합 / 답글 트리 / 소프트 삭제 / 정렬 경계 + - modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php + # 게시판 — 삭제된 글의 댓글 제외 / 캐시 total / 검색 총 건수 + - modules/_bundled/sirsoft-board/tests/Unit/Repositories/CommentListDeferredJoinTest.php + # 이커머스 — 주문 목록 소프트 삭제 + 페이지 경계 + - modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/Admin/OrderListDeferredJoinTest.php + # 브라우저 — 마지막 페이지 다음 버튼 / 페이지 이동 후 목록 정합 + - tests/Playwright/specs/pagination/deep-offset-list.spec.ts From 5f4a733b3ba69405383cb15eea8da3900ed026ba Mon Sep 17 00:00:00 2001 From: HeuJung Date: Thu, 30 Jul 2026 23:20:42 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(core,board,ecommerce,page):=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EA=B3=84=EC=B8=A1=20=EC=9D=B8=ED=94=84?= =?UTF-8?q?=EB=9D=BC=204=EC=B6=95=20=ED=99=95=EC=9E=A5=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B3=84=EC=B8=A1=20=EB=8C=80=EC=83=81=20=EC=84=A0=EC=96=B8=20?= =?UTF-8?q?=EC=A7=80=EC=A0=90=20=EC=8B=A0=EC=84=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 계측 축이 "목록 SELECT" 하나였고 프로파일 12종이 커맨드 파일에 하드코딩돼 있어 확장이 자기 대상을 추가할 수 없었다. 확장이 설치·제거되는 설치본마다 "실제로 존재하는 대상"이 달라지므로, 대상을 소유자가 선언하고 코어가 수집하도록 뒤집었다. - 선언 지점 두 곳, 스키마 하나: 코어 config/benchmark.php, 확장 getBenchmarkProfiles (모듈/플러그인). 수집은 method_exists 기반이라 인터페이스를 직접 구현한 서드파티 확장이 깨지지 않는다. - 축 4종(목록/화면/쓰기/배치)을 실행기로 분리하고 컨테이너 태그로 주입 — 축이 늘 때 커맨드를 고치지 않는다. - 화면 축은 HTTP 커널로 내부 요청을 처리한다. 라우트만 dispatch 하면 전역 미들웨어를 건너뛰어 화면과 다른 것을 재게 된다. 인증은 프로파일이 선언한 권한만 가진 임시 계정이 기본이며 --as 로 기존 계정을 지목할 수 있다. - 화면/쓰기 축은 계측 계정 생성부터 처리까지 롤백되는 트랜잭션 안에서 실행해 운영 DB 에도 잔여 데이터를 남기지 않는다. 배치 축은 내부 커밋·락 보유 때문에 감싸지 않고 --allow-write 로만 통제한다. - 잘못된 선언은 조용히 버리지 않고 사유를 경고로 남긴다. 버려진 선언이 곧 계측 사각이 되기 때문이다. 전부 건너뛴 실행은 종료 코드 1 로 끝난다. - 리포트는 환경 정보를 함께 적어 저장소 밖(storage/app/benchmarks)에 남긴다. 측정값이 실행 머신에 종속되므로 저장소에 축적하면 비교 불가능한 수치가 섞인다. 작업 중 발견해 함께 고친 것: - 이관 프로파일 3종의 soft_delete 선언이 실제 스키마와 어긋나 있었다 (users/product_inquiries/pages 에 deleted_at 없음). - 주문 목록이 상태 미지정 시 임시 주문 상태를 NOT IN 으로 제외하는데 선언에 빠져 있었다. filters 가 등가 비교만 지원한 것이 근본 원인이라 연산자 형태를 받도록 넓히고, 미허용 연산자는 측정 전에 거부한다. - 댓글 프로파일은 지배적 술어가 상관 서브쿼리라 선언형으로 재현할 수 없어 제거했다. 맨 테이블 스캔은 어느 화면도 내지 않는 수치가 된다. g7:bench:pagination → g7:bench 로 개명(4축을 재므로). 이전 이름은 별칭으로 남겼다. 테스트 61건 통과, 라이브 18종 전건 측정. --- .gitignore | 2 + AGENTS.md | 9 +- CHANGELOG.md | 4 + app/Benchmark/Axes/BatchAxisRunner.php | 141 ++++ app/Benchmark/Axes/ListAxisRunner.php | 347 +++++++++ app/Benchmark/Axes/ScreenAxisRunner.php | 261 +++++++ app/Benchmark/Axes/WriteAxisRunner.php | 227 ++++++ app/Benchmark/BenchmarkIdentity.php | 144 ++++ app/Benchmark/BenchmarkProfileRegistry.php | 275 +++++++ app/Benchmark/BenchmarkReporter.php | 226 ++++++ .../Contracts/BenchmarkAxisRunner.php | 34 + app/Benchmark/DTO/BenchmarkProfile.php | 97 +++ app/Benchmark/DTO/BenchmarkResult.php | 68 ++ app/Benchmark/DTO/BenchmarkRunOptions.php | 52 ++ app/Benchmark/QueryCollector.php | 102 +++ app/Benchmark/SyntheticSeeder.php | 190 +++++ app/Console/Commands/BenchCommand.php | 395 ++++++++++ .../Commands/BenchPaginationCommand.php | 556 ------------- app/Enums/BenchmarkAxis.php | 93 +++ app/Extension/AbstractModule.php | 32 + app/Extension/AbstractPlugin.php | 31 + app/Providers/CoreServiceProvider.php | 26 + config/benchmark.php | 116 +++ docs/README.md | 5 +- docs/backend/README.md | 1 + docs/backend/benchmark.md | 186 +++++ docs/backend/service-repository.md | 4 +- docs/cheatsheet.md | 20 +- docs/extension/module-basics.md | 1 + docs/extension/plugin-development.md | 1 + modules/_bundled/sirsoft-board/module.php | 44 ++ modules/_bundled/sirsoft-ecommerce/module.php | 86 ++ .../src/Benchmark/OrderCreationBenchmark.php | 116 +++ .../src/Repositories/OrderRepository.php | 2 +- .../Benchmark/OrderCreationBenchmarkTest.php | 161 ++++ modules/_bundled/sirsoft-page/module.php | 31 + tests/Feature/Console/BenchCommandTest.php | 725 +++++++++++++++++ .../Benchmark/BenchmarkAxisRunnerTest.php | 737 ++++++++++++++++++ tests/Unit/Benchmark/BenchmarkProfileTest.php | 153 ++++ tests/scenarios/benchmark-infra.yaml | 132 ++++ 40 files changed, 5266 insertions(+), 567 deletions(-) create mode 100644 app/Benchmark/Axes/BatchAxisRunner.php create mode 100644 app/Benchmark/Axes/ListAxisRunner.php create mode 100644 app/Benchmark/Axes/ScreenAxisRunner.php create mode 100644 app/Benchmark/Axes/WriteAxisRunner.php create mode 100644 app/Benchmark/BenchmarkIdentity.php create mode 100644 app/Benchmark/BenchmarkProfileRegistry.php create mode 100644 app/Benchmark/BenchmarkReporter.php create mode 100644 app/Benchmark/Contracts/BenchmarkAxisRunner.php create mode 100644 app/Benchmark/DTO/BenchmarkProfile.php create mode 100644 app/Benchmark/DTO/BenchmarkResult.php create mode 100644 app/Benchmark/DTO/BenchmarkRunOptions.php create mode 100644 app/Benchmark/QueryCollector.php create mode 100644 app/Benchmark/SyntheticSeeder.php create mode 100644 app/Console/Commands/BenchCommand.php delete mode 100644 app/Console/Commands/BenchPaginationCommand.php create mode 100644 app/Enums/BenchmarkAxis.php create mode 100644 config/benchmark.php create mode 100644 docs/backend/benchmark.md create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Benchmark/OrderCreationBenchmark.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/OrderCreationBenchmarkTest.php create mode 100644 tests/Feature/Console/BenchCommandTest.php create mode 100644 tests/Unit/Benchmark/BenchmarkAxisRunnerTest.php create mode 100644 tests/Unit/Benchmark/BenchmarkProfileTest.php create mode 100644 tests/scenarios/benchmark-infra.yaml diff --git a/.gitignore b/.gitignore index 009482e0..30b9125b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,8 @@ node_modules/ # Settings JSON files (환경별 설정) /storage/app/settings/ +# 성능 계측 리포트 (g7:bench --report) — 측정값이 실행 머신 사양에 종속되므로 저장소에 축적하지 않는다 +/storage/app/benchmarks/ # 확장 프론트엔드 병합 번들 캐시 (version-in-path, 런타임 생성) /storage/app/ext-bundles/ diff --git a/AGENTS.md b/AGENTS.md index 5da0d614..8947f535 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,7 @@ -### 백엔드 [backend/](docs/backend/) (32개) +### 백엔드 [backend/](docs/backend/) (33개) | 문서 | 설명 | TL;DR 핵심 | |------|------|-----------| @@ -16,6 +16,7 @@ | [api-documentation.md](docs/backend/api-documentation.md) | API 레퍼런스 문서 규정 (API Documentation) | 모든 API 엔드포인트는 레퍼런스 문서 필수 — 메서드/URI/파라미터/응답 필드 + 요청·응답 예시 ... | | [api-resources.md](docs/backend/api-resources.md) | API 리소스 | Resource: BaseApiResource 상속 필수 / Collection: BaseApiColl... | | [authentication.md](docs/backend/authentication.md) | 인증 및 세션 처리 | Laravel Sanctum 토큰 전용 인증 (Bearer 토큰만 사용) | +| [benchmark.md](docs/backend/benchmark.md) | 성능 계측 시스템 (Benchmark) | `g7:bench` 가 4축(list/screen/write/batch)을 잰다 — 계측 대상은 커맨드... | | [broadcasting.md](docs/backend/broadcasting.md) | Broadcasting (실시간 이벤트) | Laravel Reverb 사용 (WebSocket) | | [console-confirm.md](docs/backend/console-confirm.md) | 콘솔 yes/no 프롬프트 (ConsoleConfirm) | 콘솔 커맨드의 yes/no 프롬프트는 $this->unifiedConfirm() 사용 — Laravel... | | [controllers.md](docs/backend/controllers.md) | 컨트롤러 계층 구조 | AdminBaseController / AuthBaseController / PublicBaseCont... | @@ -1054,6 +1055,7 @@ php artisan migrate:rollback | `lang/**` | [database-guide.md](docs/database-guide.md) (다국어 섹션) | | `routes/**` | [routing.md](docs/backend/routing.md) | | `app/Seo/**` | [seo-system.md](docs/backend/seo-system.md) | +| `app/Benchmark/**`, `config/benchmark.php` | [benchmark.md](docs/backend/benchmark.md) | --- @@ -1086,3 +1088,8 @@ php artisan migrate:rollback - **ResolvesActivityLogType**: `app/ActivityLog/Traits/ResolvesActivityLogType.php` - **ChangeDetector**: `app/ActivityLog/ChangeDetector.php` - **CoreActivityLogListener**: `app/Listeners/CoreActivityLogListener.php` +- **BenchmarkProfileRegistry**: `app/Benchmark/BenchmarkProfileRegistry.php` +- **성능 계측 DTO**: `app/Benchmark/DTO/{BenchmarkProfile,BenchmarkRunOptions,BenchmarkResult}.php` +- **BenchmarkAxisRunner**: `app/Benchmark/Contracts/BenchmarkAxisRunner.php` +- **성능 계측 축 실행기**: `app/Benchmark/Axes/{List,Screen,Write,Batch}AxisRunner.php` +- **BenchmarkAxis**: `app/Enums/BenchmarkAxis.php` diff --git a/CHANGELOG.md b/CHANGELOG.md index 8284b4a1..ea9d5621 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,10 @@ - 목록 화면이 뒤쪽 페이지로 갈수록 느려지는 문제를 구조적으로 해결할 수 있도록, 확장이 함께 쓸 수 있는 공통 조회 방식을 코어에 추가했습니다. 목록을 두 단계(먼저 이번 페이지에 해당하는 항목만 추려내고, 그 항목에 대해서만 본문·상세 정보를 읽기)로 나눠 읽으므로 게시글·주문·로그가 수십만 건으로 늘어나도 마지막 페이지 조회 비용이 첫 페이지와 비슷하게 유지됩니다. 목록 정렬 기준을 미리 정해 둔 항목으로만 해석하는 공통 처리도 함께 제공하므로, 확장이 목록 조회를 직접 만들 때 정렬 처리를 매번 새로 구현하지 않아도 됩니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +#### 성능 점검 + +- 확장이 자기 성능 측정 대상을 직접 등록할 수 있게 했습니다. 모듈·플러그인이 자신의 목록 화면, 관리자 화면, 저장 동작, 정기 작업 중 속도를 재고 싶은 것을 선언해 두면, 사이트 관리자가 성능 점검을 실행할 때 코어 항목과 함께 측정됩니다. 확장을 설치하면 그 확장의 측정 대상이 자동으로 목록에 나타나고, 제거하면 함께 사라집니다. 화면 측정은 응답 시간과 함께 그 화면이 실행한 데이터베이스 조회 횟수를 보여주므로, 목록 자체는 빠른데 화면이 느린 원인을 찾을 수 있습니다. + ### Changed - 활동 로그·알림 발송 이력·본인인증 기록·스케줄 실행 이력·회원 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 기록의 본문·변경 내역까지 함께 읽어 느려졌지만, 이제 현재 페이지에 해당하는 기록만 상세 정보를 읽습니다. 화면에 보이는 내용은 이전과 동일합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) diff --git a/app/Benchmark/Axes/BatchAxisRunner.php b/app/Benchmark/Axes/BatchAxisRunner.php new file mode 100644 index 00000000..7d50b0fe --- /dev/null +++ b/app/Benchmark/Axes/BatchAxisRunner.php @@ -0,0 +1,141 @@ + null; + + if ($profile->mutates() && ! $options->allowWrite) { + return BenchmarkResult::skipped($profile, '데이터를 변경하는 배치입니다. --allow-write 를 붙여 실행하세요.'); + } + + $command = (string) $profile->option('command'); + $arguments = (array) $profile->option('arguments', []); + + if (! array_key_exists($command, Artisan::all())) { + return BenchmarkResult::skipped($profile, "등록되지 않은 커맨드: {$command} (해당 확장이 설치되어 있는지 확인)"); + } + + $notify("배치 실행: {$command}"); + + // 피크 메모리는 프로세스 단위 누적값이라 감소하지 않는다. 실행 전 값을 함께 + // 기록해야 "이 배치가 피크를 밀어올렸는지"를 판정할 수 있다. + $peakBefore = memory_get_peak_usage(true); + $usageBefore = memory_get_usage(true); + $buffer = new BufferedOutput; + + $start = microtime(true); + + try { + $collected = $this->collector->collect( + static fn () => Artisan::call($command, $arguments, $buffer) + ); + $exitCode = (int) $collected['value']; + $queries = $collected['queries']; + } catch (\Throwable $e) { + return BenchmarkResult::skipped($profile, '계측 실패: '.$e->getMessage()); + } + + $elapsedMs = (microtime(true) - $start) * 1000; + $peakAfter = memory_get_peak_usage(true); + $usageAfter = memory_get_usage(true); + $summary = $this->collector->summarize($queries); + + $notes = [ + sprintf('실행 전 피크 %s → 실행 후 피크 %s', $this->formatBytes($peakBefore), $this->formatBytes($peakAfter)), + ]; + + if ($peakAfter <= $peakBefore) { + $notes[] = '이 배치가 프로세스 피크를 밀어올리지 않았습니다 (실행 전 피크가 이미 더 높음).'; + } + + if ($exitCode !== 0) { + $notes[] = "커맨드가 실패 종료했습니다 (exit={$exitCode}) — 시간·메모리는 실패 지점까지의 값입니다."; + } + + $output = trim($buffer->fetch()); + + if ($output !== '') { + $notes[] = '커맨드 출력: '.$output; + } + + return new BenchmarkResult( + profile: $profile, + headers: ['커맨드', '종료코드', '소요(ms)', '피크 메모리', '메모리 증가', '쿼리(건)'], + rows: [[ + $command, + (string) $exitCode, + number_format($elapsedMs, 1), + $this->formatBytes($peakAfter), + $this->formatBytes(max(0, $usageAfter - $usageBefore)), + number_format($summary['count']), + ]], + metrics: [ + 'command' => $command, + 'arguments' => $arguments, + 'exit_code' => $exitCode, + 'elapsed_ms' => round($elapsedMs, 2), + 'peak_memory_before_bytes' => $peakBefore, + 'peak_memory_after_bytes' => $peakAfter, + 'memory_delta_bytes' => $usageAfter - $usageBefore, + 'query_count' => $summary['count'], + 'db_ms' => $summary['db_ms'], + ], + notes: $notes, + ); + } + + /** + * 바이트를 사람이 읽는 단위로 바꿉니다. + * + * @param int $bytes 바이트 + * @return string 포맷된 문자열 + */ + private function formatBytes(int $bytes): string + { + if ($bytes >= 1024 ** 3) { + return number_format($bytes / 1024 ** 3, 2).' GB'; + } + + if ($bytes >= 1024 ** 2) { + return number_format($bytes / 1024 ** 2, 1).' MB'; + } + + return number_format($bytes / 1024, 1).' KB'; + } +} diff --git a/app/Benchmark/Axes/ListAxisRunner.php b/app/Benchmark/Axes/ListAxisRunner.php new file mode 100644 index 00000000..44d2e27d --- /dev/null +++ b/app/Benchmark/Axes/ListAxisRunner.php @@ -0,0 +1,347 @@ + + */ + private const FILTER_OPERATORS = ['=', '!=', '<>', '<', '<=', '>', '>=', 'like', 'in', 'not in']; + + public function __construct(private readonly SyntheticSeeder $seeder) {} + + /** + * {@inheritDoc} + */ + public function axis(): BenchmarkAxis + { + return BenchmarkAxis::ListQuery; + } + + /** + * {@inheritDoc} + */ + public function run(BenchmarkProfile $profile, BenchmarkRunOptions $options, ?\Closure $onProgress = null): BenchmarkResult + { + $notify = $onProgress ?? static fn (string $message) => null; + $table = (string) $profile->option('table'); + + if (! Schema::hasTable($table)) { + return BenchmarkResult::skipped($profile, "테이블이 없습니다: {$table} (해당 확장이 설치되어 있는지 확인)"); + } + + if (($options->fresh || $options->seed > 0) && app()->environment('production')) { + return BenchmarkResult::skipped($profile, '운영 환경에서는 시딩/비움을 사용할 수 없습니다.'); + } + + $filterError = $this->validateFilters((array) $profile->option('filters', [])); + + if ($filterError !== null) { + return BenchmarkResult::skipped($profile, $filterError); + } + + if ($options->fresh) { + $this->seeder->truncate($table); + $notify("비움: {$table}"); + } + + if ($options->seed > 0) { + $notify("시딩: {$table} ".number_format($options->seed).' 건'); + $this->seeder->seed( + $table, + $options->seed, + (array) $profile->option('seed_overrides', []), + fn (int $inserted, int $total) => $notify(sprintf(' 시딩 %s / %s', number_format($inserted), number_format($total))) + ); + } + + $columns = $this->existingColumns($table, (array) $profile->option('columns', ['*'])); + $order = $this->existingOrder($table, (array) $profile->option('order', [['id', 'desc']])); + + $rows = []; + $notes = []; + + foreach ($options->offsets as $offset) { + $allMs = $this->measure($profile, ['*'], $order, $offset, $options->runs); + $listMs = $this->measure($profile, $columns, $order, $offset, $options->runs); + $idOnlyMs = $this->measure($profile, ['id'], $order, $offset, $options->runs); + + $rows[] = [ + 'offset' => $offset, + 'all_ms' => $allMs, + 'list_ms' => $listMs, + 'id_only_ms' => $idOnlyMs, + 'ratio' => $idOnlyMs > 0 ? round($allMs / $idOnlyMs, 1) : null, + ]; + + if ($options->explain) { + $notes[] = "EXPLAIN @ OFFSET {$offset} — 목록 컬럼"; + + foreach ($this->explain($profile, $columns, $order, $offset) as $line) { + $notes[] = ' '.$line; + } + + // 지연 조인의 inner 가 실제로 어떤 계획을 타는지가 인덱스 설계의 근거다 + $notes[] = "EXPLAIN @ OFFSET {$offset} — ID 만 (지연 조인 inner)"; + + foreach ($this->explain($profile, ['id'], $order, $offset) as $line) { + $notes[] = ' '.$line; + } + } + } + + return new BenchmarkResult( + profile: $profile, + headers: ['OFFSET', '전체 컬럼(ms)', '목록 컬럼(ms)', 'ID만(ms)', '전체÷ID'], + rows: array_map(fn (array $row) => [ + number_format($row['offset']), + number_format($row['all_ms'], 1), + number_format($row['list_ms'], 1), + number_format($row['id_only_ms'], 1), + $row['ratio'] !== null ? $row['ratio'].'×' : '-', + ], $rows), + metrics: [ + 'table' => $table, + 'columns' => $columns, + 'runs' => $options->runs, + 'offsets' => $rows, + ], + notes: $notes, + ); + } + + /** + * 스키마에 실제로 존재하는 컬럼만 남깁니다. + * + * `['*']` 는 "목록이 전 컬럼을 그대로 노출한다" 는 선언입니다. 응답 계약상 넓은 컬럼을 + * 뺄 수 없는 목록(활동 로그의 changes, 알림 발송 이력의 body 등)이 여기 해당하며, 이 + * 경우 계측의 비교 축은 select * vs select id 가 됩니다. 확장 버전에 따라 컬럼 구성이 + * 달라도 계측이 죽지 않도록 스키마에 없는 컬럼은 걸러냅니다. + * + * @param string $table 테이블명 + * @param array $columns 후보 컬럼 + * @return array 존재하는 컬럼 목록 + */ + private function existingColumns(string $table, array $columns): array + { + if ($columns === ['*']) { + return Schema::getColumnListing($table); + } + + $existing = array_values(array_filter( + $columns, + fn (mixed $column) => is_string($column) && Schema::hasColumn($table, $column) + )); + + return $existing === [] ? ['id'] : $existing; + } + + /** + * 스키마에 존재하는 정렬 컬럼만 남깁니다. + * + * @param string $table 테이블명 + * @param array $order 후보 정렬 + * @return array 적용 가능한 정렬 목록 + */ + private function existingOrder(string $table, array $order): array + { + $existing = array_values(array_filter( + $order, + fn (mixed $spec) => is_array($spec) && isset($spec[0]) && Schema::hasColumn($table, (string) $spec[0]) + )); + + return $existing === [] ? [['id', 'desc']] : $existing; + } + + /** + * 한 조합을 여러 번 실행해 중앙값(ms)을 돌려줍니다. + * + * `DB::enableQueryLog()` 는 오버헤드와 메모리 누적이 있어 쓰지 않고 직접 시간을 잽니다. + * + * @param BenchmarkProfile $profile 프로파일 + * @param array $columns select 컬럼 + * @param array $order 정렬 + * @param int $offset OFFSET + * @param int $runs 측정 횟수 + * @return float 중앙값 (밀리초) + */ + private function measure(BenchmarkProfile $profile, array $columns, array $order, int $offset, int $runs): float + { + // 첫 회는 캐시 워밍 성격이라 버린다 + $this->buildQuery($profile, $columns, $order, $offset)->get(); + + $samples = []; + + for ($i = 0; $i < max(1, $runs); $i++) { + $start = microtime(true); + $this->buildQuery($profile, $columns, $order, $offset)->get(); + $samples[] = (microtime(true) - $start) * 1000; + } + + sort($samples); + + return $samples[intdiv(count($samples), 2)]; + } + + /** + * 계측 대상 쿼리를 조립합니다. + * + * @param BenchmarkProfile $profile 프로파일 + * @param array $columns select 컬럼 + * @param array $order 정렬 + * @param int $offset OFFSET + * @return Builder 조립된 쿼리 + */ + private function buildQuery(BenchmarkProfile $profile, array $columns, array $order, int $offset): Builder + { + $table = (string) $profile->option('table'); + $query = DB::table($table)->select($columns); + + if ($profile->option('soft_delete', false) && Schema::hasColumn($table, 'deleted_at')) { + $query->whereNull('deleted_at'); + } + + $this->applyFilters($query, $table, (array) $profile->option('filters', [])); + + foreach ($order as $spec) { + $query->orderBy($spec[0], $spec[1] ?? 'asc'); + } + + return $query->offset($offset)->limit(self::PAGE_LIMIT); + } + + /** + * 선언된 필터를 계측 쿼리에 적용합니다. + * + * 값이 `[연산자, 값]` 형태면 그 연산자로, 그 밖에는 등가 비교로 해석합니다. 등가 비교만 + * 지원하면 화면이 실제로 거는 필터를 선언할 방법이 없는 목록이 생깁니다 — 주문 목록은 + * 상태 미지정 시 임시 주문 상태를 `NOT IN` 으로 제외하므로, 그것을 선언하지 못하면 + * 계측이 화면과 다른 인덱스를 타게 됩니다. + * + * 연산자는 닫힌 집합으로 해석합니다. 임의 문자열을 그대로 넘기면 선언 오타가 조용히 + * 통과하거나 의도치 않은 SQL 이 조립됩니다. + * + * @param Builder $query 계측 쿼리 + * @param string $table 대상 테이블 + * @param array $filters 선언된 필터 + */ + private function applyFilters(Builder $query, string $table, array $filters): void + { + foreach ($filters as $column => $declared) { + if (! Schema::hasColumn($table, (string) $column)) { + continue; + } + + // [연산자, 값] 형태가 아니면 등가 비교 (배열 값을 IN 으로 오해석하지 않도록 형태로 판정) + if (! is_array($declared) || count($declared) !== 2 || ! is_string($declared[0])) { + $query->where($column, $declared); + + continue; + } + + [$operator, $value] = $declared; + + match (strtolower($operator)) { + '=' => $query->where($column, '=', $value), + '!=', '<>' => $query->where($column, '!=', $value), + '<' => $query->where($column, '<', $value), + '<=' => $query->where($column, '<=', $value), + '>' => $query->where($column, '>', $value), + '>=' => $query->where($column, '>=', $value), + 'like' => $query->where($column, 'like', $value), + 'in' => $query->whereIn($column, (array) $value), + 'not in' => $query->whereNotIn($column, (array) $value), + // 도달 불가 — run() 이 실행 전에 연산자를 검증해 거부한다 + default => null, + }; + } + } + + /** + * 선언된 필터의 연산자를 실행 전에 검증합니다. + * + * 알 수 없는 연산자를 조용히 무시하면 그 필터가 빠진 채로 측정되어, 화면과 다른 것을 + * 재면서도 정상 측정으로 보고됩니다. 실행 전에 사유와 함께 거부합니다. + * + * @param array $filters 선언된 필터 + * @return string|null 실패 사유 (문제 없으면 null) + */ + private function validateFilters(array $filters): ?string + { + foreach ($filters as $column => $declared) { + if (! is_array($declared) || count($declared) !== 2 || ! is_string($declared[0])) { + continue; + } + + if (! in_array(strtolower($declared[0]), self::FILTER_OPERATORS, true)) { + return sprintf( + '필터 %s 의 연산자를 알 수 없습니다: %s (허용: %s)', + $column, + $declared[0], + implode(', ', self::FILTER_OPERATORS) + ); + } + } + + return null; + } + + /** + * 실행 계획을 수집합니다. + * + * @param BenchmarkProfile $profile 프로파일 + * @param array $columns select 컬럼 + * @param array $order 정렬 + * @param int $offset OFFSET + * @return array 실행 계획 요약 줄 목록 + */ + private function explain(BenchmarkProfile $profile, array $columns, array $order, int $offset): array + { + $query = $this->buildQuery($profile, $columns, $order, $offset); + + try { + $plan = DB::select('EXPLAIN '.$query->toSql(), $query->getBindings()); + } catch (\Throwable $e) { + return ['실행 계획 수집 실패: '.$e->getMessage()]; + } + + return array_map(function ($row) { + $row = (array) $row; + + return sprintf( + 'type=%s key=%s rows=%s filtered=%s extra=%s', + $row['type'] ?? '-', + $row['key'] ?? '-', + $row['rows'] ?? '-', + $row['filtered'] ?? '-', + $row['Extra'] ?? '-' + ); + }, $plan); + } +} diff --git a/app/Benchmark/Axes/ScreenAxisRunner.php b/app/Benchmark/Axes/ScreenAxisRunner.php new file mode 100644 index 00000000..787cadec --- /dev/null +++ b/app/Benchmark/Axes/ScreenAxisRunner.php @@ -0,0 +1,261 @@ +mutates() && ! $options->allowWrite) { + return BenchmarkResult::skipped($profile, '데이터를 변경하는 화면입니다. --allow-write 를 붙여 실행하세요.'); + } + + [$uri, $uriError] = $this->resolveUri($profile); + + if ($uriError !== null) { + return BenchmarkResult::skipped($profile, $uriError); + } + + // `--as` 로 지정한 계정은 트랜잭션 진입 전에 확인한다 — 없는 계정으로 계측을 시작하면 + // 롤백 구간 안에서 실패해 사유만 흐려진다. + if ($options->asUser !== null && $this->identity->findExistingUser($options->asUser) === null) { + return BenchmarkResult::skipped($profile, "계측에 사용할 계정을 찾을 수 없습니다: {$options->asUser}"); + } + + $method = strtoupper((string) $profile->option('method', 'GET')); + $query = (array) $profile->option('query', []); + $query['per_page'] ??= $options->perPage; + + // 데이터를 변경하는 화면은 반복 측정 시 2회차부터 조건이 달라진다(중복 키, 재고 소진). + // 롤백은 계측 종료 시 한 번이므로 회차 간에는 되돌려지지 않는다 → 1회만 잰다. + $runs = $profile->mutates() ? 1 : max(1, $options->runs); + $notes = []; + + if ($profile->mutates()) { + $notes[] = '데이터 변경 화면이라 1회만 측정했습니다 (회차 간 조건 변화 방지).'; + } + + try { + $measured = $this->identity->withRolledBackTransaction( + fn () => $this->measure($profile, (string) $uri, $method, $query, $runs, $options, $onProgress) + ); + } catch (\Throwable $e) { + return BenchmarkResult::skipped($profile, '계측 실패: '.$e->getMessage()); + } + + $summary = $measured['summary']; + + foreach ($summary['n_plus_one'] as $candidate) { + $notes[] = sprintf('N+1 후보 %d회: %s', $candidate['count'], $candidate['sql']); + } + + if ($summary['n_plus_one'] === []) { + $notes[] = 'N+1 후보 없음 (같은 SQL 5회 이상 반복 없음).'; + } + + return new BenchmarkResult( + profile: $profile, + headers: ['요청', '상태', '응답(ms)', '쿼리(건)', 'DB(ms)'], + rows: [[ + $method.' '.$uri, + (string) $measured['status'], + number_format($measured['total_ms'], 1), + number_format($summary['count']), + number_format($summary['db_ms'], 1), + ]], + metrics: [ + 'uri' => $uri, + 'method' => $method, + 'query' => $query, + 'status' => $measured['status'], + 'runs' => $runs, + 'total_ms' => $measured['total_ms'], + 'query_count' => $summary['count'], + 'db_ms' => $summary['db_ms'], + 'n_plus_one' => $summary['n_plus_one'], + 'acting_user' => $measured['acting_user'], + ], + notes: $notes, + ); + } + + /** + * 계측 대상 URI 를 해석합니다. + * + * 라우트명 선언을 권장하는 이유는 두 가지입니다 — 프리픽스를 문자열로 조립하지 않아도 + * 되고, 라우트가 사라지면 계측이 조용히 404 를 재는 대신 사유를 남기고 건너뜁니다. + * + * 예외를 던지지 않고 사유 문자열을 함께 돌려주는 이유는, 이 축의 실패 계약이 + * `BenchmarkResult::skipped` 이기 때문입니다 — 던지고 곧바로 잡는 우회로를 만들지 않습니다. + * + * @param BenchmarkProfile $profile 프로파일 + * @return array{0: string|null, 1: string|null} [경로, 실패 사유] + */ + private function resolveUri(BenchmarkProfile $profile): array + { + $routeName = $profile->option('route'); + + if (is_string($routeName) && $routeName !== '') { + if (Route::getRoutes()->getByName($routeName) === null) { + return [null, "등록되지 않은 라우트명: {$routeName} (해당 확장이 설치되어 있는지 확인)"]; + } + + return [route($routeName, (array) $profile->option('route_params', []), false), null]; + } + + return ['/'.ltrim((string) $profile->option('uri'), '/'), null]; + } + + /** + * 내부 요청을 반복 실행해 응답 시간 중앙값과 쿼리 요약을 냅니다. + * + * @param BenchmarkProfile $profile 프로파일 + * @param string $uri 경로 + * @param string $method HTTP 메서드 + * @param array $query 쿼리스트링 + * @param int $runs 측정 횟수 + * @param BenchmarkRunOptions $options 실행 옵션 + * @param \Closure|null $onProgress 진행 콜백 + * @return array{status: int, total_ms: float, summary: array, acting_user: array} 계측 결과 + */ + private function measure( + BenchmarkProfile $profile, + string $uri, + string $method, + array $query, + int $runs, + BenchmarkRunOptions $options, + ?\Closure $onProgress, + ): array { + $notify = $onProgress ?? static fn (string $message) => null; + + $issued = $this->identity->issueToken( + (array) $profile->option('permissions', []), + $options->asUser !== null ? $this->identity->findExistingUser($options->asUser) : null + ); + + $notify(sprintf( + '인증: %s (%s)', + $issued['user']->email, + $issued['ephemeral'] ? '계측용 임시 계정 — 종료 시 롤백' : '지정 계정' + )); + + $samples = []; + $status = 0; + $queries = []; + + // 첫 회는 라우트 매칭·컨테이너 해석 워밍이라 버린다 (측정 회차와 별도로 1회 더 실행) + for ($i = 0; $i <= $runs; $i++) { + $collected = $this->collector->collect( + fn () => $this->dispatch($uri, $method, $query, $issued['token']) + ); + + /** @var array{status: int, ms: float} $outcome */ + $outcome = $collected['value']; + $status = $outcome['status']; + + if ($i === 0) { + continue; + } + + $samples[] = $outcome['ms']; + $queries = $collected['queries']; + } + + sort($samples); + + return [ + 'status' => $status, + 'total_ms' => $samples[intdiv(count($samples), 2)], + 'summary' => $this->collector->summarize($queries), + 'acting_user' => [ + 'id' => $issued['user']->id, + 'email' => $issued['user']->email, + 'ephemeral' => $issued['ephemeral'], + ], + ]; + } + + /** + * 내부 요청 1회를 처리하고 소요 시간을 잽니다. + * + * 커널이 컨테이너의 `request` 인스턴스와 인증 가드 상태를 갈아치우므로, 처리 후 + * 원상 복구합니다 — 복구하지 않으면 이어지는 회차/프로파일이 앞 요청의 인증 상태를 + * 물려받아 계측이 서로 오염됩니다. + * + * @param string $uri 경로 + * @param string $method HTTP 메서드 + * @param array $query 쿼리스트링/바디 + * @param string $token Sanctum 토큰 + * @return array{status: int, ms: float} 상태 코드와 소요 시간 + */ + private function dispatch(string $uri, string $method, array $query, string $token): array + { + $previousRequest = app()->bound('request') ? app('request') : null; + + $request = Request::create($uri, $method, $query); + $request->headers->set('Accept', 'application/json'); + $request->headers->set('Authorization', 'Bearer '.$token); + + $start = microtime(true); + + try { + $response = app(HttpKernel::class)->handle($request); + $status = $response->getStatusCode(); + // 스트리밍 응답은 본문 생성까지가 사용자 체감 시간이라 여기서 소비한다 + $response->getContent(); + } finally { + $ms = (microtime(true) - $start) * 1000; + + Auth::forgetGuards(); + + if ($previousRequest !== null) { + app()->instance('request', $previousRequest); + } + } + + return ['status' => $status, 'ms' => $ms]; + } +} diff --git a/app/Benchmark/Axes/WriteAxisRunner.php b/app/Benchmark/Axes/WriteAxisRunner.php new file mode 100644 index 00000000..ca92dc20 --- /dev/null +++ b/app/Benchmark/Axes/WriteAxisRunner.php @@ -0,0 +1,227 @@ + null; + + if (! $options->allowWrite) { + return BenchmarkResult::skipped($profile, '쓰기 축입니다. --allow-write 를 붙여 실행하세요.'); + } + + $resolved = []; + + foreach (['callback', 'prepare', 'cleanup'] as $field) { + if ($field !== 'callback' && $profile->option($field) === null) { + $resolved[$field] = null; + + continue; + } + + [$callable, $error] = $this->resolveCallable($profile->option($field), $profile, $field); + + if ($error !== null) { + return BenchmarkResult::skipped($profile, $error); + } + + $resolved[$field] = $callable; + } + + $callback = $resolved['callback']; + $prepare = $resolved['prepare']; + $cleanup = $resolved['cleanup']; + + $runs = max(1, $options->runs); + + try { + $measured = $this->identity->withRolledBackTransaction( + fn () => $this->measure($callback, $prepare, $runs, $onProgress) + ); + } catch (\Throwable $e) { + return BenchmarkResult::skipped($profile, '계측 실패: '.$e->getMessage()); + } finally { + if ($cleanup !== null) { + // 트랜잭션 롤백으로 되돌지 않는 잔여물(파일·캐시)을 정리한다. + // 정리 실패가 계측 결과를 삼키면 안 되므로 사유만 알린다. + try { + $cleanup(); + } catch (\Throwable $e) { + $notify('cleanup 실패: '.$e->getMessage()); + } + } + } + + $summary = $this->collector->summarize($measured['queries']); + + return new BenchmarkResult( + profile: $profile, + headers: ['첫 회(ms)', '중앙값(ms)', '회차', '쿼리(건)', 'DB(ms)'], + rows: [[ + number_format($measured['first_ms'], 1), + number_format($measured['median_ms'], 1), + (string) $runs, + number_format($summary['count']), + number_format($summary['db_ms'], 1), + ]], + metrics: [ + 'first_ms' => $measured['first_ms'], + 'median_ms' => $measured['median_ms'], + 'runs' => $runs, + 'samples_ms' => $measured['samples'], + 'query_count' => $summary['count'], + 'db_ms' => $summary['db_ms'], + 'n_plus_one' => $summary['n_plus_one'], + ], + notes: array_merge( + ['계측으로 생긴 행은 롤백했습니다.'], + array_map( + fn (array $candidate) => sprintf('반복 쿼리 %d회: %s', $candidate['count'], $candidate['sql']), + $summary['n_plus_one'] + ) + ), + ); + } + + /** + * 선언된 콜백을 호출 가능한 형태로 해석합니다. + * + * 예외를 던지지 않고 사유 문자열을 함께 돌려주는 이유는, 이 축의 실패 계약이 + * `BenchmarkResult::skipped` 이기 때문입니다 — 던지고 곧바로 잡는 우회로를 만들지 않습니다. + * + * @param mixed $declared 선언값 + * @param BenchmarkProfile $profile 프로파일 (오류 메시지용) + * @param string $field 선언 필드명 (오류 메시지용) + * @return array{0: callable|null, 1: string|null} [해석된 콜백, 실패 사유] + */ + private function resolveCallable(mixed $declared, BenchmarkProfile $profile, string $field): array + { + $where = $profile->qualifiedKey().' 의 '.$field; + + if ($declared instanceof \Closure) { + return [null, "{$where}: 클로저는 쓸 수 없습니다 (config:cache 불가). 'Fqcn' 또는 ['Fqcn', 'method'] 형식으로 선언하세요."]; + } + + if (is_string($declared)) { + if (! class_exists($declared)) { + return [null, "{$where}: 클래스를 찾을 수 없습니다 — {$declared}"]; + } + + $instance = app($declared); + + if (! is_callable($instance)) { + return [null, "{$where}: {$declared} 에 __invoke() 가 없습니다."]; + } + + return [$instance, null]; + } + + if (is_array($declared) && count($declared) === 2 && is_string($declared[0]) && is_string($declared[1])) { + [$class, $method] = $declared; + + if (! class_exists($class)) { + return [null, "{$where}: 클래스를 찾을 수 없습니다 — {$class}"]; + } + + $instance = app($class); + + if (! method_exists($instance, $method)) { + return [null, "{$where}: {$class}::{$method}() 가 없습니다."]; + } + + return [[$instance, $method], null]; + } + + return [null, "{$where}: 'Fqcn' 또는 ['Fqcn', 'method'] 형식이어야 합니다."]; + } + + /** + * 콜백을 반복 실행해 첫 회 시간과 중앙값을 냅니다. + * + * 첫 회를 버리지 않고 따로 보고하는 이유는, 저장 경로에서는 첫 회가 포함하는 비용 + * (클래스 로딩, 설정 해석, 관계 초기 조회)이 실사용에서도 발생하기 때문입니다. + * 쿼리 요약은 마지막 회차 기준입니다 — 첫 회는 위 초기화 쿼리가 섞입니다. + * + * `prepare` 는 계측 구간 **밖**에서 회차마다 실행합니다. 저장 경로에는 선행 상태가 + * 필요한 경우가 많고(주문 생성에는 임시 주문이 필요하고 임시 주문은 1회만 전환됨), + * 그 준비 비용이 측정값에 섞이면 재려던 것을 재지 못하게 됩니다. + * + * @param callable $callback 저장 경로 콜백 (prepare 반환값을 인자로 받음) + * @param callable|null $prepare 회차별 선행 준비 콜백 (계측 제외, 회차 번호를 인자로 받음) + * @param int $runs 측정 횟수 + * @param \Closure|null $onProgress 진행 콜백 + * @return array{first_ms: float, median_ms: float, samples: array, queries: array} 계측 결과 + */ + private function measure(callable $callback, ?callable $prepare, int $runs, ?\Closure $onProgress): array + { + $notify = $onProgress ?? static fn (string $message) => null; + $samples = []; + $queries = []; + + for ($i = 1; $i <= $runs; $i++) { + $notify("쓰기 계측 {$i} / {$runs}"); + + $context = $prepare !== null ? $prepare($i) : null; + + $start = microtime(true); + $collected = $this->collector->collect(static function () use ($callback, $context) { + $callback($context); + }); + $samples[] = (microtime(true) - $start) * 1000; + $queries = $collected['queries']; + } + + $sorted = $samples; + sort($sorted); + + return [ + 'first_ms' => $samples[0], + 'median_ms' => $sorted[intdiv(count($sorted), 2)], + 'samples' => $samples, + 'queries' => $queries, + ]; + } +} diff --git a/app/Benchmark/BenchmarkIdentity.php b/app/Benchmark/BenchmarkIdentity.php new file mode 100644 index 00000000..cb2af9c1 --- /dev/null +++ b/app/Benchmark/BenchmarkIdentity.php @@ -0,0 +1,144 @@ + 0` 분기), 읽기/쓰기 분리 환경에서도 계측 요청이 이 계정을 인증할 수 + * 있습니다. 운영 DB 에서도 잔여 계정이 생기지 않는 이유가 이 롤백입니다. + */ +class BenchmarkIdentity +{ + /** + * 롤백되는 트랜잭션 안에서 콜백을 실행합니다. + * + * 계측이 만든 계정·토큰과, 계측 대상이 만든 행까지 함께 되돌립니다. 쓰기 축의 결과를 + * 되돌리는 것도 같은 장치를 씁니다. + * + * @param \Closure $callback 트랜잭션 안에서 실행할 작업 + * @return mixed 콜백 반환값 + */ + public function withRolledBackTransaction(\Closure $callback): mixed + { + DB::beginTransaction(); + + try { + return $callback(); + } finally { + // 계측 결과 산출 여부와 무관하게 되돌린다 — 예외로 빠져나가도 잔여 데이터 없음 + DB::rollBack(); + } + } + + /** + * 계측에 사용할 Sanctum 토큰을 발급합니다. + * + * 호출 전에 `--as` 계정 존재를 `findExistingUser()` 로 확인해야 합니다 — 계정 부재는 + * 계측 실패가 아니라 실행 전 판정 대상이라 이 메서드는 그 경우를 다루지 않습니다. + * + * @param array $permissions 임시 계정에 부여할 권한 식별자 목록 + * @param User|null $actor `--as` 로 확인된 기존 계정 (null 이면 임시 계정 생성) + * @return array{token: string, user: User, ephemeral: bool} 토큰과 인증 주체 + */ + public function issueToken(array $permissions = [], ?User $actor = null): array + { + $user = $actor ?? $this->makeEphemeralAdmin($permissions); + + return [ + 'token' => $user->createToken('g7-bench-'.Str::random(8))->plainTextToken, + 'user' => $user, + 'ephemeral' => $actor === null, + ]; + } + + /** + * 지정된 기존 계정을 찾습니다. + * + * @param string $identifier 계정 ID 또는 이메일 + * @return User|null 찾은 계정 (없으면 null) + */ + public function findExistingUser(string $identifier): ?User + { + return ctype_digit($identifier) + ? User::find((int) $identifier) + : User::where('email', $identifier)->first(); + } + + /** + * 선언된 권한만 가진 임시 관리자를 생성합니다. + * + * 권한을 프로파일 선언에서 받는 이유는, 계측 대상 화면이 요구하는 권한만 주어야 + * 미들웨어 통과 여부까지 실제와 같아지기 때문입니다(전권 계정으로 재면 권한 검사 + * 비용과 분기가 달라집니다). + * + * @param array $permissions 권한 식별자 목록 + * @return User 생성된 임시 관리자 + */ + private function makeEphemeralAdmin(array $permissions): User + { + $user = User::factory()->create(); + + $permissionIds = []; + + foreach ($permissions as $identifier) { + $identifier = (string) $identifier; + + $permission = Permission::firstOrCreate( + ['identifier' => $identifier], + [ + 'name' => json_encode(['ko' => $identifier, 'en' => $identifier]), + 'description' => json_encode(['ko' => $identifier, 'en' => $identifier]), + 'extension_type' => ExtensionOwnerType::Core, + 'extension_identifier' => 'core', + 'type' => 'admin', + ] + ); + + $permissionIds[] = $permission->id; + } + + $benchRole = Role::create([ + 'identifier' => 'g7_bench_'.Str::random(8), + 'name' => json_encode(['ko' => '성능 계측 전용', 'en' => 'Benchmark Only']), + 'description' => json_encode(['ko' => '성능 계측 임시 역할', 'en' => 'Temporary benchmark role']), + 'is_active' => true, + ]); + + $adminRole = Role::firstOrCreate( + ['identifier' => 'admin'], + [ + 'name' => json_encode(['ko' => '관리자', 'en' => 'Admin']), + 'description' => json_encode(['ko' => '시스템 관리자', 'en' => 'System Admin']), + 'extension_type' => ExtensionOwnerType::Core, + 'extension_identifier' => 'core', + 'type' => 'admin', + 'is_active' => true, + ] + ); + + if ($permissionIds !== []) { + $benchRole->permissions()->sync($permissionIds); + } + + $user->roles()->attach($adminRole->id, ['assigned_at' => now(), 'assigned_by' => null]); + $user->roles()->attach($benchRole->id, ['assigned_at' => now(), 'assigned_by' => null]); + + return $user->fresh(); + } +} diff --git a/app/Benchmark/BenchmarkProfileRegistry.php b/app/Benchmark/BenchmarkProfileRegistry.php new file mode 100644 index 00000000..93ac970c --- /dev/null +++ b/app/Benchmark/BenchmarkProfileRegistry.php @@ -0,0 +1,275 @@ +|null + */ + private ?array $profiles = null; + + /** + * 수집 중 발생한 선언 오류 메시지 + * + * @var array + */ + private array $warnings = []; + + public function __construct( + private readonly ModuleManagerInterface $moduleManager, + private readonly PluginManagerInterface $pluginManager, + ) {} + + /** + * 모든 프로파일을 정규화 키 기준으로 반환합니다. + * + * @return array 정규화 키 → 프로파일 + */ + public function all(): array + { + if ($this->profiles !== null) { + return $this->profiles; + } + + $profiles = []; + + foreach ($this->collectCore() as $profile) { + $profiles[$profile->qualifiedKey()] = $profile; + } + + foreach ($this->collectExtensions() as $profile) { + $profiles[$profile->qualifiedKey()] = $profile; + } + + return $this->profiles = $profiles; + } + + /** + * 특정 축의 프로파일만 반환합니다. + * + * @param BenchmarkAxis $axis 대상 축 + * @return array 정규화 키 → 프로파일 + */ + public function byAxis(BenchmarkAxis $axis): array + { + return array_filter($this->all(), fn (BenchmarkProfile $profile) => $profile->axis === $axis); + } + + /** + * 키로 프로파일을 해석합니다. + * + * 정규화 키(`sirsoft-ecommerce/orders`)를 우선 대조하고, 없으면 짧은 키로 찾습니다. + * 짧은 키가 둘 이상의 확장에서 선언돼 모호하면 후보 목록을 사유로 돌려줍니다 — + * 임의로 하나를 고르면 어느 확장의 목록을 잰 것인지 알 수 없게 됩니다. + * + * @param string $key 프로파일 키 (정규화 키 또는 짧은 키) + * @return array{0: BenchmarkProfile|null, 1: string|null} [찾은 프로파일, 실패 사유] + */ + public function resolve(string $key): array + { + $profiles = $this->all(); + + if (isset($profiles[$key])) { + return [$profiles[$key], null]; + } + + $matches = array_filter($profiles, fn (BenchmarkProfile $profile) => $profile->key === $key); + + if ($matches === []) { + return [null, "등록되지 않은 프로파일: {$key} (--list-profiles 로 목록 확인)"]; + } + + if (count($matches) > 1) { + $candidates = implode(', ', array_keys($matches)); + + return [null, "프로파일 키가 모호합니다: {$key} — 다음 중 하나로 지정하세요: {$candidates}"]; + } + + return [reset($matches), null]; + } + + /** + * 키로 프로파일을 찾습니다. (해석 실패 시 null) + * + * 사유가 필요하면 `resolve()` 를 씁니다. + * + * @param string $key 프로파일 키 (정규화 키 또는 짧은 키) + * @return BenchmarkProfile|null 찾은 프로파일 + */ + public function find(string $key): ?BenchmarkProfile + { + return $this->resolve($key)[0]; + } + + /** + * 수집 중 무시된 선언의 사유 목록을 반환합니다. + * + * @return array 경고 메시지 + */ + public function warnings(): array + { + $this->all(); + + return $this->warnings; + } + + /** + * 코어 프로파일을 수집합니다. + * + * @return array 코어 프로파일 목록 + */ + private function collectCore(): array + { + $declared = config('benchmark.profiles', []); + + return $this->normalize(is_array($declared) ? $declared : [], 'core', 'core'); + } + + /** + * 활성 모듈/플러그인 프로파일을 수집합니다. + * + * @return array 확장 프로파일 목록 + */ + private function collectExtensions(): array + { + $profiles = []; + + foreach ($this->moduleManager->getActiveModules() as $module) { + $profiles = array_merge( + $profiles, + $this->normalize($this->extract($module), 'module', $module->getIdentifier()) + ); + } + + foreach ($this->pluginManager->getActivePlugins() as $plugin) { + $profiles = array_merge( + $profiles, + $this->normalize($this->extract($plugin), 'plugin', $plugin->getIdentifier()) + ); + } + + return $profiles; + } + + /** + * 확장의 `getBenchmarkProfiles()` 선언을 격리 호출로 읽습니다. + * + * @param object $extension 확장 인스턴스 + * @return array 선언 배열 (실패 시 빈 배열) + */ + private function extract(object $extension): array + { + if (! method_exists($extension, 'getBenchmarkProfiles')) { + return []; + } + + try { + $declared = $extension->getBenchmarkProfiles(); + } catch (\Throwable $e) { + $this->warnings[] = sprintf( + '%s: getBenchmarkProfiles() 호출 실패 — %s', + method_exists($extension, 'getIdentifier') ? $extension->getIdentifier() : $extension::class, + $e->getMessage() + ); + + return []; + } + + return is_array($declared) ? $declared : []; + } + + /** + * 선언 배열을 검증해 프로파일 객체로 정규화합니다. + * + * @param array $declared 선언 배열 (키 → 정의) + * @param string $sourceKind 출처 종류 (core|module|plugin) + * @param string $sourceIdentifier 출처 식별자 + * @return array 정규화된 프로파일 목록 + */ + private function normalize(array $declared, string $sourceKind, string $sourceIdentifier): array + { + $profiles = []; + + foreach ($declared as $key => $definition) { + if (! is_string($key) || $key === '') { + $this->warnings[] = "{$sourceIdentifier}: 프로파일 키는 빈 문자열이 아닌 문자열이어야 합니다."; + + continue; + } + + if (! is_array($definition)) { + $this->warnings[] = "{$sourceIdentifier}/{$key}: 프로파일 정의는 배열이어야 합니다."; + + continue; + } + + $axis = BenchmarkAxis::tryFrom((string) ($definition['type'] ?? '')); + + if ($axis === null) { + $this->warnings[] = sprintf( + '%s/%s: 알 수 없는 type — %s (허용: %s)', + $sourceIdentifier, + $key, + var_export($definition['type'] ?? null, true), + implode('|', BenchmarkAxis::values()) + ); + + continue; + } + + $missing = array_values(array_filter( + $axis->requiredOptions(), + fn (array $group) => array_filter( + $group, + fn (string $option) => isset($definition[$option]) + ) === [] + )); + + if ($missing !== []) { + $this->warnings[] = sprintf( + '%s/%s: %s 축 필수 옵션 누락 — %s', + $sourceIdentifier, + $key, + $axis->value, + implode(', ', array_map(fn (array $group) => implode('|', $group), $missing)) + ); + + continue; + } + + $label = $definition['label'] ?? null; + unset($definition['type'], $definition['label']); + + $profiles[] = new BenchmarkProfile( + key: $key, + axis: $axis, + sourceKind: $sourceKind, + sourceIdentifier: $sourceIdentifier, + options: $definition, + label: is_string($label) ? $label : null, + ); + } + + return $profiles; + } +} diff --git a/app/Benchmark/BenchmarkReporter.php b/app/Benchmark/BenchmarkReporter.php new file mode 100644 index 00000000..08443a76 --- /dev/null +++ b/app/Benchmark/BenchmarkReporter.php @@ -0,0 +1,226 @@ + 항목 → 값 + */ + public function environment(): array + { + return [ + 'APP_ENV' => (string) config('app.env'), + 'G7 버전' => (string) config('app.version'), + 'DB 연결' => (string) config('database.default'), + 'DB 스키마' => $this->databaseName(), + 'DB 버전' => $this->databaseVersion(), + 'PHP 버전' => PHP_VERSION, + 'OPcache' => function_exists('opcache_get_status') && @opcache_get_status(false) !== false ? 'on' : 'off', + 'memory_limit' => (string) ini_get('memory_limit'), + 'config:cache' => app()->configurationIsCached() ? 'on' : 'off', + '실행 머신' => php_uname('s').' '.php_uname('r').' / '.php_uname('m'), + ]; + } + + /** + * JSON 으로 직렬화합니다. + * + * @param array $results 계측 결과 + * @param BenchmarkRunOptions $options 실행 옵션 + * @param array $warnings 프로파일 수집 경고 + * @return string JSON 문자열 + */ + public function toJson(array $results, BenchmarkRunOptions $options, array $warnings = []): string + { + return (string) json_encode([ + 'generated_at' => now()->toIso8601String(), + 'environment' => $this->environment(), + 'options' => $options->toArray(), + 'warnings' => $warnings, + 'results' => array_map(fn (BenchmarkResult $result) => $result->toArray(), $results), + ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + } + + /** + * 마크다운 리포트를 만듭니다. + * + * @param array $results 계측 결과 + * @param BenchmarkRunOptions $options 실행 옵션 + * @param array $warnings 프로파일 수집 경고 + * @return string 마크다운 문서 + */ + public function toMarkdown(array $results, BenchmarkRunOptions $options, array $warnings = []): string + { + $lines = [ + '# G7 성능 점검 리포트', + '', + '- 생성 시각: '.now()->toDateTimeString(), + '', + '## 실행 환경', + '', + '| 항목 | 값 |', + '| ------ | ------ |', + ]; + + foreach ($this->environment() as $name => $value) { + $lines[] = "| {$name} | {$value} |"; + } + + $lines[] = ''; + $lines[] = '계측값은 위 환경에 종속됩니다. 다른 리포트와 비교할 때는 환경이 같은지 먼저 확인하세요.'; + $lines[] = ''; + $lines[] = '## 실행 조건'; + $lines[] = ''; + $lines[] = '| 항목 | 값 |'; + $lines[] = '| ------ | ------ |'; + + foreach ($options->toArray() as $name => $value) { + $lines[] = sprintf('| %s | %s |', $name, $this->stringify($value)); + } + + if ($warnings !== []) { + $lines[] = ''; + $lines[] = '## 무시된 프로파일 선언'; + $lines[] = ''; + + foreach ($warnings as $warning) { + $lines[] = '- '.$warning; + } + } + + foreach (BenchmarkAxis::cases() as $axis) { + $axisResults = array_values(array_filter( + $results, + fn (BenchmarkResult $result) => $result->profile->axis === $axis + )); + + if ($axisResults === []) { + continue; + } + + $lines[] = ''; + $lines[] = sprintf('## %s (%s)', $axis->label(), $axis->value); + + foreach ($axisResults as $result) { + $lines = array_merge($lines, $this->markdownSection($result)); + } + } + + return implode("\n", $lines)."\n"; + } + + /** + * 결과 1건의 마크다운 절을 만듭니다. + * + * @param BenchmarkResult $result 계측 결과 + * @return array 마크다운 줄 목록 + */ + private function markdownSection(BenchmarkResult $result): array + { + $title = $result->profile->qualifiedKey(); + + if ($result->profile->label !== null) { + $title .= ' — '.$result->profile->label; + } + + $lines = ['', '### '.$title, '']; + + if ($result->skipped) { + $lines[] = '측정하지 않음: '.$result->skipReason; + + return $lines; + } + + $lines[] = '| '.implode(' | ', $result->headers).' |'; + $lines[] = '| '.implode(' | ', array_fill(0, count($result->headers), '------')).' |'; + + foreach ($result->rows as $row) { + $lines[] = '| '.implode(' | ', $row).' |'; + } + + if ($result->notes !== []) { + $lines[] = ''; + + foreach ($result->notes as $note) { + // 실행 계획/SQL 은 표 안에서 깨지므로 코드 스팬으로 감싼다 + $lines[] = '- '.(str_contains($note, '|') ? '`'.$note.'`' : $note); + } + } + + return $lines; + } + + /** + * 옵션 값을 표에 넣을 문자열로 바꿉니다. + * + * @param mixed $value 옵션 값 + * @return string 표시 문자열 + */ + private function stringify(mixed $value): string + { + return match (true) { + is_bool($value) => $value ? 'true' : 'false', + is_array($value) => implode(', ', array_map(fn (mixed $item) => (string) $item, $value)), + $value === null => '-', + default => (string) $value, + }; + } + + /** + * 계측이 사용한 DB 스키마명을 반환합니다. + * + * 읽기/쓰기 분리 설정에서는 스키마명이 `write`/`read` 하위에만 있어 최상위 `database` + * 가 비어 있습니다. 리포트에 스키마가 비면 어느 DB 를 잰 것인지 알 수 없으므로 + * 세 위치를 순서대로 확인합니다. + * + * @return string 스키마명 (확인 불가 시 '-') + */ + private function databaseName(): string + { + $connection = (string) config('database.default'); + + foreach (['database', 'write.database', 'read.database'] as $key) { + $name = config("database.connections.{$connection}.{$key}"); + + if (is_string($name) && $name !== '') { + return $name; + } + } + + return '-'; + } + + /** + * DB 서버 버전을 조회합니다. + * + * @return string DB 버전 (조회 실패 시 '-') + */ + private function databaseVersion(): string + { + try { + $row = DB::selectOne('select version() as version'); + + return (string) ($row->version ?? '-'); + } catch (\Throwable) { + return '-'; + } + } +} diff --git a/app/Benchmark/Contracts/BenchmarkAxisRunner.php b/app/Benchmark/Contracts/BenchmarkAxisRunner.php new file mode 100644 index 00000000..1c264b26 --- /dev/null +++ b/app/Benchmark/Contracts/BenchmarkAxisRunner.php @@ -0,0 +1,34 @@ + $options 축 고유 옵션 + * @param string|null $label 표시용 설명 + */ + public function __construct( + public string $key, + public BenchmarkAxis $axis, + public string $sourceKind, + public string $sourceIdentifier, + public array $options = [], + public ?string $label = null, + ) {} + + /** + * 출처를 포함한 전역 고유 키를 반환합니다. + * + * 서로 다른 확장이 같은 키(`orders` 등)를 선언할 수 있으므로 충돌 시에는 이 키로 + * 지목합니다. 커맨드는 짧은 키가 유일할 때만 짧은 키를 허용합니다. + * + * @return string `{출처}/{키}` 형태의 정규화 키 + */ + public function qualifiedKey(): string + { + return $this->sourceIdentifier.'/'.$this->key; + } + + /** + * 축 고유 옵션 값을 읽습니다. + * + * @param string $name 옵션 키 + * @param mixed $default 미선언 시 기본값 + * @return mixed 옵션 값 + */ + public function option(string $name, mixed $default = null): mixed + { + return $this->options[$name] ?? $default; + } + + /** + * 이 프로파일 실행이 데이터를 변경하는지 판정합니다. + * + * 축 기본값을 쓰되, 프로파일이 `mutating` 을 명시하면 그 선언을 따릅니다. + * `screen` 축은 GET 이 아니면 변경으로 봅니다. + * + * @return bool 데이터 변경 여부 + */ + public function mutates(): bool + { + $declared = $this->options['mutating'] ?? null; + + if (is_bool($declared)) { + return $declared; + } + + if ($this->axis === BenchmarkAxis::Screen) { + return strtoupper((string) $this->option('method', 'GET')) !== 'GET'; + } + + return $this->axis->mutatesByDefault(); + } + + /** + * 배열로 직렬화합니다. (`--json` / 리포트 출력용) + * + * @return array 직렬화 결과 + */ + public function toArray(): array + { + return [ + 'key' => $this->key, + 'qualified_key' => $this->qualifiedKey(), + 'axis' => $this->axis->value, + 'source' => ['kind' => $this->sourceKind, 'identifier' => $this->sourceIdentifier], + 'label' => $this->label, + 'options' => $this->options, + ]; + } +} diff --git a/app/Benchmark/DTO/BenchmarkResult.php b/app/Benchmark/DTO/BenchmarkResult.php new file mode 100644 index 00000000..a39170ca --- /dev/null +++ b/app/Benchmark/DTO/BenchmarkResult.php @@ -0,0 +1,68 @@ + $headers 표 헤더 + * @param array> $rows 표 행 (표시용 문자열) + * @param array $metrics 기계 판독용 원시 수치 + * @param array $notes 부가 설명 줄 (실행 계획, N+1 후보 등) + * @param bool $skipped 실행하지 않았는지 여부 + * @param string|null $skipReason 실행하지 않은 사유 + */ + public function __construct( + public BenchmarkProfile $profile, + public array $headers = [], + public array $rows = [], + public array $metrics = [], + public array $notes = [], + public bool $skipped = false, + public ?string $skipReason = null, + ) {} + + /** + * 실행하지 않은 결과를 만듭니다. + * + * 실패/건너뜀을 결과 목록에서 빼면 리포트가 "전부 측정됨"으로 읽히므로, 사유를 담은 + * 결과로 남겨 표와 JSON 양쪽에 드러냅니다. + * + * @param BenchmarkProfile $profile 대상 프로파일 + * @param string $reason 건너뛴 사유 + * @return self 건너뜀 결과 + */ + public static function skipped(BenchmarkProfile $profile, string $reason): self + { + return new self(profile: $profile, skipped: true, skipReason: $reason); + } + + /** + * 배열로 직렬화합니다. + * + * @return array 직렬화 결과 + */ + public function toArray(): array + { + return [ + 'profile' => $this->profile->qualifiedKey(), + 'axis' => $this->profile->axis->value, + 'label' => $this->profile->label, + 'source' => ['kind' => $this->profile->sourceKind, 'identifier' => $this->profile->sourceIdentifier], + 'skipped' => $this->skipped, + 'skip_reason' => $this->skipReason, + 'headers' => $this->headers, + 'rows' => $this->rows, + 'metrics' => $this->metrics, + 'notes' => $this->notes, + ]; + } +} diff --git a/app/Benchmark/DTO/BenchmarkRunOptions.php b/app/Benchmark/DTO/BenchmarkRunOptions.php new file mode 100644 index 00000000..f93366b3 --- /dev/null +++ b/app/Benchmark/DTO/BenchmarkRunOptions.php @@ -0,0 +1,52 @@ + $offsets 측정할 OFFSET 목록 (list 축) + * @param int $runs 측정 횟수 (첫 회는 버림) + * @param int $seed 계측 전 합성 행 시딩 건수 (0 = 시딩 안 함, list 축) + * @param bool $fresh 시딩 전 대상 테이블 비움 (list 축) + * @param bool $explain 실행 계획 수집 (list 축) + * @param bool $allowWrite 데이터 변경 축 실행 허용 + * @param string|null $asUser 계측에 사용할 기존 계정 (ID 또는 이메일, screen 축) + * @param int $perPage 목록/화면 1페이지 건수 + */ + public function __construct( + public array $offsets = [0], + public int $runs = 3, + public int $seed = 0, + public bool $fresh = false, + public bool $explain = false, + public bool $allowWrite = false, + public ?string $asUser = null, + public int $perPage = 20, + ) {} + + /** + * 배열로 직렬화합니다. (리포트의 실행 조건 기재용) + * + * @return array 직렬화 결과 + */ + public function toArray(): array + { + return [ + 'offsets' => $this->offsets, + 'runs' => $this->runs, + 'seed' => $this->seed, + 'fresh' => $this->fresh, + 'explain' => $this->explain, + 'allow_write' => $this->allowWrite, + 'as_user' => $this->asUser, + 'per_page' => $this->perPage, + ]; + } +} diff --git a/app/Benchmark/QueryCollector.php b/app/Benchmark/QueryCollector.php new file mode 100644 index 00000000..9d51691e --- /dev/null +++ b/app/Benchmark/QueryCollector.php @@ -0,0 +1,102 @@ + + */ + private array $queries = []; + + public function __construct() + { + DB::listen(function (QueryExecuted $event) { + if (! $this->collecting) { + return; + } + + $this->queries[] = ['sql' => $event->sql, 'time' => (float) $event->time]; + }); + } + + /** + * 콜백 실행 구간의 쿼리를 수집합니다. + * + * @template TReturn + * + * @param \Closure(): TReturn $callback 계측 대상 작업 + * @return array{value: TReturn, queries: array} 반환값과 수집 결과 + */ + public function collect(\Closure $callback): array + { + $this->queries = []; + $this->collecting = true; + + try { + $value = $callback(); + } finally { + $this->collecting = false; + } + + return ['value' => $value, 'queries' => $this->queries]; + } + + /** + * 수집된 쿼리를 요약합니다. + * + * @param array $queries 수집 결과 + * @return array{count: int, db_ms: float, n_plus_one: array} 요약 + */ + public function summarize(array $queries): array + { + $grouped = []; + + foreach ($queries as $query) { + $grouped[$query['sql']] = ($grouped[$query['sql']] ?? 0) + 1; + } + + arsort($grouped); + + $candidates = []; + + foreach ($grouped as $sql => $count) { + if ($count < self::N_PLUS_ONE_THRESHOLD) { + continue; + } + + $candidates[] = ['count' => $count, 'sql' => $sql]; + } + + return [ + 'count' => count($queries), + 'db_ms' => round(array_sum(array_column($queries, 'time')), 2), + 'n_plus_one' => $candidates, + ]; + } +} diff --git a/app/Benchmark/SyntheticSeeder.php b/app/Benchmark/SyntheticSeeder.php new file mode 100644 index 00000000..71671297 --- /dev/null +++ b/app/Benchmark/SyntheticSeeder.php @@ -0,0 +1,190 @@ + $overrides 컬럼별 고정값 (실제 조회 조건과 맞추기 위한 지정) + * @param \Closure|null $onProgress 진행 콜백 (int $inserted, int $total) + */ + public function seed(string $table, int $count, array $overrides = [], ?\Closure $onProgress = null): void + { + $fillable = $this->fillableColumns($table); + + $chunk = []; + $inserted = 0; + + for ($i = 1; $i <= $count; $i++) { + $chunk[] = $this->synthesizeRow($table, $fillable, $overrides, $i); + + if (count($chunk) >= self::CHUNK_SIZE) { + $this->insert($table, $chunk); + $inserted += count($chunk); + $chunk = []; + + if ($onProgress !== null) { + $onProgress($inserted, $count); + } + } + } + + if ($chunk !== []) { + $this->insert($table, $chunk); + $inserted += count($chunk); + + if ($onProgress !== null) { + $onProgress($inserted, $count); + } + } + } + + /** + * 대상 테이블을 비웁니다. + * + * 다른 테이블이 FK 로 참조하면 TRUNCATE 가 거부되므로 제약을 잠시 내립니다. + * + * @param string $table 대상 테이블 + */ + public function truncate(string $table): void + { + Schema::withoutForeignKeyConstraints(function () use ($table) { + DB::table($table)->truncate(); + }); + } + + /** + * 값을 반드시 채워야 하는 컬럼 목록을 반환합니다. + * + * @param string $table 대상 테이블 + * @return array> Schema::getColumns() 항목 목록 + */ + private function fillableColumns(string $table): array + { + return array_values(array_filter( + Schema::getColumns($table), + fn (array $column) => ! ($column['auto_increment'] ?? false) + && ! $column['nullable'] + && ($column['default'] === null) + )); + } + + /** + * 합성 행 1건을 만듭니다. + * + * @param string $table 대상 테이블 + * @param array> $fillable 채워야 하는 컬럼 목록 + * @param array $overrides 컬럼별 고정값 + * @param int $index 행 인덱스 (고유값 생성용) + * @return array 합성 행 + */ + private function synthesizeRow(string $table, array $fillable, array $overrides, int $index): array + { + $row = []; + + foreach ($fillable as $column) { + $row[$column['name']] = array_key_exists($column['name'], $overrides) + ? $overrides[$column['name']] + : $this->synthesizeValue($column, $index); + } + + // NULL 허용 컬럼이라 위 루프에서 빠졌더라도, 실제 조회 조건에 쓰이는 컬럼은 + // 채워야 계측이 화면과 같은 인덱스를 타게 된다. + foreach ($overrides as $name => $value) { + if (! array_key_exists($name, $row) && Schema::hasColumn($table, $name)) { + $row[$name] = $value; + } + } + + return $row; + } + + /** + * 합성 행을 삽입합니다. (외래키 제약 일시 해제) + * + * @param string $table 대상 테이블 + * @param array> $chunk 삽입할 행 묶음 + */ + private function insert(string $table, array $chunk): void + { + Schema::withoutForeignKeyConstraints(function () use ($table, $chunk) { + DB::table($table)->insert($chunk); + }); + } + + /** + * 컬럼 타입에 맞는 합성 값을 만듭니다. + * + * @param array $column Schema::getColumns() 항목 + * @param int $index 행 인덱스 (고유값 생성용) + * @return mixed 합성 값 + */ + private function synthesizeValue(array $column, int $index): mixed + { + $type = strtolower((string) ($column['type_name'] ?? $column['type'] ?? 'varchar')); + + return match (true) { + str_contains($type, 'int') => $index, + str_contains($type, 'decimal'), str_contains($type, 'float'), str_contains($type, 'double') => 1000, + str_contains($type, 'bool'), str_contains($type, 'tinyint') => 0, + str_contains($type, 'json') => '{}', + // 시간 컬럼을 한 값으로 채우면 정렬이 전부 동률이 되어 filesort 가 지배해 버린다. + // 정렬 인덱스 효과를 재는 것이 목적이므로 행마다 값을 분산시킨다. + str_contains($type, 'date'), str_contains($type, 'time') => now()->subMinutes($index), + str_contains($type, 'text') => str_repeat('벤치마크 본문 ', 100), + default => $this->synthesizeString($column, $index), + }; + } + + /** + * 문자열 컬럼의 선언 길이에 맞는 합성 값을 만듭니다. + * + * 컬럼 길이를 무시하고 고정 길이 문자열을 넣으면 `varchar(10)` 류(신고 대상 타입, + * 우편번호, 로그 타입 등)에서 "Data too long" 으로 시딩 자체가 실패한다. + * + * @param array $column Schema::getColumns() 항목 + * @param int $index 행 인덱스 (고유값 생성용) + * @return string 합성 문자열 + */ + private function synthesizeString(array $column, int $index): string + { + $length = 40; + + // type 은 'varchar(10)' 형태로 선언 길이를 담고 있다 + if (preg_match('/\((\d+)\)/', (string) ($column['type'] ?? ''), $m) === 1) { + $length = max(1, (int) $m[1]); + } + + // 짧은 컬럼은 고유성 확보가 우선이라 인덱스 자체를 문자열로 쓴다 + if ($length <= 12) { + return substr((string) $index, -$length); + } + + return substr("bench-{$index}-".Str::random(8), 0, $length); + } +} diff --git a/app/Console/Commands/BenchCommand.php b/app/Console/Commands/BenchCommand.php new file mode 100644 index 00000000..45618db1 --- /dev/null +++ b/app/Console/Commands/BenchCommand.php @@ -0,0 +1,395 @@ + $runners 축 실행기 목록 (컨테이너 태그 주입) + */ + public function __construct( + private readonly BenchmarkProfileRegistry $registry, + private readonly BenchmarkReporter $reporter, + private readonly iterable $runners, + ) { + parent::__construct(); + } + + /** + * 커맨드를 실행합니다. + * + * @return int 종료 코드 + */ + public function handle(): int + { + $this->applyDatabaseOverride(); + + if ($this->option('list-profiles')) { + return $this->listProfiles(); + } + + [$profiles, $selectionError] = $this->selectProfiles(); + + if ($selectionError !== null) { + $this->error($selectionError); + + return self::FAILURE; + } + + if ($profiles === []) { + $this->error('계측할 프로파일이 없습니다. --profile / --axis / --all 중 하나를 지정하세요.'); + + return self::FAILURE; + } + + $options = $this->runOptions(); + + // 계측 결과가 환경에 좌우되므로 실행 환경을 먼저 드러낸다 + if (! $this->option('json')) { + foreach ($this->reporter->environment() as $name => $value) { + $this->line(sprintf(' %-14s %s', $name, $value)); + } + + $this->newLine(); + } + + $this->reportCollectionWarnings(); + + $results = []; + + foreach ($profiles as $profile) { + $results[] = $this->runProfile($profile, $options); + } + + return $this->output($results, $options); + } + + /** + * 계측용 데이터베이스 지정을 반영합니다. + * + * 계측은 대량 시딩을 동반하므로 개발 DB 대신 폐기 가능한 DB 를 지정할 수 있어야 합니다. + * config 가 캐시된 환경에서는 `.env` / `--env` 로 연결을 바꿀 수 없어 런타임 치환이 + * 유일한 수단입니다. + */ + private function applyDatabaseOverride(): void + { + $database = (string) $this->option('database'); + + if ($database === '') { + return; + } + + $connection = config('database.default'); + + config([ + "database.connections.{$connection}.database" => $database, + "database.connections.{$connection}.write.database" => $database, + "database.connections.{$connection}.read.database" => $database, + ]); + + DB::purge($connection); + } + + /** + * 등록된 프로파일 목록을 출력합니다. + * + * @return int 종료 코드 + */ + private function listProfiles(): int + { + $profiles = $this->registry->all(); + + if ($this->option('json')) { + $this->line((string) json_encode( + array_map(fn (BenchmarkProfile $profile) => $profile->toArray(), array_values($profiles)), + JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES + )); + + return self::SUCCESS; + } + + $this->table( + ['프로파일', '축', '출처', '설명'], + array_map(fn (BenchmarkProfile $profile) => [ + $profile->qualifiedKey(), + $profile->axis->value, + $profile->sourceKind, + $profile->label ?? '-', + ], array_values($profiles)) + ); + + $this->reportCollectionWarnings(); + + return self::SUCCESS; + } + + /** + * 실행할 프로파일을 고릅니다. + * + * @return array{0: array, 1: string|null} [실행 대상, 실패 사유] + */ + private function selectProfiles(): array + { + $keys = array_filter((array) $this->option('profile'), 'strlen'); + + if ($keys !== []) { + $selected = []; + + foreach ($keys as $key) { + [$profile, $error] = $this->registry->resolve((string) $key); + + if ($error !== null) { + return [[], $error]; + } + + $selected[] = $profile; + } + + return [$selected, null]; + } + + $axisOption = (string) $this->option('axis'); + + if ($axisOption !== '') { + $axis = BenchmarkAxis::tryFrom($axisOption); + + if ($axis === null) { + return [[], "알 수 없는 축: {$axisOption} (허용: ".implode('|', BenchmarkAxis::values()).')']; + } + + return [array_values($this->registry->byAxis($axis)), null]; + } + + return [$this->option('all') ? array_values($this->registry->all()) : [], null]; + } + + /** + * 커맨드 옵션을 실행 옵션으로 해석합니다. + * + * @return BenchmarkRunOptions 실행 옵션 + */ + private function runOptions(): BenchmarkRunOptions + { + $asUser = (string) $this->option('as'); + + return new BenchmarkRunOptions( + offsets: $this->parseOffsets(), + runs: max(1, (int) $this->option('runs')), + seed: max(0, (int) $this->option('seed')), + fresh: (bool) $this->option('fresh'), + explain: (bool) $this->option('explain'), + allowWrite: (bool) $this->option('allow-write'), + asUser: $asUser === '' ? null : $asUser, + perPage: max(1, (int) $this->option('per-page')), + ); + } + + /** + * OFFSET 목록을 파싱합니다. + * + * @return array 오름차순 정렬된 OFFSET 목록 + */ + private function parseOffsets(): array + { + $offsets = array_map('intval', array_filter(explode(',', (string) $this->option('offsets')), 'strlen')); + sort($offsets); + + return $offsets === [] ? [0] : $offsets; + } + + /** + * 프로파일 1건을 계측합니다. + * + * @param BenchmarkProfile $profile 대상 프로파일 + * @param BenchmarkRunOptions $options 실행 옵션 + * @return BenchmarkResult 계측 결과 + */ + private function runProfile(BenchmarkProfile $profile, BenchmarkRunOptions $options): BenchmarkResult + { + $runner = $this->runnerFor($profile->axis); + + if ($runner === null) { + return BenchmarkResult::skipped($profile, "축 실행기가 없습니다: {$profile->axis->value}"); + } + + if (! $this->option('json')) { + $this->line(sprintf('▶ %s (%s)', $profile->qualifiedKey(), $profile->axis->label())); + } + + $onProgress = $this->option('json') + ? null + : fn (string $message) => $this->line(' '.$message); + + return $runner->run($profile, $options, $onProgress); + } + + /** + * 축에 대응하는 실행기를 찾습니다. + * + * @param BenchmarkAxis $axis 대상 축 + * @return BenchmarkAxisRunner|null 실행기 (없으면 null) + */ + private function runnerFor(BenchmarkAxis $axis): ?BenchmarkAxisRunner + { + foreach ($this->runners as $runner) { + if ($runner->axis() === $axis) { + return $runner; + } + } + + return null; + } + + /** + * 무시된 프로파일 선언을 알립니다. + * + * 선언 오류를 조용히 버리면 그 대상이 계측 사각으로 남으므로 매 실행에 드러냅니다. + */ + private function reportCollectionWarnings(): void + { + if ($this->option('json')) { + return; + } + + foreach ($this->registry->warnings() as $warning) { + $this->warn('무시된 프로파일 선언 — '.$warning); + } + } + + /** + * 계측 결과를 출력합니다. + * + * @param array $results 계측 결과 + * @param BenchmarkRunOptions $options 실행 옵션 + * @return int 종료 코드 + */ + private function output(array $results, BenchmarkRunOptions $options): int + { + $warnings = $this->registry->warnings(); + + if ($this->option('json')) { + $this->line($this->reporter->toJson($results, $options, $warnings)); + } else { + foreach ($results as $result) { + $this->newLine(); + $this->line(sprintf('%s (%s)', $result->profile->qualifiedKey(), $result->profile->axis->label())); + + if ($result->skipped) { + $this->warn(' 측정하지 않음: '.$result->skipReason); + + continue; + } + + $this->table($result->headers, $result->rows); + + foreach ($result->notes as $note) { + $this->line(' '.$note); + } + } + } + + if ($this->wantsReport()) { + $path = $this->writeReport($results, $options, $warnings); + $this->newLine(); + $this->info('리포트 저장: '.$path); + } + + // 전부 건너뛴 실행을 성공으로 보고하면 "측정했다"로 읽히므로 실패로 돌려준다 + $measured = array_filter($results, fn (BenchmarkResult $result) => ! $result->skipped); + + return $measured === [] ? self::FAILURE : self::SUCCESS; + } + + /** + * 리포트 출력이 요청되었는지 판정합니다. + * + * `--report` 는 값이 선택이라 `option()` 만으로는 "미지정"과 "값 없이 지정"을 + * 구분할 수 없으므로 원시 입력을 확인합니다. + * + * @return bool 리포트 출력 여부 + */ + private function wantsReport(): bool + { + return $this->input->hasParameterOption('--report') + || $this->input->hasParameterOption('--report=') + || (string) $this->option('report') !== ''; + } + + /** + * 마크다운 리포트를 저장합니다. + * + * @param array $results 계측 결과 + * @param BenchmarkRunOptions $options 실행 옵션 + * @param array $warnings 프로파일 수집 경고 + * @return string 저장된 절대 경로 + */ + private function writeReport(array $results, BenchmarkRunOptions $options, array $warnings): string + { + $given = (string) $this->option('report'); + + $path = $given !== '' + ? $given + : storage_path(self::REPORT_DIR.'/bench-'.now()->format('Ymd-His').'.md'); + + $directory = dirname($path); + + if (! is_dir($directory)) { + mkdir($directory, 0755, true); + } + + file_put_contents($path, $this->reporter->toMarkdown($results, $options, $warnings)); + + return $path; + } +} diff --git a/app/Console/Commands/BenchPaginationCommand.php b/app/Console/Commands/BenchPaginationCommand.php deleted file mode 100644 index ad72b8cd..00000000 --- a/app/Console/Commands/BenchPaginationCommand.php +++ /dev/null @@ -1,556 +0,0 @@ -, order: array, soft_delete: bool}> - */ - private function profiles(): array - { - return [ - 'board_posts' => [ - 'table' => 'board_posts', - 'columns' => ['id', 'board_id', 'title', 'author_name', 'view_count', 'is_notice', 'created_at'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => true, - // 실제 목록은 게시판 하나를 조회한다. 필터 없이 재면 인덱스 선택이 달라져 - // 화면에서 일어나는 일과 다른 것을 재게 된다. - 'filters' => ['board_id' => 1], - 'seed_overrides' => ['board_id' => 1, 'is_notice' => 0], - ], - 'orders' => [ - 'table' => 'ecommerce_orders', - 'columns' => ['id', 'user_id', 'order_number', 'order_status', 'order_device', 'is_first_order', 'currency', 'currency_snapshot', 'total_amount', 'total_shipping_amount', 'total_paid_amount', 'total_cancelled_amount', 'total_refunded_amount', 'total_points_used_amount', 'total_earned_points_amount', 'mc_total_amount', 'mc_total_shipping_amount', 'ordered_at', 'created_at', 'updated_at', 'deleted_at'], - 'order' => [['ordered_at', 'desc'], ['id', 'desc']], - 'soft_delete' => true, - ], - // 아래 두 목록은 응답 계약상 넓은 컬럼(changes/properties, body)을 그대로 노출하므로 - // 컬럼 프루닝이 불가하다. 지연 조인만 적용했고, 계측 축도 select * vs select id 다. - 'activity_logs' => [ - 'table' => 'activity_logs', - 'columns' => ['*'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => false, - ], - 'notification_logs' => [ - 'table' => 'notification_logs', - 'columns' => ['*'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => false, - ], - 'users' => [ - 'table' => 'users', - 'columns' => ['*'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => true, - ], - 'reports' => [ - 'table' => 'boards_reports', - 'columns' => ['*'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => true, - ], - 'coupon_issues' => [ - 'table' => 'ecommerce_promotion_coupon_issues', - 'columns' => ['*'], - 'order' => [['issued_at', 'desc'], ['id', 'desc']], - 'soft_delete' => false, - ], - 'products' => [ - 'table' => 'ecommerce_products', - 'columns' => ['*'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => true, - ], - 'product_inquiries' => [ - 'table' => 'ecommerce_product_inquiries', - 'columns' => ['*'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => true, - ], - 'product_reviews' => [ - 'table' => 'ecommerce_product_reviews', - 'columns' => ['*'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => true, - ], - 'pages' => [ - 'table' => 'pages', - 'columns' => ['*'], - 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'soft_delete' => true, - ], - 'extra_fee_templates' => [ - 'table' => 'ecommerce_shipping_policy_extra_fee_templates', - 'columns' => ['*'], - 'order' => [['zipcode', 'asc'], ['id', 'asc']], - 'soft_delete' => false, - ], - ]; - } - - /** - * 커맨드를 실행합니다. - * - * @return int 종료 코드 - */ - public function handle(): int - { - $profiles = $this->profiles(); - - if ($this->option('list-profiles')) { - foreach ($profiles as $key => $profile) { - $this->line(sprintf('%-20s %s', $key, $profile['table'])); - } - - return self::SUCCESS; - } - - $key = (string) $this->option('table'); - - if (! isset($profiles[$key])) { - $this->error("등록되지 않은 프로파일: {$key} (--list-profiles 로 목록 확인)"); - - return self::FAILURE; - } - - $profile = $profiles[$key]; - - // 계측은 대량 시딩을 동반하므로 개발 DB 대신 폐기 가능한 DB 를 지정할 수 있어야 한다. - // config 가 캐시된 환경에서는 .env / --env 로 연결을 바꿀 수 없어 런타임 치환이 유일한 수단이다. - $database = (string) $this->option('database'); - - if ($database !== '') { - $connection = config('database.default'); - - config([ - "database.connections.{$connection}.database" => $database, - "database.connections.{$connection}.write.database" => $database, - "database.connections.{$connection}.read.database" => $database, - ]); - - DB::purge($connection); - } - - if (! Schema::hasTable($profile['table'])) { - $this->error("테이블이 없습니다: {$profile['table']} (해당 확장이 설치되어 있는지 확인)"); - - return self::FAILURE; - } - - // 계측 결과가 환경에 좌우되므로 실행 환경을 먼저 드러낸다 - $this->line(sprintf( - '환경: APP_ENV=%s / DB=%s / config:cache=%s', - config('app.env'), - config('database.default'), - app()->configurationIsCached() ? 'on' : 'off' - )); - - if ($this->option('fresh')) { - if (app()->environment('production')) { - $this->error('운영 환경에서는 --fresh 를 사용할 수 없습니다.'); - - return self::FAILURE; - } - - // 다른 테이블이 FK 로 참조하면 TRUNCATE 가 거부되므로 제약을 잠시 내린다 - Schema::withoutForeignKeyConstraints(function () use ($profile) { - DB::table($profile['table'])->truncate(); - }); - - $this->warn("비움: {$profile['table']}"); - } - - $seed = (int) $this->option('seed'); - - if ($seed > 0) { - $this->seedRows($profile['table'], $seed, $profile['seed_overrides'] ?? []); - } - - $columns = $this->existingColumns($profile['table'], $profile['columns']); - $order = $this->existingOrder($profile['table'], $profile['order']); - $offsets = $this->parseOffsets(); - $runs = max(1, (int) $this->option('runs')); - - $rows = []; - - foreach ($offsets as $offset) { - // 세 축: 컬럼 인자를 생략했을 때(=select *) / 목록 컬럼만 / 키 컬럼만. - // 첫 축이 프루닝 이전 상태, 셋째 축이 지연 조인의 inner 에 해당한다. - $allMs = $this->measure($profile, ['*'], $order, $offset, $runs); - $listMs = $this->measure($profile, $columns, $order, $offset, $runs); - $idOnlyMs = $this->measure($profile, ['id'], $order, $offset, $runs); - - $rows[] = [ - 'offset' => $offset, - 'all_ms' => $allMs, - 'list_ms' => $listMs, - 'id_only_ms' => $idOnlyMs, - 'ratio' => $idOnlyMs > 0 ? round($allMs / $idOnlyMs, 1) : null, - 'explain' => $this->option('explain') ? $this->explain($profile, $columns, $order, $offset) : null, - // 지연 조인의 inner 가 실제로 어떤 계획을 타는지가 인덱스 설계의 근거다 - 'explain_id_only' => $this->option('explain') ? $this->explain($profile, ['id'], $order, $offset) : null, - ]; - } - - if ($this->option('json')) { - $this->line(json_encode([ - 'profile' => $key, - 'table' => $profile['table'], - 'columns' => $columns, - 'runs' => $runs, - 'results' => $rows, - ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); - - return self::SUCCESS; - } - - $this->table( - ['OFFSET', '전체 컬럼(ms)', '목록 컬럼(ms)', 'ID만(ms)', '전체÷ID'], - array_map(fn (array $row) => [ - number_format($row['offset']), - number_format($row['all_ms'], 1), - number_format($row['list_ms'], 1), - number_format($row['id_only_ms'], 1), - $row['ratio'] !== null ? $row['ratio'].'×' : '-', - ], $rows) - ); - - if ($this->option('explain')) { - foreach ($rows as $row) { - $this->newLine(); - $this->line("EXPLAIN @ OFFSET {$row['offset']} — 목록 컬럼"); - - foreach ($row['explain'] as $line) { - $this->line(' '.$line); - } - - $this->line("EXPLAIN @ OFFSET {$row['offset']} — ID 만 (지연 조인 inner)"); - - foreach ($row['explain_id_only'] as $line) { - $this->line(' '.$line); - } - } - } - - return self::SUCCESS; - } - - /** - * OFFSET 목록을 파싱합니다. - * - * @return array 오름차순 정렬된 OFFSET 목록 - */ - private function parseOffsets(): array - { - $offsets = array_map('intval', array_filter(explode(',', (string) $this->option('offsets')), 'strlen')); - sort($offsets); - - return $offsets === [] ? [0] : $offsets; - } - - /** - * 스키마에 실제로 존재하는 컬럼만 남깁니다. - * - * @param string $table 테이블명 - * @param array $columns 후보 컬럼 - * @return array 존재하는 컬럼 목록 - */ - private function existingColumns(string $table, array $columns): array - { - // ['*'] 는 "목록이 전 컬럼을 그대로 노출한다" 는 선언이다. 응답 계약상 넓은 컬럼을 - // 뺄 수 없는 목록(활동 로그의 changes, 알림 발송 이력의 body 등)이 여기 해당하며, - // 이 경우 계측의 비교 축은 "select * vs select id" 가 된다. - if ($columns === ['*']) { - return Schema::getColumnListing($table); - } - - $existing = array_values(array_filter($columns, fn (string $column) => Schema::hasColumn($table, $column))); - - return $existing === [] ? ['id'] : $existing; - } - - /** - * 스키마에 존재하는 정렬 컬럼만 남깁니다. - * - * @param string $table 테이블명 - * @param array $order 후보 정렬 - * @return array 적용 가능한 정렬 목록 - */ - private function existingOrder(string $table, array $order): array - { - $existing = array_values(array_filter($order, fn (array $spec) => Schema::hasColumn($table, $spec[0]))); - - return $existing === [] ? [['id', 'desc']] : $existing; - } - - /** - * 한 조합을 여러 번 실행해 중앙값(ms)을 돌려줍니다. - * - * DB::enableQueryLog() 는 오버헤드와 메모리 누적이 있어 쓰지 않고 직접 시간을 잰다. - * - * @param array $profile 프로파일 정의 - * @param array $columns select 컬럼 - * @param array $order 정렬 - * @param int $offset OFFSET - * @param int $runs 측정 횟수 - * @return float 중앙값 (밀리초) - */ - private function measure(array $profile, array $columns, array $order, int $offset, int $runs): float - { - // 첫 회는 캐시 워밍 성격이라 버린다 - $this->runQuery($profile, $columns, $order, $offset); - - $samples = []; - - for ($i = 0; $i < $runs; $i++) { - $start = microtime(true); - $this->runQuery($profile, $columns, $order, $offset); - $samples[] = (microtime(true) - $start) * 1000; - } - - sort($samples); - - return $samples[intdiv(count($samples), 2)]; - } - - /** - * 계측 대상 쿼리를 실행합니다. - * - * @param array $profile 프로파일 정의 - * @param array $columns select 컬럼 - * @param array $order 정렬 - * @param int $offset OFFSET - */ - private function runQuery(array $profile, array $columns, array $order, int $offset): void - { - $this->buildQuery($profile, $columns, $order, $offset)->get(); - } - - /** - * 계측 대상 쿼리를 조립합니다. - * - * @param array $profile 프로파일 정의 - * @param array $columns select 컬럼 - * @param array $order 정렬 - * @param int $offset OFFSET - * @return Builder 조립된 쿼리 - */ - private function buildQuery(array $profile, array $columns, array $order, int $offset) - { - $query = DB::table($profile['table'])->select($columns); - - if ($profile['soft_delete'] && Schema::hasColumn($profile['table'], 'deleted_at')) { - $query->whereNull('deleted_at'); - } - - foreach ($profile['filters'] ?? [] as $column => $value) { - if (Schema::hasColumn($profile['table'], $column)) { - $query->where($column, $value); - } - } - - foreach ($order as $spec) { - $query->orderBy($spec[0], $spec[1]); - } - - return $query->offset($offset)->limit(20); - } - - /** - * 실행 계획을 수집합니다. - * - * @param array $profile 프로파일 정의 - * @param array $columns select 컬럼 - * @param array $order 정렬 - * @param int $offset OFFSET - * @return array 실행 계획 요약 줄 목록 - */ - private function explain(array $profile, array $columns, array $order, int $offset): array - { - $query = $this->buildQuery($profile, $columns, $order, $offset); - - try { - $plan = DB::select('EXPLAIN '.$query->toSql(), $query->getBindings()); - } catch (\Throwable $e) { - return ['실행 계획 수집 실패: '.$e->getMessage()]; - } - - return array_map(function ($row) { - $row = (array) $row; - - return sprintf( - 'type=%s key=%s rows=%s filtered=%s extra=%s', - $row['type'] ?? '-', - $row['key'] ?? '-', - $row['rows'] ?? '-', - $row['filtered'] ?? '-', - $row['Extra'] ?? '-' - ); - }, $plan); - } - - /** - * 계측용 합성 행을 시딩합니다. - * - * 스키마를 introspect 해 NOT NULL + 기본값 없는 컬럼만 타입에 맞춰 채운다. 프로파일마다 - * 별도 시더를 두면 대상이 늘 때마다 시더가 따라 늘어나므로 일반화한다. - * - * @param string $table 대상 테이블 - * @param int $count 시딩 건수 - * @param array $overrides 컬럼별 고정값 (실제 조회 조건과 맞추기 위한 지정) - */ - private function seedRows(string $table, int $count, array $overrides = []): void - { - $columns = Schema::getColumns($table); - $fillable = array_values(array_filter( - $columns, - fn (array $column) => ! ($column['auto_increment'] ?? false) - && ! $column['nullable'] - && ($column['default'] === null) - )); - - $this->info("시딩: {$table} {$count} 건"); - $bar = $this->output->createProgressBar($count); - - $chunk = []; - $chunkSize = 500; - - for ($i = 1; $i <= $count; $i++) { - $row = []; - - foreach ($fillable as $column) { - $row[$column['name']] = array_key_exists($column['name'], $overrides) - ? $overrides[$column['name']] - : $this->synthesizeValue($column, $i); - } - - foreach ($overrides as $name => $value) { - if (! array_key_exists($name, $row) && Schema::hasColumn($table, $name)) { - $row[$name] = $value; - } - } - - $chunk[] = $row; - - if (count($chunk) >= $chunkSize) { - $this->insertSynthetic($table, $chunk); - $bar->advance(count($chunk)); - $chunk = []; - } - } - - if ($chunk !== []) { - $this->insertSynthetic($table, $chunk); - $bar->advance(count($chunk)); - } - - $bar->finish(); - $this->newLine(2); - } - - /** - * 합성 행을 삽입합니다. (외래키 제약 일시 해제) - * - * 합성 값은 실제 부모 행을 가리키지 않으므로 FK 를 그대로 두면 삽입이 거부된다. - * 본 커맨드가 재는 것은 대상 테이블의 OFFSET 스캔 비용이고 조인은 측정 대상이 아니므로, - * 부모 무결성 없이 행 수와 행 폭만 재현하면 목적에 충분하다. 폐기 가능한 계측용 DB - * (`--database`)에서만 쓰는 것을 전제로 한다. - * - * @param string $table 대상 테이블 - * @param array> $chunk 삽입할 행 묶음 - */ - private function insertSynthetic(string $table, array $chunk): void - { - Schema::withoutForeignKeyConstraints(function () use ($table, $chunk) { - DB::table($table)->insert($chunk); - }); - } - - /** - * 컬럼 타입에 맞는 합성 값을 만듭니다. - * - * @param array $column Schema::getColumns() 항목 - * @param int $index 행 인덱스 (고유값 생성용) - * @return mixed 합성 값 - */ - private function synthesizeValue(array $column, int $index): mixed - { - $type = strtolower((string) ($column['type_name'] ?? $column['type'] ?? 'varchar')); - - return match (true) { - str_contains($type, 'int') => $index, - str_contains($type, 'decimal'), str_contains($type, 'float'), str_contains($type, 'double') => 1000, - str_contains($type, 'bool'), str_contains($type, 'tinyint') => 0, - str_contains($type, 'json') => '{}', - // 시간 컬럼을 한 값으로 채우면 정렬이 전부 동률이 되어 filesort 가 지배해 버린다. - // 정렬 인덱스 효과를 재는 것이 목적이므로 행마다 값을 분산시킨다. - str_contains($type, 'date'), str_contains($type, 'time') => now()->subMinutes($index), - str_contains($type, 'text') => str_repeat('벤치마크 본문 ', 100), - default => $this->synthesizeString($column, $index), - }; - } - - /** - * 문자열 컬럼의 선언 길이에 맞는 합성 값을 만듭니다. - * - * 컬럼 길이를 무시하고 고정 길이 문자열을 넣으면 `varchar(10)` 류(신고 대상 타입, - * 우편번호, 로그 타입 등)에서 "Data too long" 으로 시딩 자체가 실패한다. - * - * @param array $column Schema::getColumns() 항목 - * @param int $index 행 인덱스 (고유값 생성용) - * @return string 합성 문자열 - */ - private function synthesizeString(array $column, int $index): string - { - $length = 40; - - // type 은 'varchar(10)' 형태로 선언 길이를 담고 있다 - if (preg_match('/\((\d+)\)/', (string) ($column['type'] ?? ''), $m) === 1) { - $length = max(1, (int) $m[1]); - } - - // 짧은 컬럼은 고유성 확보가 우선이라 인덱스 자체를 문자열로 쓴다 - if ($length <= 12) { - return substr((string) $index, -$length); - } - - return substr("bench-{$index}-".Str::random(8), 0, $length); - } -} diff --git a/app/Enums/BenchmarkAxis.php b/app/Enums/BenchmarkAxis.php new file mode 100644 index 00000000..9b6bc53d --- /dev/null +++ b/app/Enums/BenchmarkAxis.php @@ -0,0 +1,93 @@ + '목록 조회', + self::Screen => '화면 응답', + self::Write => '쓰기 작업', + self::Batch => '배치 작업', + }; + } + + /** + * 프로파일 선언에서 반드시 채워야 하는 옵션 키 목록 + * + * 각 원소는 "대안 그룹"이며, 그룹마다 최소 하나가 선언되어야 합니다 + * (`screen` 축은 라우트명 또는 URI 중 하나). 레지스트리가 이 목록으로 선언을 + * 검증하며, 누락된 선언은 사유와 함께 경고로 드러내고 목록에서 제외합니다 + * (조용히 버리면 계측 사각이 됩니다). + * + * @return array> 필수 옵션 대안 그룹 목록 + */ + public function requiredOptions(): array + { + return match ($this) { + self::ListQuery => [['table']], + self::Screen => [['route', 'uri']], + self::Write => [['callback']], + self::Batch => [['command']], + }; + } + + /** + * 기본적으로 데이터를 변경하는 축인지 여부 + * + * true 인 축은 `--allow-write` 없이는 실행을 거부합니다. `screen` 축은 선언한 + * HTTP 메서드에 따라 달라지므로 프로파일 단위로 다시 판정합니다. + * + * @return bool 데이터 변경 축 여부 + */ + public function mutatesByDefault(): bool + { + return match ($this) { + self::ListQuery, self::Screen => false, + self::Write, self::Batch => true, + }; + } + + /** + * 모든 값 배열 + * + * @return array 축 값 목록 + */ + public static function values(): array + { + return array_column(self::cases(), 'value'); + } +} diff --git a/app/Extension/AbstractModule.php b/app/Extension/AbstractModule.php index c0347f53..66fea241 100644 --- a/app/Extension/AbstractModule.php +++ b/app/Extension/AbstractModule.php @@ -749,6 +749,38 @@ abstract class AbstractModule implements CacheableExtensionInterface, ModuleInte return []; } + /** + * 성능 계측 프로파일 정의를 반환합니다. + * + * 이 모듈이 소유한 목록/화면/저장 경로/배치 중 성능을 재고 싶은 대상을 선언합니다. + * `g7:bench` 커맨드가 코어 `config/benchmark.php` 선언과 함께 수집합니다 + * (`App\Benchmark\BenchmarkProfileRegistry`). 계측 대상을 코어 커맨드에 하드코딩하지 + * 않는 이유는, 확장이 설치·제거되는 설치본마다 실제로 존재하는 대상이 다르기 + * 때문입니다. 키는 모듈 내부에서만 고유하면 되고, 다른 확장과 겹치면 커맨드가 + * `{식별자}/{키}` 로 지목합니다. + * + * `write` 축의 `callback` 은 클로저를 쓸 수 없습니다 — 코어 선언과 스키마를 공유하고 + * 코어 쪽은 `config:cache` 대상이므로, 형식을 `'Fqcn'`(invokable) 또는 + * `['Fqcn', 'method']` 로 통일합니다. + * + * @return array> 프로파일 키 → 정의 + * [ + * 'orders' => [ + * 'type' => 'list', // list | screen | write | batch + * 'label' => '주문 목록', + * 'table' => 'ecommerce_orders', + * 'columns' => ['id', 'order_number', ...], + * 'order' => [['ordered_at', 'desc']], + * 'filters' => ['order_status' => 'paid'], + * 'soft_delete' => true, + * ], + * ] + */ + public function getBenchmarkProfiles(): array + { + return []; + } + /** * 모듈 설치 시 실행할 시더 클래스 목록 반환 * diff --git a/app/Extension/AbstractPlugin.php b/app/Extension/AbstractPlugin.php index 3e811cd6..1d41aa54 100644 --- a/app/Extension/AbstractPlugin.php +++ b/app/Extension/AbstractPlugin.php @@ -658,6 +658,37 @@ abstract class AbstractPlugin implements CacheableExtensionInterface, PluginInte return []; } + /** + * 성능 계측 프로파일 정의를 반환합니다. + * + * 이 플러그인이 소유한 목록/화면/저장 경로/배치 중 성능을 재고 싶은 대상을 선언합니다. + * `g7:bench` 커맨드가 코어 `config/benchmark.php` 선언과 함께 수집합니다 + * (`App\Benchmark\BenchmarkProfileRegistry`). 계측 대상을 코어 커맨드에 하드코딩하지 + * 않는 이유는, 확장이 설치·제거되는 설치본마다 실제로 존재하는 대상이 다르기 + * 때문입니다. 키는 플러그인 내부에서만 고유하면 되고, 다른 확장과 겹치면 커맨드가 + * `{식별자}/{키}` 로 지목합니다. + * + * `write` 축의 `callback` 은 클로저를 쓸 수 없습니다 — 코어 선언과 스키마를 공유하고 + * 코어 쪽은 `config:cache` 대상이므로, 형식을 `'Fqcn'`(invokable) 또는 + * `['Fqcn', 'method']` 로 통일합니다. + * + * @return array> 프로파일 키 → 정의 + * [ + * 'consent_history' => [ + * 'type' => 'list', // list | screen | write | batch + * 'label' => '동의 이력 목록', + * 'table' => 'gdpr_user_consent_histories', + * 'columns' => ['*'], + * 'order' => [['created_at', 'desc']], + * 'soft_delete' => false, + * ], + * ] + */ + public function getBenchmarkProfiles(): array + { + return []; + } + /** * 플러그인 설치 시 실행할 시더 클래스 목록 반환 * diff --git a/app/Providers/CoreServiceProvider.php b/app/Providers/CoreServiceProvider.php index c63e4bb0..874976be 100644 --- a/app/Providers/CoreServiceProvider.php +++ b/app/Providers/CoreServiceProvider.php @@ -2,6 +2,11 @@ namespace App\Providers; +use App\Benchmark\Axes\BatchAxisRunner; +use App\Benchmark\Axes\ListAxisRunner; +use App\Benchmark\Axes\ScreenAxisRunner; +use App\Benchmark\Axes\WriteAxisRunner; +use App\Console\Commands\BenchCommand; use App\Contracts\Extension\CacheInterface; use App\Contracts\Extension\ExtensionMiddlewareRegistryInterface; use App\Contracts\Extension\HookListenerInterface; @@ -119,9 +124,30 @@ class CoreServiceProvider extends ServiceProvider $this->registerRepositoryBindings(); $this->registerExtensionManagers(); + $this->registerBenchmarkAxes(); // ActivityLogManager 제거됨 — Monolog 채널(config/logging.php 'activity')로 대체 } + /** + * 성능 계측 축 실행기를 등록합니다. + * + * 축이 늘어날 때 `g7:bench` 커맨드를 고치지 않고 여기에 실행기만 추가하면 되도록 + * 태그로 묶어 주입합니다. 실행기는 CLI 계측 시점에만 해석되므로 웹 요청 비용은 없습니다. + */ + private function registerBenchmarkAxes(): void + { + $this->app->tag([ + ListAxisRunner::class, + ScreenAxisRunner::class, + WriteAxisRunner::class, + BatchAxisRunner::class, + ], 'benchmark.axes'); + + $this->app->when(BenchCommand::class) + ->needs('$runners') + ->giveTagged('benchmark.axes'); + } + /** * Extension(모듈/플러그인) PSR-4 오토로드를 등록합니다. * diff --git a/config/benchmark.php b/config/benchmark.php new file mode 100644 index 00000000..6d7b16eb --- /dev/null +++ b/config/benchmark.php @@ -0,0 +1,116 @@ + 1] + | 연산자: ['order_status' => ['not in', [...]]] + | 연산자 닫힌 집합: = != <> < <= > >= like in "not in". + | 목록에 없는 연산자는 측정 전에 사유와 함께 거부한다. + | 선언형으로 재현 못 하는 술어(상관 서브쿼리·권한 스코프)가 지배적인 + | 목록은 프로파일을 두지 않는다 — 어느 화면도 내지 않는 수치가 된다. + | soft_delete : true 면 deleted_at IS NULL 부착 + | seed_overrides : 합성 시딩 시 고정할 컬럼값 (filters 와 맞출 용도) + | + | type=screen — 화면 1장 응답 시간 + 실행 쿼리 건수 + N+1 후보 + | route : 라우트명 (권장 — URI 프리픽스 조립 금지, 사라지면 즉시 실패) + | route_params: 라우트 파라미터 + | uri : 라우트명이 없을 때만 쓰는 원시 경로 + | method : HTTP 메서드 (기본 GET) + | query : 쿼리스트링 [키 => 값] + | permissions : 계측용 임시 계정에 부여할 권한 식별자 목록 + | + | type=write — 저장 경로 1회 소요 시간 + | callback : 'Fqcn' (invokable) 또는 ['Fqcn', 'method']. 클로저는 config:cache 를 + | 깨뜨리므로 금지하며 레지스트리가 거부한다 + | cleanup : 동일 형식. 측정으로 생긴 행을 되돌린다 + | + | type=batch — 배치 커맨드 소요 시간 + 피크 메모리 + | command : artisan 커맨드명 + | arguments : 커맨드 인자/옵션 [키 => 값] + | + */ + + 'profiles' => [ + + // --- 목록 조회 (list) ------------------------------------------------- + + // 활동 로그와 알림 발송 이력은 응답 계약상 넓은 컬럼(changes/properties, body)을 + // 그대로 노출하므로 컬럼 프루닝이 불가하다. 지연 조인만 적용했고 계측 비교축은 + // select * vs select id 다. + 'activity_logs' => [ + 'type' => 'list', + 'label' => '활동 로그 목록', + 'table' => 'activity_logs', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => false, + ], + + 'notification_logs' => [ + 'type' => 'list', + 'label' => '알림 발송 이력 목록', + 'table' => 'notification_logs', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => false, + ], + + // 회원 테이블은 소프트 삭제 컬럼이 없다 (탈퇴는 status 로 표현) — 실제 스키마 기준 선언 + 'users' => [ + 'type' => 'list', + 'label' => '회원 목록', + 'table' => 'users', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => false, + ], + + // --- 화면 응답 (screen) ----------------------------------------------- + + 'activity_logs_screen' => [ + 'type' => 'screen', + 'label' => '관리자 활동 로그 목록 화면', + 'route' => 'api.admin.activity-logs.index', + 'query' => ['per_page' => 20], + 'permissions' => ['core.activities.read'], + ], + + 'users_screen' => [ + 'type' => 'screen', + 'label' => '관리자 회원 목록 화면', + 'route' => 'api.admin.users.index', + 'query' => ['per_page' => 20], + 'permissions' => ['core.users.read'], + ], + + // --- 배치 작업 (batch) ------------------------------------------------ + + 'sitemap' => [ + 'type' => 'batch', + 'label' => '사이트맵 생성', + 'command' => 'seo:generate-sitemap', + 'arguments' => ['--sync' => true], + ], + + ], +]; diff --git a/docs/README.md b/docs/README.md index 28674e30..28b14c7a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,7 @@ | 카테고리 | 문서 수 | 링크 상태 | |----------|---------|----------| -| [백엔드](backend/) | 33개 | 정상 | +| [백엔드](backend/) | 34개 | 정상 | | [프론트엔드](frontend/) | 51개 | 정상 | | [확장 시스템](extension/) | 31개 | 정상 | | 공통 | 20개 | 정상 | @@ -125,7 +125,7 @@ G7 이 제공하는 REST API 의 엔드포인트별 요청 파라미터·응답 ## 카테고리별 전체 문서 목록 -### 백엔드 (33개) +### 백엔드 (34개) | 문서 | 제목 | |------|------| @@ -135,6 +135,7 @@ G7 이 제공하는 REST API 의 엔드포인트별 요청 파라미터·응답 | [api-documentation.md](backend/api-documentation.md) | API 레퍼런스 문서 규정 (API Documentation) | | [api-resources.md](backend/api-resources.md) | API 리소스 | | [authentication.md](backend/authentication.md) | 인증 및 세션 처리 | +| [benchmark.md](backend/benchmark.md) | 성능 계측 시스템 (Benchmark) | | [broadcasting.md](backend/broadcasting.md) | Broadcasting (실시간 이벤트) | | [console-confirm.md](backend/console-confirm.md) | 콘솔 yes/no 프롬프트 (ConsoleConfirm) | | [controllers.md](backend/controllers.md) | 컨트롤러 계층 구조 | diff --git a/docs/backend/README.md b/docs/backend/README.md index f9b5a83a..b5d78a24 100644 --- a/docs/backend/README.md +++ b/docs/backend/README.md @@ -36,6 +36,7 @@ | [api-documentation.md](api-documentation.md) | API 레퍼런스 문서 규정 (API Documentation) | 모든 API 엔드포인트는 레퍼런스 문서 필수 — 메서드/URI/파라미터/응답 필드 +... | | [api-resources.md](api-resources.md) | API 리소스 | Resource: BaseApiResource 상속 필수 / Collection: B... | | [authentication.md](authentication.md) | 인증 및 세션 처리 | Laravel Sanctum 토큰 전용 인증 (Bearer 토큰만 사용) | +| [benchmark.md](benchmark.md) | 성능 계측 시스템 (Benchmark) | `g7:bench` 가 4축(list/screen/write/batch)을 잰다 — ... | | [broadcasting.md](broadcasting.md) | Broadcasting (실시간 이벤트) | Laravel Reverb 사용 (WebSocket) | | [console-confirm.md](console-confirm.md) | 콘솔 yes/no 프롬프트 (ConsoleConfirm) | 콘솔 커맨드의 yes/no 프롬프트는 $this->unifiedConfirm() 사용... | | [controllers.md](controllers.md) | 컨트롤러 계층 구조 | AdminBaseController / AuthBaseController / Publ... | diff --git a/docs/backend/benchmark.md b/docs/backend/benchmark.md new file mode 100644 index 00000000..df655bbe --- /dev/null +++ b/docs/backend/benchmark.md @@ -0,0 +1,186 @@ +# 성능 계측 시스템 (Benchmark) + +> **백엔드 가이드** | [목차로 돌아가기](README.md) + +--- + +## TL;DR (5초 요약) + +```text +1. `g7:bench` 가 4축(list/screen/write/batch)을 잰다 — 계측 대상은 커맨드에 하드코딩하지 않는다 +2. 선언 지점: 코어 `config/benchmark.php`, 확장 `getBenchmarkProfiles()` — 스키마 동일 +3. 화면(screen) 축은 프로파일이 선언한 권한만 가진 임시 계정으로 내부 요청 → 롤백. `--as=` 로 기존 계정 지정 가능 +4. 데이터를 변경하는 축(write/batch/비-GET screen)은 `--allow-write` 없이 실행 거부 +5. 문서에 옮길 수치는 `--report` 산출물에서만 옮긴다 (환경 정보 동반 — 눈대중 기재 금지) +``` + +--- + +## 목차 + +- [무엇을 재는가](#무엇을-재는가) +- [선언 지점](#선언-지점) +- [축별 스키마](#축별-스키마) +- [실행](#실행) +- [안전장치](#안전장치) +- [리포트](#리포트) +- [관련 문서](#관련-문서) + +--- + +## 무엇을 재는가 + +| 축 | 재는 것 | 실행 방식 | +| ------ | ------ | ------ | +| `list` | 목록 SELECT 비용 (전체 컬럼 / 목록 컬럼 / 키 컬럼 3축) | 선언된 필터·정렬로 쿼리를 조립해 OFFSET 별 반복 측정 | +| `screen` | 화면 1장 응답 시간 + 실행 쿼리 건수 + N+1 후보 | 라우트를 HTTP 커널로 내부 요청 처리 | +| `write` | 저장 경로 1회 소요 시간 + 쿼리 건수 | 확장이 선언한 콜백 실행 | +| `batch` | 배치 커맨드 소요 시간 + 피크 메모리 | artisan 커맨드 실행 감싸기 | + +`screen` 축이 값어치가 가장 크다 — 사용자가 체감하는 단위이고, 목록 SELECT 는 빨라도 N+1 로 느려지는 경우를 잡는다. `list` 축의 세 값 배수는 지연 조인(`PaginatesWithDeferredJoin`) 적용의 기대 효과에 대응한다 (셋째 축이 지연 조인의 inner 쿼리). + +## 선언 지점 + +계측 대상은 **소유자가 선언한다**. 커맨드에 대상을 하드코딩하지 않는 이유는, 확장이 설치·제거되는 설치본마다 실제로 존재하는 대상이 다르기 때문이다. + +| 소유자 | 선언 위치 | +| ------ | ------ | +| 코어 | `config/benchmark.php` 의 `profiles` 배열 | +| 모듈 | `module.php` 의 `getBenchmarkProfiles()` 오버라이드 | +| 플러그인 | `plugin.php` 의 `getBenchmarkProfiles()` 오버라이드 | + +프로파일 키는 확장 내부에서만 고유하면 된다. 서로 다른 확장이 같은 키를 쓰면 커맨드가 `{식별자}/{키}` 로 지목하며, 짧은 키가 모호할 때는 후보를 나열하고 실행을 거부한다 (임의로 하나를 고르면 어느 확장의 목록을 잰 것인지 알 수 없게 된다). + +```php +// modules/_bundled/sirsoft-ecommerce/module.php +public function getBenchmarkProfiles(): array +{ + return [ + 'orders' => [ + 'type' => 'list', + 'label' => '주문 목록', + 'table' => 'ecommerce_orders', + // 컬럼을 다시 적지 않고 Repository 상수를 참조한다 — 다시 적으면 계측이 조용히 낡는다 + 'columns' => OrderRepository::LIST_COLUMNS, + 'order' => [['ordered_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + ]; +} +``` + +선언이 잘못된 프로파일은 **조용히 버리지 않는다** — 사유와 함께 경고로 드러내고 목록에서 제외한다 (버려진 선언이 곧 계측 사각이 된다). + +## 축별 스키마 + +공통 필드는 `type`(필수)과 `label`(선택)이다. + +### type=list + +| 필드 | 설명 | +| ------ | ------ | +| `table` | 대상 테이블 (필수) | +| `columns` | 목록이 실제로 select 하는 컬럼. `['*']` 는 "응답 계약상 전 컬럼 노출이라 프루닝 불가" 선언이며 이때 비교축은 `select *` vs `select id` | +| `order` | `[[컬럼, 방향], ...]` | +| `filters` | 화면이 실제로 거는 필터. 필터 없이 재면 인덱스 선택이 달라져 화면에서 일어나는 일과 다른 것을 잰다 (아래 형식) | +| `soft_delete` | true 면 `deleted_at IS NULL` 부착 | +| `seed_overrides` | 합성 시딩 시 고정할 컬럼값 (`filters` 와 맞출 용도) | + +`filters` 는 등가 비교와 연산자 형태를 함께 받는다. 등가 비교만 지원하면 화면이 실제로 거는 필터를 선언할 수 없는 목록이 생긴다 — 주문 목록은 상태 미지정 시 임시 주문 상태를 `NOT IN` 으로 제외한다. + +```php +'filters' => [ + 'board_id' => 1, // 등가 비교 + 'order_status' => ['not in', OrderStatusEnum::listHiddenValues()], // 연산자 형태 + 'created_at' => ['>=', '2026-01-01'], +], +``` + +연산자는 닫힌 집합이다 — `=` `!=` `<>` `<` `<=` `>` `>=` `like` `in` `not in`. 목록에 없는 연산자를 선언하면 측정 전에 사유와 함께 거부한다(조용히 빠진 필터로 측정하면 화면과 다른 것을 재면서 정상 측정으로 보고된다). 값을 Enum·상수로 참조해 도메인 SSoT 와 어긋나지 않게 한다. + +선언형 필터로 재현할 수 없는 술어(상관 서브쿼리, 권한 스코프 등)가 목록의 지배적 조건이라면 **프로파일을 두지 않는다** — 어느 화면도 내지 않는 수치가 되기 때문이다. + +### type=screen + +| 필드 | 설명 | +| ------ | ------ | +| `route` | 라우트명 (권장). 라우트가 사라지면 조용히 404 를 재는 대신 즉시 실패한다 | +| `route_params` | 라우트 파라미터 | +| `uri` | 라우트명이 없을 때만 쓰는 원시 경로 | +| `method` | HTTP 메서드 (기본 `GET`) | +| `query` | 쿼리스트링 `[키 => 값]` | +| `permissions` | 계측용 임시 계정에 부여할 권한 식별자 목록 | + +권한을 프로파일에서 받는 이유는, 계측 대상 화면이 요구하는 권한만 주어야 미들웨어 통과 여부까지 실제와 같아지기 때문이다 (전권 계정으로 재면 권한 검사 비용과 분기가 달라진다). + +### type=write + +| 필드 | 설명 | +| ------ | ------ | +| `prepare` | 회차별 선행 준비 콜백 (**계측 구간 밖**에서 실행, 회차 번호를 인자로 받음) | +| `callback` | 계측 대상 콜백 (`prepare` 반환값을 인자로 받음) | +| `cleanup` | 트랜잭션 롤백으로 되돌지 않는 잔여물(파일·캐시) 정리 | + +콜백은 `'Fqcn'`(invokable) 또는 `['Fqcn', 'method']` 형식만 허용한다 — 코어 선언이 `config/benchmark.php` 에 있고 이 파일은 `config:cache` 대상이라 클로저를 담을 수 없으므로, 확장 선언도 같은 형식으로 통일한다. 클로저를 선언하면 레지스트리가 사유와 함께 거부한다. + +`prepare` 를 계측 구간 밖에 두는 이유는, 저장 경로에는 선행 상태가 필요한 경우가 많고(주문 생성에는 임시 주문이 필요하고 임시 주문은 1회만 전환됨) 그 준비 비용이 측정값에 섞이면 재려던 것을 재지 못하기 때문이다. + +### type=batch + +| 필드 | 설명 | +| ------ | ------ | +| `command` | artisan 커맨드명 | +| `arguments` | 커맨드 인자/옵션 `[키 => 값]` | + +## 실행 + +```bash +# 등록된 프로파일 목록 +php artisan g7:bench --list-profiles + +# 프로파일 단위 +php artisan g7:bench --profile=core/users_screen +php artisan g7:bench --profile=sirsoft-ecommerce/orders --offsets=0,20000,50000,199980 --runs=3 --explain + +# 축 단위 / 전체 +php artisan g7:bench --axis=screen +php artisan g7:bench --all --allow-write + +# 깊은 OFFSET 계측 (대량 합성 행 시딩 — 폐기 가능한 DB 에서만) +php artisan --env=testing g7:bench --profile=sirsoft-board/board_posts --fresh --seed=200000 +php artisan g7:bench --profile=sirsoft-board/board_posts --database=g7_bench --seed=200000 + +# 기계 판독 / 리포트 +php artisan g7:bench --axis=list --json +php artisan g7:bench --all --allow-write --report +php artisan g7:bench --all --allow-write --report=C:/tmp/before.md +``` + +`g7:bench:pagination` 은 이전 이름의 별칭으로 남아 있어 그대로 호출된다 (옵션은 `--profile=` 기준). + +## 안전장치 + +| 장치 | 동작 | +| ------ | ------ | +| `--allow-write` | 데이터를 변경하는 축(`write`/`batch`/비-GET `screen`)은 이 플래그 없이 실행을 거부 | +| 롤백 트랜잭션 | `screen`/`write` 축은 계측 계정 생성부터 요청·저장 처리까지 롤백되는 트랜잭션 안에서 실행 → 운영 DB 에서도 잔여 데이터가 남지 않음 | +| 운영 환경 시딩 거부 | `--seed`/`--fresh` 는 `production` 에서 거부 | +| `--database=` | 계측용 DB 지정. config 가 캐시된 환경에서 연결을 바꿀 유일한 수단 | + +트랜잭션이 열려 있으면 Laravel 이 읽기 쿼리도 write PDO 로 보내므로(`Connection::getReadPdo` 의 `transactions > 0` 분기), 읽기/쓰기 분리 환경에서도 계측 요청이 임시 계정을 인증할 수 있다. + +`batch` 축은 트랜잭션으로 감싸지 않는다 — 배치 커맨드는 내부에서 커밋하거나 DDL 을 실행할 수 있고, 대량 처리를 긴 트랜잭션에 담으면 락 보유 시간이 계측 자체보다 위험해진다. + +## 리포트 + +`--report` 는 마크다운 리포트를 `storage/app/benchmarks/` 에 저장한다 (`--report=<경로>` 로 임의 경로 지정). 이 디렉토리는 Git 미추적이다 — 계측값은 실행 머신·DB 버전·OPcache 여부에 종속되므로 저장소에 축적하면 서로 비교 불가능한 수치가 섞인다. + +리포트는 환경 정보(APP_ENV, DB 버전, PHP 버전, OPcache, `config:cache`, 실행 머신)와 실행 조건을 축별 표 앞에 함께 적는다. **문서에 옮길 수치는 이 산출물에서만 옮긴다** — 눈대중·기억으로 적은 수치는 어느 환경에서 나온 값인지 확인할 수 없다. + +측정하지 못한 프로파일은 목록에서 빠지지 않고 사유와 함께 남는다. 전부 건너뛴 실행은 종료 코드 1 로 끝난다 (성공으로 보고하면 "측정했다"로 읽힌다). + +## 관련 문서 + +- [service-repository.md](service-repository.md) — 목록 조회 컬럼 프루닝과 지연 조인 +- [cheatsheet.md](../cheatsheet.md) — 커맨드 요약 +- [hooks.md](../extension/hooks.md) — 확장 선언 훅 일반 diff --git a/docs/backend/service-repository.md b/docs/backend/service-repository.md index 571913a5..b09b3b49 100644 --- a/docs/backend/service-repository.md +++ b/docs/backend/service-repository.md @@ -847,7 +847,7 @@ public function getAll(): Collection 게시글 테이블 20만 행 기준 실측 (각 3회 중앙값, 첫 회 버림): ```bash -php artisan g7:bench:pagination --table=board_posts --seed=200000 --offsets=0,20000,50000,199980 --runs=3 --explain +php artisan g7:bench --profile=sirsoft-board/board_posts --seed=200000 --offsets=0,20000,50000,199980 --runs=3 --explain ``` | OFFSET | 목록 컬럼 | ID 만 조회 | 배수 | @@ -863,6 +863,8 @@ php artisan g7:bench:pagination --table=board_posts --seed=200000 --offsets=0,20 `SUBSTRING(content, 1, 200)` 을 목록 컬럼에 두는 것은 프루닝이 아니다. 앞 200바이트를 얻기 위해 오버플로 페이지를 그대로 읽기 때문이다. 잘라내기는 지연 조인의 outer 로 옮긴다. +계측 대상 프로파일 선언 방법과 나머지 3축(화면 응답·쓰기·배치)은 [benchmark.md](benchmark.md) 를 참조한다. + ### 사용 ```php diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index fef00780..aa7ec63e 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -258,12 +258,20 @@ php artisan seo:generate-sitemap --mode=full|auto|incremental # 재생성 모 ### 성능 계측 Artisan 커맨드 ```bash -# 깊은 OFFSET 목록 조회 비용 계측 (목록 컬럼 vs 키 컬럼 두 축). 상세: docs/backend/service-repository.md -# 대량 합성 행을 시딩하므로 운영 데이터가 있는 환경에서 --seed/--fresh 를 쓰지 않는다. -php artisan --env=testing g7:bench:pagination --list-profiles -php artisan --env=testing g7:bench:pagination --table=board_posts --fresh --seed=200000 -php artisan --env=testing g7:bench:pagination --table=board_posts --offsets=0,20000,50000,199980 --runs=3 --explain -php artisan --env=testing g7:bench:pagination --table=orders --json # 기계 판독용 +# 4축(목록/화면/쓰기/배치) 성능 계측. 계측 대상은 코어 config/benchmark.php + 확장 getBenchmarkProfiles() 선언. +# 상세: docs/backend/benchmark.md +php artisan g7:bench --list-profiles # 등록된 프로파일 목록 +php artisan g7:bench --profile=core/users_screen # 화면 1장 응답 시간 + 쿼리 건수 + N+1 후보 +php artisan g7:bench --axis=list # 축 단위 +php artisan g7:bench --all --allow-write --report # 전체 + 마크다운 리포트 (storage/app/benchmarks/) + +# 깊은 OFFSET 계측 — 대량 합성 행을 시딩하므로 운영 데이터가 있는 환경에서 --seed/--fresh 를 쓰지 않는다. +php artisan --env=testing g7:bench --profile=sirsoft-board/board_posts --fresh --seed=200000 +php artisan --env=testing g7:bench --profile=sirsoft-board/board_posts --offsets=0,20000,50000,199980 --runs=3 --explain +php artisan g7:bench --profile=sirsoft-ecommerce/orders --json # 기계 판독용 + +# 데이터를 변경하는 축(write/batch/비-GET screen)은 --allow-write 없이 거부된다. +php artisan g7:bench --profile=sirsoft-ecommerce/order_create --allow-write ``` ### API 문서 Artisan 커맨드 diff --git a/docs/extension/module-basics.md b/docs/extension/module-basics.md index 35c56a63..4c9ab689 100644 --- a/docs/extension/module-basics.md +++ b/docs/extension/module-basics.md @@ -142,6 +142,7 @@ | `getDependencies()` | `[]` | 의존성 | | `getMetadata()` | `[]` | 메타데이터 | | `getMiddleware()` | `[]` | 확장 미들웨어 선언 (self-gate targets) — `{class, groups, timing?, targets}` ([middleware.md](../backend/middleware.md)) | +| `getBenchmarkProfiles()` | `[]` | 성능 계측 대상 선언 (`g7:bench` 가 수집) — 목록/화면/쓰기/배치 4축 ([benchmark.md](../backend/benchmark.md)) | | `upgrades()` | `[]` | 업그레이드 스텝 (`upgrades/` 디렉토리 자동 발견). **`g7_version >= 7.0.0-beta.5` 인 모듈은 신규 step 이 `AbstractUpgradeStep` 상속 의무** ([upgrade-step-guide §13](upgrade-step-guide.md)) — 미상속 시 `ModuleManager::runUpgradeSteps` 가 `RuntimeException` throw | #### 동적 권한/역할/메뉴 보존 규칙 diff --git a/docs/extension/plugin-development.md b/docs/extension/plugin-development.md index 6f4c07bc..6217e6c5 100644 --- a/docs/extension/plugin-development.md +++ b/docs/extension/plugin-development.md @@ -241,6 +241,7 @@ class Plugin implements PluginInterface | `getDependencies()` | `[]` | 의존하는 모듈/플러그인 목록 | | `getHookListeners()` | `[]` | 훅 리스너 클래스 목록 | | `getMiddleware()` | `[]` | 확장 미들웨어 선언 (self-gate targets) — `{class, groups, timing?, targets}` ([middleware.md](../backend/middleware.md)) | +| `getBenchmarkProfiles()` | `[]` | 성능 계측 대상 선언 (`g7:bench` 가 수집) — 목록/화면/쓰기/배치 4축 ([benchmark.md](../backend/benchmark.md)) | | `upgrades()` | `[]` | 업그레이드 스텝 (`upgrades/` 디렉토리 자동 발견). **`g7_version >= 7.0.0-beta.5` 인 플러그인은 신규 step 이 `AbstractUpgradeStep` 상속 의무** ([upgrade-step-guide §13](upgrade-step-guide.md)) — 미상속 시 `PluginManager::runUpgradeSteps` 가 `RuntimeException` throw | > **동적 식별자 보존 규칙**: `Permission::updateOrCreate()` / `Role::firstOrCreate()` 등으로 런타임에 생성한 엔티티는 업데이트 시 `cleanupStalePluginEntries` 에 의해 "정적 정의에 없는 고아 레코드" 로 판정되어 삭제될 위험이 있습니다. 이를 방지하려면 동적 식별자 목록을 위 3개 훅에서 반환하세요 — 정적 정의 + 동적 식별자가 병합된 expected 목록을 기준으로 판정되어 보존됩니다. 상세는 [extension-update-system.md](extension-update-system.md) 참조. diff --git a/modules/_bundled/sirsoft-board/module.php b/modules/_bundled/sirsoft-board/module.php index b9b167b1..794dc1b1 100644 --- a/modules/_bundled/sirsoft-board/module.php +++ b/modules/_bundled/sirsoft-board/module.php @@ -848,6 +848,50 @@ class Module extends AbstractModule ]; } + /** + * 성능 계측 프로파일 정의 (`g7:bench`). + * + * 게시글 목록의 `columns` 는 `PostRepository::paginate()` 가 select 하는 컬럼 집합입니다 + * (본문 미리보기용 `SUBSTRING` 표현식은 지연 조인의 outer 에서만 적용되고 계측 대상인 + * 건너뛰기 비용과 무관하므로 제외). 게시글 목록은 화면이 게시판 하나를 조회하므로 필터 + * 없이 재면 인덱스 선택이 달라져 화면에서 일어나는 일과 다른 것을 재게 됩니다. + * + * @return array> 프로파일 키 → 정의 + */ + public function getBenchmarkProfiles(): array + { + return [ + 'board_posts' => [ + 'type' => 'list', + 'label' => '게시글 목록', + 'table' => 'board_posts', + 'columns' => [ + 'id', 'board_id', 'user_id', 'parent_id', 'category', + 'title', 'author_name', 'content_mode', + 'is_notice', 'is_secret', 'status', 'depth', + 'view_count', 'comments_count', 'replies_count', 'attachments_count', + 'trigger_type', 'ip_address', 'created_at', 'updated_at', 'deleted_at', + ], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'filters' => ['board_id' => 1], + 'seed_overrides' => ['board_id' => 1, 'is_notice' => 0], + 'soft_delete' => true, + ], + // 댓글은 계측 프로파일을 두지 않는다. 페이지네이션되는 댓글 목록은 회원 본인 댓글 + // 목록뿐이고(CommentRepository), 그 쿼리에는 회원 스코프 · 삭제 게시글 제외 + // (whereExists 서브쿼리) · 비활성 게시판 제외가 무조건 붙는다. 선언형 필터로 + // 재현할 수 없는 술어라, 맨 테이블 스캔을 재면 어느 화면도 내지 않는 수치가 된다. + 'reports' => [ + 'type' => 'list', + 'label' => '신고 목록', + 'table' => 'boards_reports', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + ]; + } + /** * 새 댓글 알림 정의. */ diff --git a/modules/_bundled/sirsoft-ecommerce/module.php b/modules/_bundled/sirsoft-ecommerce/module.php index 39b125ad..37a4f155 100644 --- a/modules/_bundled/sirsoft-ecommerce/module.php +++ b/modules/_bundled/sirsoft-ecommerce/module.php @@ -6,9 +6,11 @@ use App\Extension\AbstractModule; use App\Models\IdentityMessageDefinition; use App\Seo\Concerns\LocalizesSeoValues; use Illuminate\Database\Seeder; +use Modules\Sirsoft\Ecommerce\Benchmark\OrderCreationBenchmark; use Modules\Sirsoft\Ecommerce\Database\Seeders\ClaimReasonSeeder; use Modules\Sirsoft\Ecommerce\Database\Seeders\SequenceSeeder; use Modules\Sirsoft\Ecommerce\Database\Seeders\ShippingCarrierSeeder; +use Modules\Sirsoft\Ecommerce\Enums\OrderStatusEnum; use Modules\Sirsoft\Ecommerce\Http\Middleware\DetectDevice; use Modules\Sirsoft\Ecommerce\Http\Middleware\ResolveShippingCountry; use Modules\Sirsoft\Ecommerce\Http\Middleware\VerifyGuestOrderToken; @@ -40,6 +42,7 @@ use Modules\Sirsoft\Ecommerce\Listeners\UserCurrencyInfoListener; use Modules\Sirsoft\Ecommerce\Listeners\UserMileageCleanupListener; use Modules\Sirsoft\Ecommerce\Listeners\UserMileageInfoListener; use Modules\Sirsoft\Ecommerce\Listeners\UserShippingCountryInfoListener; +use Modules\Sirsoft\Ecommerce\Repositories\OrderRepository; class Module extends AbstractModule { @@ -1242,6 +1245,89 @@ class Module extends AbstractModule ]; } + /** + * 성능 계측 프로파일 정의 (`g7:bench`). + * + * 목록 프로파일의 `columns` 는 해당 목록이 실제로 select 하는 컬럼이어야 합니다. 주문 + * 목록은 Repository 가 상수로 들고 있으므로 그 상수를 그대로 참조합니다 — 여기에 컬럼을 + * 다시 적으면 Repository 변경 시 계측이 조용히 낡습니다. 나머지 목록의 `['*']` 는 + * "응답 계약상 전 컬럼을 노출해 프루닝 불가" 선언이며, 이때 비교축은 select * vs select id + * 입니다. + * + * @return array> 프로파일 키 → 정의 + */ + public function getBenchmarkProfiles(): array + { + return [ + 'orders' => [ + 'type' => 'list', + 'label' => '주문 목록', + 'table' => 'ecommerce_orders', + 'columns' => OrderRepository::LIST_COLUMNS, + 'order' => [['ordered_at', 'desc'], ['id', 'desc']], + // 관리자 주문 목록은 상태 미지정 시 임시 주문 상태를 제외한다 + // (OrderRepository::getListWithFilters). 이 술어를 빼고 재면 옵티마이저가 다른 + // 인덱스를 골라 화면에서 일어나는 일과 다른 것을 잰다. 값은 Enum SSoT 를 참조한다. + 'filters' => ['order_status' => ['not in', OrderStatusEnum::listHiddenValues()]], + 'soft_delete' => true, + ], + 'products' => [ + 'type' => 'list', + 'label' => '상품 목록', + 'table' => 'ecommerce_products', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + // 상품 문의는 소프트 삭제 컬럼이 없다 — 실제 스키마 기준 선언 + 'product_inquiries' => [ + 'type' => 'list', + 'label' => '상품 문의 목록', + 'table' => 'ecommerce_product_inquiries', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => false, + ], + 'product_reviews' => [ + 'type' => 'list', + 'label' => '상품 후기 목록', + 'table' => 'ecommerce_product_reviews', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + 'soft_delete' => true, + ], + 'coupon_issues' => [ + 'type' => 'list', + 'label' => '쿠폰 발급 이력 목록', + 'table' => 'ecommerce_promotion_coupon_issues', + 'columns' => ['*'], + 'order' => [['issued_at', 'desc'], ['id', 'desc']], + 'soft_delete' => false, + ], + 'extra_fee_templates' => [ + 'type' => 'list', + 'label' => '추가 배송비 템플릿 목록', + 'table' => 'ecommerce_shipping_policy_extra_fee_templates', + 'columns' => ['*'], + 'order' => [['zipcode', 'asc'], ['id', 'asc']], + 'soft_delete' => false, + ], + 'orders_screen' => [ + 'type' => 'screen', + 'label' => '관리자 주문 목록 화면', + 'route' => 'api.modules.sirsoft-ecommerce.admin.orders.index', + 'query' => ['per_page' => 20], + 'permissions' => ['sirsoft-ecommerce.orders.read'], + ], + 'order_create' => [ + 'type' => 'write', + 'label' => '주문 생성 (임시 주문 → 주문 전환)', + 'prepare' => [OrderCreationBenchmark::class, 'prepare'], + 'callback' => [OrderCreationBenchmark::class, 'create'], + ], + ]; + } + /** * 무통장입금 입금 안내 알림 정의. * diff --git a/modules/_bundled/sirsoft-ecommerce/src/Benchmark/OrderCreationBenchmark.php b/modules/_bundled/sirsoft-ecommerce/src/Benchmark/OrderCreationBenchmark.php new file mode 100644 index 00000000..86a3042d --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Benchmark/OrderCreationBenchmark.php @@ -0,0 +1,116 @@ +create(); + + $product = Product::factory()->create([ + 'stock_quantity' => 10000, + 'has_options' => false, + 'option_groups' => null, + // 기본 배송정책 해석에 맡긴다 — 특정 정책을 지목하면 그 정책의 비용만 재게 된다 + 'shipping_policy_id' => null, + ]); + + $option = ProductOption::factory()->forProduct($product)->create([ + 'stock_quantity' => 10000, + 'price_adjustment' => 0, + 'is_default' => true, + 'is_active' => true, + ]); + + // 구매 대상 제한 검증이 Auth::user() 의 역할을 읽으므로 인증 주체를 세운다. + // 콘솔에는 세션이 없어 login() 대신 setUser() 를 쓴다. + Auth::setUser($user); + + $tempOrder = $this->tempOrderService->createTempOrderFromDirectItems( + items: [[ + 'product_id' => $product->id, + 'product_option_id' => $option->id, + 'quantity' => 1, + ]], + userId: $user->id, + cartKey: null, + ); + + return [ + 'temp_order' => $tempOrder, + 'user' => $user, + // 프론트엔드가 보내는 결제예정금액에 해당 — 임시 주문의 최종 금액을 그대로 쓴다 + 'expected_total_amount' => (float) $tempOrder->getFinalAmount(), + ]; + } + + /** + * 계측 대상 — 임시 주문을 실제 주문으로 생성합니다. + * + * @param array{temp_order: TempOrder, user: User, expected_total_amount: float} $context prepare 산출물 + * @return Order 생성된 주문 + */ + public function create(array $context): Order + { + return $this->orderProcessingService->createFromTempOrder( + tempOrder: $context['temp_order'], + ordererInfo: [ + 'name' => '성능계측', + 'phone' => '01000000000', + 'email' => $context['user']->email, + ], + shippingInfo: [ + 'recipient_name' => '성능계측', + 'recipient_phone' => '01000000000', + 'country_code' => 'KR', + 'zipcode' => '06236', + 'address' => '서울특별시 강남구 테헤란로 1', + 'address_detail' => '1층', + ], + paymentMethod: 'card', + expectedTotalAmount: $context['expected_total_amount'], + ); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php index a2aee2dc..999f6a7e 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php @@ -38,7 +38,7 @@ class OrderRepository implements OrderRepositoryInterface * * @var array */ - private const LIST_COLUMNS = [ + public const LIST_COLUMNS = [ 'id', 'user_id', 'order_number', diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/OrderCreationBenchmarkTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/OrderCreationBenchmarkTest.php new file mode 100644 index 00000000..6a416c9b --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/OrderCreationBenchmarkTest.php @@ -0,0 +1,161 @@ +getBenchmarkProfiles(); + + // 목록 축 — 주문 목록은 Repository 상수를 참조해 컬럼 중복 선언을 없앤다 + $this->assertArrayHasKey('orders', $declared); + $this->assertSame('list', $declared['orders']['type']); + $this->assertSame('ecommerce_orders', $declared['orders']['table']); + $this->assertContains('order_number', $declared['orders']['columns']); + $this->assertNotContains('memo', $declared['orders']['columns'], '목록에 쓰지 않는 넓은 컬럼은 빠져야 한다.'); + + // 관리자 주문 목록은 상태 미지정 시 임시 주문 상태를 NOT IN 으로 제외한다 + // (OrderRepository::getListWithFilters). 이 술어가 빠지면 다른 인덱스를 재게 된다. + $this->assertSame( + ['order_status' => ['not in', OrderStatusEnum::listHiddenValues()]], + $declared['orders']['filters'], + '주문 목록 프로파일은 화면 기본 필터를 Enum SSoT 기준으로 선언해야 한다.' + ); + + // 화면 축 — 라우트명으로 선언 (URI 프리픽스 조립 금지) + $this->assertSame('screen', $declared['orders_screen']['type']); + $this->assertSame( + 'api.modules.sirsoft-ecommerce.admin.orders.index', + $declared['orders_screen']['route'] + ); + $this->assertSame(['sirsoft-ecommerce.orders.read'], $declared['orders_screen']['permissions']); + + // 쓰기 축 — prepare(계측 제외) / callback(계측) 분리, 클로저 아닌 형식 + $this->assertSame('write', $declared['order_create']['type']); + $this->assertSame([OrderCreationBenchmark::class, 'prepare'], $declared['order_create']['prepare']); + $this->assertSame([OrderCreationBenchmark::class, 'create'], $declared['order_create']['callback']); + } + + /** + * 선언한 프로파일이 코어 레지스트리에 수집됩니다. + */ + #[Test] + public function 선언한_프로파일이_코어_레지스트리에_수집된다(): void + { + $profile = app(BenchmarkProfileRegistry::class)->find('sirsoft-ecommerce/order_create'); + + $this->assertSame(BenchmarkAxis::Write, $profile->axis); + $this->assertSame('module', $profile->sourceKind); + $this->assertSame('sirsoft-ecommerce', $profile->sourceIdentifier); + $this->assertTrue($profile->mutates(), '쓰기 축은 데이터를 변경하는 축이다.'); + } + + /** + * `prepare` 가 구매 가능한 상품·옵션과 임시 주문을 세웁니다. + * + * 임시 주문을 팩토리로 조립하지 않고 실제 서비스로 만드는 이유는, 주문 생성이 저장된 + * 계산 파라미터로 금액을 재계산해 저장값과 대조하기 때문입니다 (손으로 만든 계산 결과는 + * 어긋나 차단되고, 어긋나지 않게 맞추면 실제 결제 경로가 아닌 다른 것을 재게 됩니다). + */ + #[Test] + public function prepare_가_임시_주문을_세운다(): void + { + $context = app(OrderCreationBenchmark::class)->prepare(1); + + $this->assertInstanceOf(TempOrder::class, $context['temp_order']); + $this->assertGreaterThan(0, $context['expected_total_amount'], '결제예정금액이 산출되어야 한다.'); + $this->assertSame( + $context['user']->id, + $context['temp_order']->user_id, + '임시 주문이 준비한 회원에 귀속되어야 한다.' + ); + $this->assertSame( + (float) $context['temp_order']->getFinalAmount(), + $context['expected_total_amount'], + '결제예정금액은 임시 주문의 최종 금액과 같아야 한다 — 다르면 금액 검증에 막힌다.' + ); + } + + /** + * 계측 대상 콜백이 실제 주문을 생성합니다. + */ + #[Test] + public function callback_이_실제_주문을_생성한다(): void + { + $benchmark = app(OrderCreationBenchmark::class); + + $ordersBefore = Order::count(); + $order = $benchmark->create($benchmark->prepare(1)); + + $this->assertInstanceOf(Order::class, $order); + $this->assertSame($ordersBefore + 1, Order::count()); + $this->assertNotEmpty($order->order_number); + } + + /** + * 쓰기 축 실행기가 이 대상을 실행하고 결과를 산출합니다. (수치 단언 없음) + * + * 계측이 만든 행은 실행기가 감싼 트랜잭션 롤백으로 되돌아가므로 주문 건수가 늘지 않습니다. + */ + #[Test] + public function 쓰기_축_실행이_결과를_산출하고_흔적을_남기지_않는다(): void + { + $ordersBefore = Order::count(); + + $result = app(WriteAxisRunner::class)->run( + app(BenchmarkProfileRegistry::class)->find('sirsoft-ecommerce/order_create'), + new BenchmarkRunOptions(runs: 1, allowWrite: true), + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertSame(['첫 회(ms)', '중앙값(ms)', '회차', '쿼리(건)', 'DB(ms)'], $result->headers); + $this->assertGreaterThan(0, $result->metrics['query_count'], '주문 생성은 쿼리를 실행한다.'); + + $this->assertSame($ordersBefore, Order::count(), '계측으로 생긴 주문은 롤백되어야 한다.'); + } + + /** + * `--allow-write` 없이는 이 대상이 실행되지 않습니다. + */ + #[Test] + public function allow_write_없이는_실행되지_않는다(): void + { + $ordersBefore = Order::count(); + + $result = app(WriteAxisRunner::class)->run( + app(BenchmarkProfileRegistry::class)->find('sirsoft-ecommerce/order_create'), + new BenchmarkRunOptions(runs: 1), + ); + + $this->assertTrue($result->skipped); + $this->assertStringContainsString('--allow-write', (string) $result->skipReason); + $this->assertSame($ordersBefore, Order::count()); + } +} diff --git a/modules/_bundled/sirsoft-page/module.php b/modules/_bundled/sirsoft-page/module.php index 311148d8..db9a2968 100644 --- a/modules/_bundled/sirsoft-page/module.php +++ b/modules/_bundled/sirsoft-page/module.php @@ -168,4 +168,35 @@ class Module extends AbstractModule ], ]; } + + /** + * 성능 계측 프로파일 정의 (`g7:bench`). + * + * 페이지 목록은 응답 계약상 본문까지 그대로 노출하므로 컬럼 프루닝이 불가합니다 + * (`['*']`). 이 경우 계측의 비교축은 select * vs select id 이며, 그 배수가 지연 조인 + * 적용의 기대 효과입니다. + * + * @return array> 프로파일 키 → 정의 + */ + public function getBenchmarkProfiles(): array + { + return [ + 'pages' => [ + 'type' => 'list', + 'label' => '페이지 목록', + 'table' => 'pages', + 'columns' => ['*'], + 'order' => [['created_at', 'desc'], ['id', 'desc']], + // 페이지는 소프트 삭제 컬럼이 없다 — 실제 스키마 기준 선언 + 'soft_delete' => false, + ], + 'pages_screen' => [ + 'type' => 'screen', + 'label' => '관리자 페이지 목록 화면', + 'route' => 'api.modules.sirsoft-page.admin.pages.index', + 'query' => ['per_page' => 20], + 'permissions' => ['sirsoft-page.pages.read'], + ], + ]; + } } diff --git a/tests/Feature/Console/BenchCommandTest.php b/tests/Feature/Console/BenchCommandTest.php new file mode 100644 index 00000000..d1b5d4af --- /dev/null +++ b/tests/Feature/Console/BenchCommandTest.php @@ -0,0 +1,725 @@ +> $profiles 치환할 코어 프로파일 선언 + */ + private function withCoreProfiles(array $profiles): void + { + Config::set('benchmark.profiles', $profiles); + + // 레지스트리는 수집 결과를 인스턴스에 캐시하므로 새 인스턴스를 강제한다 + $this->bindExtensionManagers([], []); + } + + /** + * 코어 config 선언이 프로파일 목록에 노출됩니다. + * + * @effects core_config_profiles_are_collected + */ + #[Test] + public function 코어_config_선언이_프로파일_목록에_노출된다(): void + { + $this->withCoreProfiles([ + 'bench_users' => [ + 'type' => 'list', + 'label' => '테스트 회원 목록', + 'table' => 'users', + 'columns' => ['id'], + 'order' => [['id', 'desc']], + ], + ]); + + $this->artisan('g7:bench', ['--list-profiles' => true]) + ->expectsOutputToContain('core/bench_users') + ->assertExitCode(0); + } + + /** + * 확장이 선언한 프로파일도 같은 목록에 함께 수집됩니다. + * + * 확장 선언 지점(`getBenchmarkProfiles()`)이 실제로 수집 경로에 연결돼 있는지를 + * 확인합니다. 특정 번들 확장에 의존하지 않도록 가짜 모듈을 매니저에 주입합니다. + * + * @effects module_declared_profiles_are_collected, profile_source_kind_and_identifier_are_recorded + */ + #[Test] + public function 확장_선언_프로파일이_수집된다(): void + { + $this->withCoreProfiles([]); + $this->fakeModuleWithProfiles([ + 'fake_list' => [ + 'type' => 'list', + 'label' => '가짜 확장 목록', + 'table' => 'users', + 'columns' => ['id'], + ], + ]); + + $profiles = app(BenchmarkProfileRegistry::class)->all(); + + $this->assertArrayHasKey('vendor-fake/fake_list', $profiles); + $this->assertSame('module', $profiles['vendor-fake/fake_list']->sourceKind); + $this->assertSame(BenchmarkAxis::ListQuery, $profiles['vendor-fake/fake_list']->axis); + } + + /** + * 플러그인이 선언한 프로파일도 함께 수집됩니다. + * + * @effects plugin_declared_profiles_are_collected + */ + #[Test] + public function 플러그인_선언_프로파일이_수집된다(): void + { + $this->withCoreProfiles([]); + $this->fakeExtensionsWithProfiles([], [ + 'fake_plugin_list' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + $profiles = app(BenchmarkProfileRegistry::class)->all(); + + $this->assertArrayHasKey('vendor-fake-plugin/fake_plugin_list', $profiles); + $this->assertSame('plugin', $profiles['vendor-fake-plugin/fake_plugin_list']->sourceKind); + } + + /** + * 확장 하나의 선언 실패가 전체 목록을 날리지 않습니다. + * + * 확장 하나가 던진 예외로 목록이 비면 계측 자체를 못 하게 되므로, 실패한 확장만 사유와 + * 함께 제외하고 나머지는 살립니다. + * + * @effects extension_declaration_failure_is_isolated_to_that_extension + */ + #[Test] + public function 확장_선언_실패는_그_확장에만_국한된다(): void + { + $this->withCoreProfiles([ + 'survivor' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + $broken = new class + { + /** + * @return string 모듈 식별자 + */ + public function getIdentifier(): string + { + return 'vendor-broken'; + } + + /** + * @return array> 선언 (항상 실패) + */ + public function getBenchmarkProfiles(): array + { + throw new \RuntimeException('declaration blew up'); + } + }; + + $this->bindExtensionManagers([$broken], []); + + $registry = app(BenchmarkProfileRegistry::class); + + // 코어 선언은 살아남는다 + $this->assertArrayHasKey('core/survivor', $registry->all()); + // 실패한 확장은 사유와 함께 드러난다 + $this->assertStringContainsString('vendor-broken', implode("\n", $registry->warnings())); + } + + /** + * 잘못된 선언은 조용히 버려지지 않고 사유와 함께 경고로 드러납니다. + * + * @effects missing_type_declaration_is_rejected_with_reason, unknown_axis_type_is_rejected_with_allowed_values, missing_required_option_is_rejected_naming_the_option, rejected_declarations_appear_in_warnings_not_silently_dropped + */ + #[Test] + public function 잘못된_선언은_사유와_함께_경고로_드러난다(): void + { + $this->withCoreProfiles([ + 'no_type' => ['table' => 'users'], + 'unknown_axis' => ['type' => 'nonsense', 'table' => 'users'], + 'missing_required' => ['type' => 'screen'], + 'valid' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + $registry = app(BenchmarkProfileRegistry::class); + + // 정상 선언만 남는다 + $this->assertSame(['core/valid'], array_keys($registry->all())); + + $warnings = implode("\n", $registry->warnings()); + $this->assertStringContainsString('core/no_type', $warnings); + $this->assertStringContainsString('core/unknown_axis', $warnings); + $this->assertStringContainsString('core/missing_required', $warnings); + // screen 축은 route 또는 uri 중 하나가 필요하다는 사유가 드러나야 한다 + $this->assertStringContainsString('route|uri', $warnings); + } + + /** + * 짧은 키가 둘 이상의 출처에서 선언되면 후보를 제시하고 실행을 거부합니다. + * + * 임의로 하나를 고르면 어느 확장의 대상을 잰 것인지 알 수 없게 됩니다. + * + * @effects ambiguous_short_key_lists_candidates_and_refuses, qualified_key_resolves_exactly, unique_short_key_resolves + */ + #[Test] + public function 모호한_짧은_키는_후보를_제시하고_거부한다(): void + { + $this->withCoreProfiles([ + 'orders' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + $this->fakeModuleWithProfiles([ + 'orders' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + $this->artisan('g7:bench', ['--profile' => ['orders']]) + ->expectsOutputToContain('모호') + ->assertExitCode(1); + + // 정규화 키로 지목하면 정상 해석된다 + $registry = app(BenchmarkProfileRegistry::class); + $profile = $registry->find('vendor-fake/orders'); + $this->assertInstanceOf(BenchmarkProfile::class, $profile); + $this->assertSame('vendor-fake', $profile->sourceIdentifier); + + // 겹치지 않는 짧은 키는 그대로 해석된다 (모호할 때만 거부) + $this->withCoreProfiles([ + 'only_here' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + [$unique, $error] = app(BenchmarkProfileRegistry::class)->resolve('only_here'); + $this->assertNull($error); + $this->assertSame('core/only_here', $unique->qualifiedKey()); + } + + /** + * 미등록 프로파일 키는 실패로 끝납니다. + * + * @effects unknown_key_fails_with_list_profiles_hint + */ + #[Test] + public function 미등록_프로파일_키는_실패한다(): void + { + $this->withCoreProfiles([]); + + $this->artisan('g7:bench', ['--profile' => ['nope']]) + ->expectsOutputToContain('등록되지 않은 프로파일') + ->assertExitCode(1); + } + + /** + * 대상 지정이 없으면 무엇을 잴지 알 수 없으므로 실패합니다. + * + * @effects no_selection_fails_asking_for_profile_or_axis_or_all + */ + #[Test] + public function 대상_미지정_실행은_실패한다(): void + { + $this->withCoreProfiles([ + 'bench_users' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + $this->artisan('g7:bench') + ->expectsOutputToContain('--profile / --axis / --all') + ->assertExitCode(1); + } + + /** + * 목록 축은 컬럼 폭 3축 표를 산출합니다. (수치는 단언하지 않음) + * + * @effects list_axis_reports_all_list_and_id_only_columns + */ + #[Test] + public function 목록_축은_컬럼_폭_3축_표를_산출한다(): void + { + $this->withCoreProfiles([ + 'bench_users' => [ + 'type' => 'list', + 'table' => 'users', + 'columns' => ['id', 'email'], + 'order' => [['id', 'desc']], + 'soft_delete' => true, + ], + ]); + + $exitCode = Artisan::call('g7:bench', [ + '--profile' => ['core/bench_users'], + '--offsets' => '0', + '--runs' => 1, + ]); + + $this->assertSame(0, $exitCode); + + $output = Artisan::output(); + + // 세 축이 함께 나와야 배수(전체÷ID)의 근거가 성립한다 + $this->assertStringContainsString('전체 컬럼(ms)', $output); + $this->assertStringContainsString('목록 컬럼(ms)', $output); + $this->assertStringContainsString('ID만(ms)', $output); + $this->assertStringContainsString('전체÷ID', $output); + } + + /** + * 없는 테이블을 가리키는 목록 프로파일은 사유와 함께 건너뛰어집니다. + * + * 확장이 제거된 설치본에서 계측이 죽지 않아야 하고, 동시에 "측정했다"로 읽혀서도 + * 안 되므로 사유가 남고 종료 코드는 실패입니다. + * + * @effects skipped_profiles_remain_in_output_with_reason + */ + #[Test] + public function 없는_테이블_프로파일은_사유와_함께_건너뛴다(): void + { + $this->withCoreProfiles([ + 'ghost' => ['type' => 'list', 'table' => 'table_that_does_not_exist', 'columns' => ['id']], + ]); + + $this->artisan('g7:bench', ['--profile' => ['core/ghost'], '--offsets' => '0', '--runs' => 1]) + ->expectsOutputToContain('테이블이 없습니다') + ->assertExitCode(1); + } + + /** + * 쓰기 축은 `--allow-write` 없이 실행을 거부합니다. + * + * @effects mutating_axes_refuse_without_allow_write + */ + #[Test] + public function 쓰기_축은_allow_write_없이_거부된다(): void + { + $this->withCoreProfiles([ + 'bench_write' => [ + 'type' => 'write', + 'callback' => [BenchCommandWriteSubject::class, 'run'], + ], + ]); + + $this->artisan('g7:bench', ['--profile' => ['core/bench_write']]) + ->expectsOutputToContain('--allow-write') + ->assertExitCode(1); + } + + /** + * 데이터를 변경하는 배치도 `--allow-write` 없이 거부됩니다. + * + * @effects mutating_axes_refuse_without_allow_write + */ + #[Test] + public function 변경하는_배치는_allow_write_없이_거부된다(): void + { + $this->withCoreProfiles([ + 'bench_batch' => ['type' => 'batch', 'command' => 'seo:clear'], + ]); + + $this->artisan('g7:bench', ['--profile' => ['core/bench_batch']]) + ->expectsOutputToContain('--allow-write') + ->assertExitCode(1); + } + + /** + * 쓰기 축 콜백에 클로저를 쓸 수 없다는 사유가 드러납니다. + * + * 코어 선언은 `config:cache` 대상이라 클로저를 담을 수 없으므로 형식을 통일합니다. + * + * @effects closure_callback_is_rejected_with_config_cache_reason + */ + #[Test] + public function 쓰기_축_클로저_콜백은_사유와_함께_거부된다(): void + { + $this->withCoreProfiles([ + 'bench_closure' => [ + 'type' => 'write', + 'callback' => fn () => null, + ], + ]); + + $this->artisan('g7:bench', [ + '--profile' => ['core/bench_closure'], + '--allow-write' => true, + ]) + ->expectsOutputToContain('클로저는 쓸 수 없습니다') + ->assertExitCode(1); + } + + /** + * 알 수 없는 축 이름은 허용 목록과 함께 거부됩니다. + */ + #[Test] + public function 알_수_없는_축_이름은_거부된다(): void + { + $this->withCoreProfiles([]); + + $this->artisan('g7:bench', ['--axis' => 'nonsense']) + ->expectsOutputToContain('알 수 없는 축') + ->assertExitCode(1); + } + + /** + * `--json` 출력이 약속된 최상위 키와 결과 키를 담습니다. + * + * @effects json_output_carries_environment_options_warnings_results + */ + #[Test] + public function json_출력이_약속된_스키마를_담는다(): void + { + $this->withCoreProfiles([ + 'bench_users' => [ + 'type' => 'list', + 'label' => '테스트 회원 목록', + 'table' => 'users', + 'columns' => ['id'], + 'order' => [['id', 'desc']], + ], + ]); + + $exitCode = Artisan::call('g7:bench', [ + '--profile' => ['core/bench_users'], + '--offsets' => '0', + '--runs' => 1, + '--json' => true, + ]); + + $this->assertSame(0, $exitCode); + + $payload = json_decode(Artisan::output(), true); + + $this->assertIsArray($payload, 'JSON 출력이 파싱 가능해야 한다.'); + foreach (['generated_at', 'environment', 'options', 'warnings', 'results'] as $key) { + $this->assertArrayHasKey($key, $payload); + } + + $result = $payload['results'][0]; + foreach (['profile', 'axis', 'label', 'source', 'skipped', 'headers', 'rows', 'metrics', 'notes'] as $key) { + $this->assertArrayHasKey($key, $result); + } + $this->assertSame('core/bench_users', $result['profile']); + $this->assertSame('list', $result['axis']); + $this->assertFalse($result['skipped']); + } + + /** + * `--report` 는 환경 정보와 축 절을 담은 마크다운을 지정 경로에 씁니다. + * + * @effects report_includes_environment_and_run_conditions, report_writes_to_given_path + */ + #[Test] + public function report_는_환경_정보를_담은_마크다운을_쓴다(): void + { + $this->withCoreProfiles([ + 'bench_users' => [ + 'type' => 'list', + 'label' => '테스트 회원 목록', + 'table' => 'users', + 'columns' => ['id'], + 'order' => [['id', 'desc']], + ], + ]); + + $path = storage_path('app/benchmarks/test-'.uniqid().'.md'); + + $this->artisan('g7:bench', [ + '--profile' => ['core/bench_users'], + '--offsets' => '0', + '--runs' => 1, + '--report' => $path, + ])->assertExitCode(0); + + $this->assertFileExists($path); + + $markdown = (string) file_get_contents($path); + + // 환경 정보가 빠진 리포트는 다른 리포트와 비교할 수 없으므로 필수 항목이다 + $this->assertStringContainsString('## 실행 환경', $markdown); + $this->assertStringContainsString('DB 버전', $markdown); + $this->assertStringContainsString('config:cache', $markdown); + $this->assertStringContainsString('## 실행 조건', $markdown); + $this->assertStringContainsString('## 목록 조회 (list)', $markdown); + $this->assertStringContainsString('core/bench_users — 테스트 회원 목록', $markdown); + + @unlink($path); + } + + /** + * 운영 환경에서는 시딩/비움이 거부됩니다. + * + * @effects seeding_and_truncation_are_refused_in_production + */ + #[Test] + public function 운영_환경에서는_시딩이_거부된다(): void + { + $this->withCoreProfiles([ + 'bench_users' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + // 계측 대상 테이블을 실제로 건드리기 전에 거부되는지를 본다 + $this->app->detectEnvironment(fn () => 'production'); + + try { + $this->artisan('g7:bench', [ + '--profile' => ['core/bench_users'], + '--offsets' => '0', + '--runs' => 1, + '--seed' => 10, + ]) + ->expectsOutputToContain('운영 환경에서는 시딩/비움을 사용할 수 없습니다') + ->assertExitCode(1); + } finally { + $this->app->detectEnvironment(fn () => 'testing'); + } + } + + /** + * `--report` 를 값 없이 주면 저장소 미추적 기본 경로에 리포트를 씁니다. + * + * @effects report_defaults_to_untracked_storage_path + */ + #[Test] + public function report_는_값_없이_주면_기본_경로에_쓴다(): void + { + $this->withCoreProfiles([ + 'bench_users' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + $directory = storage_path('app/benchmarks'); + $before = is_dir($directory) ? glob($directory.'/bench-*.md') : []; + + $exitCode = Artisan::call('g7:bench', [ + '--profile' => ['core/bench_users'], + '--offsets' => '0', + '--runs' => 1, + '--report' => null, + ]); + + $this->assertSame(0, $exitCode); + + $after = glob($directory.'/bench-*.md'); + $created = array_values(array_diff($after ?: [], $before ?: [])); + + $this->assertCount(1, $created, '기본 경로(storage/app/benchmarks)에 리포트가 생성되어야 한다.'); + $this->assertStringContainsString('## 실행 환경', (string) file_get_contents($created[0])); + + @unlink($created[0]); + } + + /** + * `--database` 는 계측에 사용할 연결을 다시 지목합니다. + * + * config 가 캐시된 환경에서 연결을 바꿀 유일한 수단이라, 이 치환이 동작하지 않으면 + * 대량 시딩이 개발 DB 로 들어갑니다. + * + * @effects database_override_option_repoints_the_connection + */ + #[Test] + public function database_옵션이_연결을_다시_지목한다(): void + { + $this->withCoreProfiles([ + 'bench_users' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + ]); + + $connection = (string) config('database.default'); + // 실제 접속이 끊기지 않도록 현재 스키마명으로 재지목한다 (치환 경로 자체를 검증) + $current = (string) (config("database.connections.{$connection}.database") + ?: config("database.connections.{$connection}.write.database")); + + Artisan::call('g7:bench', [ + '--profile' => ['core/bench_users'], + '--offsets' => '0', + '--runs' => 1, + '--database' => $current, + ]); + + // 최상위와 읽기/쓰기 하위 모두 지목되어야 한다 — 하나라도 남으면 그 경로만 다른 DB 를 본다 + $this->assertSame($current, config("database.connections.{$connection}.database")); + $this->assertSame($current, config("database.connections.{$connection}.write.database")); + $this->assertSame($current, config("database.connections.{$connection}.read.database")); + } + + /** + * 전부 건너뛴 실행은 실패로 끝납니다. (성공으로 보고하면 "측정했다"로 읽힘) + * + * @effects all_skipped_run_exits_nonzero, skipped_profiles_remain_in_output_with_reason + */ + #[Test] + public function 전부_건너뛴_실행은_실패로_끝난다(): void + { + $this->withCoreProfiles([ + 'ghost_a' => ['type' => 'list', 'table' => 'missing_table_a', 'columns' => ['id']], + 'ghost_b' => ['type' => 'list', 'table' => 'missing_table_b', 'columns' => ['id']], + ]); + + $exitCode = Artisan::call('g7:bench', ['--axis' => 'list', '--offsets' => '0', '--runs' => 1]); + + $this->assertSame(1, $exitCode); + + $output = Artisan::output(); + + // 건너뛴 프로파일이 목록에서 빠지지 않고 사유와 함께 남는다 + $this->assertStringContainsString('core/ghost_a', $output); + $this->assertStringContainsString('core/ghost_b', $output); + $this->assertStringContainsString('테이블이 없습니다', $output); + } + + /** + * 축 필터가 해당 축의 프로파일만 고릅니다. + * + * @effects axis_filter_selects_only_that_axis + */ + #[Test] + public function 축_필터는_해당_축만_고른다(): void + { + $this->withCoreProfiles([ + 'bench_users' => ['type' => 'list', 'table' => 'users', 'columns' => ['id']], + 'bench_screen' => ['type' => 'screen', 'route' => 'api.admin.users.index'], + 'bench_batch' => ['type' => 'batch', 'command' => 'seo:clear'], + ]); + + $registry = app(BenchmarkProfileRegistry::class); + + $this->assertSame(['core/bench_users'], array_keys($registry->byAxis(BenchmarkAxis::ListQuery))); + $this->assertSame(['core/bench_screen'], array_keys($registry->byAxis(BenchmarkAxis::Screen))); + $this->assertSame(['core/bench_batch'], array_keys($registry->byAxis(BenchmarkAxis::Batch))); + $this->assertSame([], array_keys($registry->byAxis(BenchmarkAxis::Write))); + } + + /** + * GET 화면은 변경으로 보지 않고, 비-GET 화면은 변경으로 봅니다. + */ + #[Test] + public function screen_축의_변경_판정은_http_메서드를_따른다(): void + { + $this->withCoreProfiles([ + 'get_screen' => ['type' => 'screen', 'route' => 'api.admin.users.index'], + 'post_screen' => ['type' => 'screen', 'route' => 'api.admin.users.store', 'method' => 'POST'], + 'declared_readonly' => ['type' => 'screen', 'route' => 'api.admin.users.store', 'method' => 'POST', 'mutating' => false], + ]); + + $registry = app(BenchmarkProfileRegistry::class); + + $this->assertFalse($registry->find('core/get_screen')->mutates()); + $this->assertTrue($registry->find('core/post_screen')->mutates()); + // 선언이 축 기본값을 덮어쓴다 + $this->assertFalse($registry->find('core/declared_readonly')->mutates()); + } + + /** + * 프로파일을 선언한 가짜 모듈을 모듈 매니저에 주입합니다. + * + * 특정 번들 확장 설치 여부에 의존하지 않고 "확장 선언이 수집된다"는 성질만 검증합니다. + * + * @param array> $profiles 가짜 모듈이 선언할 프로파일 + */ + private function fakeModuleWithProfiles(array $profiles): void + { + $this->fakeExtensionsWithProfiles($profiles, []); + } + + /** + * 프로파일을 선언한 가짜 모듈/플러그인을 각 매니저에 주입합니다. + * + * @param array> $modileProfiles 가짜 모듈이 선언할 프로파일 + * @param array> $pluginProfiles 가짜 플러그인이 선언할 프로파일 + */ + private function fakeExtensionsWithProfiles(array $modileProfiles, array $pluginProfiles): void + { + $this->bindExtensionManagers( + $modileProfiles === [] ? [] : [$this->fakeExtension('vendor-fake', $modileProfiles)], + $pluginProfiles === [] ? [] : [$this->fakeExtension('vendor-fake-plugin', $pluginProfiles)], + ); + } + + /** + * 프로파일만 선언하는 가짜 확장 인스턴스를 만듭니다. + * + * @param string $identifier 확장 식별자 + * @param array> $profiles 선언할 프로파일 + * @return object 가짜 확장 + */ + private function fakeExtension(string $identifier, array $profiles): object + { + return new class($identifier, $profiles) + { + /** + * @param string $identifier 확장 식별자 + * @param array> $profiles 선언할 프로파일 + */ + public function __construct( + private readonly string $identifier, + private readonly array $profiles, + ) {} + + /** + * @return string 확장 식별자 + */ + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @return array> 선언한 프로파일 + */ + public function getBenchmarkProfiles(): array + { + return $this->profiles; + } + }; + } + + /** + * 확장 매니저를 가짜로 바인딩하고 레지스트리 캐시를 비웁니다. + * + * @param array $modules 활성 모듈 목록 + * @param array $plugins 활성 플러그인 목록 + */ + private function bindExtensionManagers(array $modules, array $plugins): void + { + $moduleManager = \Mockery::mock(ModuleManagerInterface::class); + $moduleManager->shouldReceive('getActiveModules')->andReturn($modules); + + $pluginManager = \Mockery::mock(PluginManagerInterface::class); + $pluginManager->shouldReceive('getActivePlugins')->andReturn($plugins); + + $this->app->instance(ModuleManagerInterface::class, $moduleManager); + $this->app->instance(PluginManagerInterface::class, $pluginManager); + $this->app->forgetInstance(BenchmarkProfileRegistry::class); + } +} + +/** + * 쓰기 축 콜백 형식 검증용 대상 (실행되지 않음 — 가드에서 먼저 막힘). + */ +class BenchCommandWriteSubject +{ + /** + * 계측 대상 자리표시자. + */ + public function run(mixed $context = null): void {} +} diff --git a/tests/Unit/Benchmark/BenchmarkAxisRunnerTest.php b/tests/Unit/Benchmark/BenchmarkAxisRunnerTest.php new file mode 100644 index 00000000..3ae4037d --- /dev/null +++ b/tests/Unit/Benchmark/BenchmarkAxisRunnerTest.php @@ -0,0 +1,737 @@ + $options 축 고유 옵션 + * @return BenchmarkProfile 프로파일 + */ + private function profile(BenchmarkAxis $axis, array $options): BenchmarkProfile + { + return new BenchmarkProfile( + key: 'sample', + axis: $axis, + sourceKind: 'core', + sourceIdentifier: 'core', + options: $options, + ); + } + + /** + * 목록 축은 컬럼 폭 3축을 재고 배수를 함께 산출합니다. + * + * @effects list_axis_reports_all_list_and_id_only_columns + */ + #[Test] + public function 목록_축은_3축_결과와_배수를_산출한다(): void + { + $result = app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, [ + 'table' => 'users', + 'columns' => ['id', 'email'], + 'order' => [['id', 'desc']], + 'soft_delete' => true, + ]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + + $this->assertFalse($result->skipped); + $this->assertSame(['OFFSET', '전체 컬럼(ms)', '목록 컬럼(ms)', 'ID만(ms)', '전체÷ID'], $result->headers); + $this->assertCount(1, $result->rows); + $this->assertSame('users', $result->metrics['table']); + $this->assertSame(['id', 'email'], $result->metrics['columns']); + + $offset = $result->metrics['offsets'][0]; + foreach (['offset', 'all_ms', 'list_ms', 'id_only_ms', 'ratio'] as $key) { + $this->assertArrayHasKey($key, $offset); + } + } + + /** + * 배수는 키 컬럼 조회를 기준선으로 산출됩니다. + * + * 이 기준선이 지연 조인의 inner 쿼리에 해당하므로, 배수가 곧 적용 기대 효과입니다. + * + * @effects list_axis_ratio_is_derived_from_id_only_baseline + */ + #[Test] + public function 배수는_키_컬럼_기준선으로_산출된다(): void + { + $result = app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, ['table' => 'users', 'columns' => ['*']]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + + $offset = $result->metrics['offsets'][0]; + + $this->assertSame( + round($offset['all_ms'] / $offset['id_only_ms'], 1), + $offset['ratio'], + '배수는 전체 컬럼 ÷ 키 컬럼이어야 한다.' + ); + } + + /** + * 선언된 필터와 소프트 삭제가 실제 계측 쿼리에 반영됩니다. + * + * 필터 없이 재면 인덱스 선택이 달라져 화면에서 일어나는 일과 다른 것을 잽니다. + * + * @effects declared_filters_are_applied_to_measured_query, soft_delete_declaration_adds_deleted_at_predicate + */ + #[Test] + public function 선언된_필터와_소프트_삭제가_계측_쿼리에_반영된다(): void + { + // 확장 설치 여부에 의존하지 않도록 소프트 삭제 컬럼이 있는 코어 테이블로 검증한다. + // 컬럼이 없는 테이블에서 술어가 붙지 않는 성질은 다음 테스트가 다룬다. + $collected = app(QueryCollector::class)->collect(function () { + app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, [ + 'table' => 'attachments', + 'columns' => ['id'], + 'filters' => ['disk' => 'public'], + 'soft_delete' => true, + ]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + }); + + $sql = str_replace('`', '"', implode("\n", array_column($collected['queries'], 'sql'))); + + $this->assertStringContainsString('"deleted_at" is null', $sql); + $this->assertStringContainsString('"disk" = ?', $sql); + } + + /** + * 연산자 형태 필터가 계측 쿼리에 반영됩니다. + * + * 등가 비교만 지원하면 화면이 실제로 거는 필터를 선언할 수 없는 목록이 생깁니다 — + * 주문 목록은 상태 미지정 시 임시 주문 상태를 `NOT IN` 으로 제외합니다. + * + * @effects operator_filters_are_applied_to_measured_query + */ + #[Test] + public function 연산자_형태_필터가_계측_쿼리에_반영된다(): void + { + $collected = app(QueryCollector::class)->collect(function () { + app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, [ + 'table' => 'attachments', + 'columns' => ['id'], + 'filters' => [ + 'disk' => ['not in', ['public', 'local']], + 'id' => ['>=', 10], + ], + ]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + }); + + $sql = str_replace('`', '"', implode("\n", array_column($collected['queries'], 'sql'))); + + $this->assertStringContainsString('"disk" not in (?, ?)', $sql); + $this->assertStringContainsString('"id" >= ?', $sql); + } + + /** + * 배열 값을 등가 비교로 선언하면 IN 으로 오해석하지 않습니다. + * + * 형태(2원소 + 첫 원소가 문자열)로만 연산자 선언을 판정하므로, 값 자체가 배열인 등가 + * 비교와 구분됩니다. + * + * @effects equality_filter_is_not_misread_as_operator_form + */ + #[Test] + public function 세_원소_배열은_연산자_선언으로_보지_않는다(): void + { + $result = app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, [ + 'table' => 'attachments', + 'columns' => ['id'], + // 2원소가 아니라 연산자 선언 형태가 아니다 → 등가 비교로 처리(연산자 검증 통과) + 'filters' => ['disk' => ['a', 'b', 'c']], + ]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + + // 연산자 검증에 걸리지 않고 실행된다 + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + } + + /** + * 알 수 없는 연산자는 측정 전에 사유와 함께 거부됩니다. + * + * 조용히 무시하면 그 필터가 빠진 채로 측정되어, 화면과 다른 것을 재면서도 정상 측정으로 + * 보고됩니다. + * + * @effects unknown_filter_operator_is_refused_before_measuring + */ + #[Test] + public function 알_수_없는_필터_연산자는_측정_전에_거부된다(): void + { + $result = app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, [ + 'table' => 'attachments', + 'columns' => ['id'], + 'filters' => ['disk' => ['betwixt', ['a', 'b']]], + ]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + + $this->assertTrue($result->skipped); + $this->assertStringContainsString('연산자를 알 수 없습니다', (string) $result->skipReason); + $this->assertStringContainsString('betwixt', (string) $result->skipReason); + } + + /** + * 소프트 삭제 컬럼이 없는 테이블에는 술어를 붙이지 않습니다. + * + * 선언을 그대로 믿고 붙이면 컬럼 없는 테이블에서 SQL 오류로 계측이 죽습니다. + * + * @effects soft_delete_declaration_adds_deleted_at_predicate + */ + #[Test] + public function 소프트_삭제_컬럼이_없으면_술어를_붙이지_않는다(): void + { + $collected = app(QueryCollector::class)->collect(function () { + app(ListAxisRunner::class)->run( + // users 에는 deleted_at 이 없다 (탈퇴는 status 로 표현) + $this->profile(BenchmarkAxis::ListQuery, [ + 'table' => 'users', + 'columns' => ['id'], + 'soft_delete' => true, + ]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + }); + + $sql = str_replace('`', '"', implode("\n", array_column($collected['queries'], 'sql'))); + + $this->assertStringNotContainsString('"deleted_at" is null', $sql); + } + + /** + * `--explain` 은 목록 컬럼과 키 컬럼 두 폭의 실행 계획을 모두 수집합니다. + * + * 키 컬럼 계획이 지연 조인 inner 의 계획이라, 인덱스 설계 근거는 두 계획의 대조입니다. + * + * @effects explain_option_collects_plans_for_both_column_widths + */ + #[Test] + public function explain_은_두_폭의_실행_계획을_수집한다(): void + { + $result = app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, ['table' => 'users', 'columns' => ['id', 'email']]), + new BenchmarkRunOptions(offsets: [0], runs: 1, explain: true), + ); + + $notes = implode("\n", $result->notes); + + $this->assertStringContainsString('EXPLAIN @ OFFSET 0 — 목록 컬럼', $notes); + $this->assertStringContainsString('EXPLAIN @ OFFSET 0 — ID 만 (지연 조인 inner)', $notes); + } + + /** + * `['*']` 선언은 실제 스키마 컬럼으로 펼쳐집니다. + * + * 응답 계약상 전 컬럼을 노출하는 목록의 비교축이 `select *` vs `select id` 임을 고정합니다. + * + * @effects wildcard_columns_expand_to_actual_schema_columns + */ + #[Test] + public function 와일드카드_컬럼은_실제_스키마로_펼쳐진다(): void + { + $result = app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, ['table' => 'users', 'columns' => ['*']]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + + $this->assertContains('id', $result->metrics['columns']); + $this->assertContains('email', $result->metrics['columns']); + $this->assertGreaterThan(2, count($result->metrics['columns'])); + } + + /** + * 스키마에 없는 선언 컬럼은 걸러집니다. + * + * 확장 버전에 따라 컬럼 구성이 달라도 계측이 죽지 않아야 합니다. + * + * @effects nonexistent_declared_columns_are_filtered_out + */ + #[Test] + public function 없는_컬럼은_걸러진다(): void + { + $result = app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, [ + 'table' => 'users', + 'columns' => ['id', 'column_that_does_not_exist'], + ]), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + + $this->assertSame(['id'], $result->metrics['columns']); + } + + /** + * 없는 테이블은 사유와 함께 건너뛰어집니다. + */ + #[Test] + public function 없는_테이블은_사유와_함께_건너뛴다(): void + { + $result = app(ListAxisRunner::class)->run( + $this->profile(BenchmarkAxis::ListQuery, ['table' => 'nope_missing_table']), + new BenchmarkRunOptions(offsets: [0], runs: 1), + ); + + $this->assertTrue($result->skipped); + $this->assertStringContainsString('테이블이 없습니다', (string) $result->skipReason); + } + + /** + * 화면 축은 상태 코드·응답 시간·쿼리 건수를 산출하고 계측 흔적을 남기지 않습니다. + * + * @effects screen_axis_dispatches_through_http_kernel_with_middleware, screen_axis_reports_status_response_time_and_query_count, absence_of_n_plus_one_is_stated_explicitly, measurement_leaves_no_residual_rows_after_rollback, container_request_and_auth_guards_are_restored_after_each_dispatch, route_name_declaration_resolves_uri_without_prefix_assembly + */ + #[Test] + public function 화면_축은_응답과_쿼리_요약을_산출하고_흔적을_남기지_않는다(): void + { + $usersBefore = User::count(); + + $result = app(ScreenAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Screen, [ + 'route' => 'api.admin.users.index', + 'query' => ['per_page' => 5], + 'permissions' => ['core.users.read'], + ]), + new BenchmarkRunOptions(runs: 1), + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertSame(['요청', '상태', '응답(ms)', '쿼리(건)', 'DB(ms)'], $result->headers); + $this->assertSame(200, $result->metrics['status']); + $this->assertGreaterThan(0, $result->metrics['query_count'], '화면 1장은 최소 1건 이상 쿼리를 실행한다.'); + $this->assertTrue($result->metrics['acting_user']['ephemeral'], '기본은 계측용 임시 계정이다.'); + + // N+1 후보가 없으면 없다고 명시해야 한다 — 침묵은 "확인 안 함"과 구분되지 않는다 + $notes = implode("\n", $result->notes); + $this->assertTrue( + str_contains($notes, 'N+1 후보'), + 'N+1 후보 유무가 결과에 명시되어야 한다.' + ); + + // 계측 계정 생성부터 요청 처리까지 롤백되므로 잔여 계정/역할/토큰이 없어야 한다 + $this->assertSame($usersBefore, User::count()); + $this->assertSame(0, DB::table('roles')->where('identifier', 'like', 'g7_bench_%')->count()); + $this->assertSame(0, DB::table('personal_access_tokens')->where('name', 'like', 'g7-bench-%')->count()); + + // 인증 가드가 복구되지 않으면 이어지는 계측이 앞 요청의 인증을 물려받는다 + $this->assertFalse(Auth::check(), '계측 후 인증 가드가 복구되어야 한다.'); + } + + /** + * 선언한 권한만 임시 계정에 부여됩니다. + * + * 전권 계정으로 재면 권한 검사 비용과 분기가 실제와 달라집니다. + * + * @effects ephemeral_admin_receives_only_declared_permissions + */ + #[Test] + public function 임시_계정은_선언한_권한만_받는다(): void + { + // 선언한 권한이 없으면 관리자 목록 화면의 권한 미들웨어를 통과하지 못해야 한다 + $result = app(ScreenAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Screen, [ + 'route' => 'api.admin.users.index', + 'permissions' => [], + ]), + new BenchmarkRunOptions(runs: 1), + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertSame( + 403, + $result->metrics['status'], + '권한을 선언하지 않으면 권한 미들웨어에서 막혀야 한다 — 전권 계정이 아니라는 실증.' + ); + } + + /** + * `--as` 로 지정한 기존 계정으로 계측할 수 있습니다. + * + * @effects as_option_measures_with_the_named_existing_account + */ + #[Test] + public function as_옵션은_지정_계정으로_계측한다(): void + { + $user = User::factory()->create(); + + $result = app(ScreenAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Screen, ['route' => 'api.admin.users.index']), + new BenchmarkRunOptions(runs: 1, asUser: $user->email), + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertFalse($result->metrics['acting_user']['ephemeral']); + $this->assertSame($user->email, $result->metrics['acting_user']['email']); + } + + /** + * 없는 계정을 지정하면 사유와 함께 건너뛰어집니다. + */ + #[Test] + public function 없는_지정_계정은_사유와_함께_건너뛴다(): void + { + $result = app(ScreenAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Screen, ['route' => 'api.admin.users.index']), + new BenchmarkRunOptions(runs: 1, asUser: 'no-such-account@example.test'), + ); + + $this->assertTrue($result->skipped); + $this->assertStringContainsString('계정을 찾을 수 없습니다', (string) $result->skipReason); + } + + /** + * 등록되지 않은 라우트명은 404 를 재는 대신 즉시 건너뛰어집니다. + * + * @effects missing_route_name_fails_instead_of_measuring_a_404 + */ + #[Test] + public function 없는_라우트명은_사유와_함께_건너뛴다(): void + { + $result = app(ScreenAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Screen, ['route' => 'api.route.that.does.not.exist']), + new BenchmarkRunOptions(runs: 1), + ); + + $this->assertTrue($result->skipped); + $this->assertStringContainsString('등록되지 않은 라우트명', (string) $result->skipReason); + } + + /** + * 쓰기 축의 `prepare` 는 계측 구간 밖에서 회차마다 실행되고 결과가 콜백에 전달됩니다. + * + * @effects prepare_runs_once_per_iteration, callback_receives_prepare_result, write_axis_reports_first_run_and_median_separately + */ + #[Test] + public function 쓰기_축은_prepare_를_회차마다_계측_밖에서_실행한다(): void + { + BenchmarkWriteSpy::reset(); + + $result = app(WriteAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Write, [ + 'prepare' => [BenchmarkWriteSpy::class, 'prepare'], + 'callback' => [BenchmarkWriteSpy::class, 'create'], + 'cleanup' => [BenchmarkWriteSpy::class, 'cleanup'], + ]), + new BenchmarkRunOptions(runs: 3, allowWrite: true), + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertSame(3, BenchmarkWriteSpy::$prepareCalls, 'prepare 는 회차마다 실행된다.'); + $this->assertSame(3, BenchmarkWriteSpy::$createCalls); + $this->assertSame([1, 2, 3], BenchmarkWriteSpy::$receivedContexts, 'prepare 반환값이 콜백에 전달된다.'); + $this->assertSame(1, BenchmarkWriteSpy::$cleanupCalls, 'cleanup 은 계측 종료 후 1회 실행된다.'); + + $this->assertSame(['첫 회(ms)', '중앙값(ms)', '회차', '쿼리(건)', 'DB(ms)'], $result->headers); + $this->assertCount(3, $result->metrics['samples_ms']); + } + + /** + * `prepare` 실행은 계측 창 밖이라 쿼리 집계에 섞이지 않습니다. + * + * 시간이 아니라 쿼리 건수로 확인합니다 — 시간 기반 확인은 실행 환경에 따라 흔들립니다. + * + * @effects prepare_runs_outside_the_measured_window + */ + #[Test] + public function prepare_쿼리는_계측_집계에_섞이지_않는다(): void + { + BenchmarkWriteSpy::reset(); + BenchmarkWriteSpy::$prepareQueries = 3; + BenchmarkWriteSpy::$createQueries = 1; + + $result = app(WriteAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Write, [ + 'prepare' => [BenchmarkWriteSpy::class, 'prepare'], + 'callback' => [BenchmarkWriteSpy::class, 'create'], + ]), + new BenchmarkRunOptions(runs: 1, allowWrite: true), + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertSame( + 1, + $result->metrics['query_count'], + 'prepare 가 실행한 3건은 집계에서 빠지고 계측 대상 1건만 남아야 한다.' + ); + } + + /** + * 쓰기 축은 `--allow-write` 없이 실행을 거부합니다. + */ + #[Test] + public function 쓰기_축은_allow_write_없이_거부한다(): void + { + BenchmarkWriteSpy::reset(); + + $result = app(WriteAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Write, ['callback' => [BenchmarkWriteSpy::class, 'create']]), + new BenchmarkRunOptions(runs: 1), + ); + + $this->assertTrue($result->skipped); + $this->assertStringContainsString('--allow-write', (string) $result->skipReason); + $this->assertSame(0, BenchmarkWriteSpy::$createCalls, '거부되면 콜백이 실행되지 않아야 한다.'); + } + + /** + * `cleanup` 실패가 계측 결과를 삼키지 않습니다. + * + * @effects cleanup_failure_does_not_swallow_the_measurement + */ + #[Test] + public function cleanup_실패가_계측_결과를_삼키지_않는다(): void + { + BenchmarkWriteSpy::reset(); + BenchmarkWriteSpy::$cleanupThrows = true; + + $messages = []; + + $result = app(WriteAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Write, [ + 'callback' => [BenchmarkWriteSpy::class, 'create'], + 'cleanup' => [BenchmarkWriteSpy::class, 'cleanup'], + ]), + new BenchmarkRunOptions(runs: 1, allowWrite: true), + function (string $message) use (&$messages) { + $messages[] = $message; + }, + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertStringContainsString('cleanup 실패', implode("\n", $messages)); + } + + /** + * 배치 축은 소요 시간과 피크 메모리(실행 전/후)를 산출합니다. + * + * @effects batch_axis_reports_elapsed_time_and_peak_memory, peak_memory_before_and_after_are_both_reported + */ + #[Test] + public function 배치_축은_시간과_피크_메모리를_산출한다(): void + { + $result = app(BatchAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Batch, ['command' => 'seo:clear']), + new BenchmarkRunOptions(allowWrite: true), + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertSame(['커맨드', '종료코드', '소요(ms)', '피크 메모리', '메모리 증가', '쿼리(건)'], $result->headers); + $this->assertSame('seo:clear', $result->metrics['command']); + // 피크는 프로세스 누적값이라 실행 전 값이 함께 있어야 이 배치의 기여를 판정할 수 있다 + $this->assertArrayHasKey('peak_memory_before_bytes', $result->metrics); + $this->assertArrayHasKey('peak_memory_after_bytes', $result->metrics); + $this->assertGreaterThanOrEqual( + $result->metrics['peak_memory_before_bytes'], + $result->metrics['peak_memory_after_bytes'], + '피크 메모리는 감소하지 않는다.' + ); + } + + /** + * 실패 종료한 배치는 부분 측정임을 사유로 남깁니다. + * + * 실패를 감추면 "이 시간이 전체 처리 비용"으로 읽힙니다. + * + * @effects batch_axis_reports_nonzero_exit_code_as_partial_measurement + */ + #[Test] + public function 실패_종료_배치는_부분_측정임을_남긴다(): void + { + Artisan::command('bench:always-fails', fn () => 3)->describe('계측 테스트용 실패 커맨드'); + + $result = app(BatchAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Batch, ['command' => 'bench:always-fails']), + new BenchmarkRunOptions(allowWrite: true), + ); + + $this->assertFalse($result->skipped, '건너뜀 사유: '.(string) $result->skipReason); + $this->assertSame(3, $result->metrics['exit_code']); + $this->assertStringContainsString('실패 종료', implode("\n", $result->notes)); + } + + /** + * 등록되지 않은 커맨드는 사유와 함께 건너뛰어집니다. + * + * @effects unregistered_command_is_skipped_with_reason + */ + #[Test] + public function 없는_커맨드는_사유와_함께_건너뛴다(): void + { + $result = app(BatchAxisRunner::class)->run( + $this->profile(BenchmarkAxis::Batch, ['command' => 'no:such-command']), + new BenchmarkRunOptions(allowWrite: true), + ); + + $this->assertTrue($result->skipped); + $this->assertStringContainsString('등록되지 않은 커맨드', (string) $result->skipReason); + } + + /** + * 쿼리 수집기는 계측 구간의 쿼리만 모으고 반복 SQL 을 N+1 후보로 보고합니다. + * + * @effects repeated_identical_sql_is_reported_as_n_plus_one_candidate + */ + #[Test] + public function 쿼리_수집기는_구간_밖_쿼리를_섞지_않는다(): void + { + $collector = app(QueryCollector::class); + + // 구간 밖 쿼리 + DB::table('users')->limit(1)->get(); + + $collected = $collector->collect(function () { + for ($i = 0; $i < 6; $i++) { + DB::table('users')->where('id', $i)->limit(1)->get(); + } + }); + + // 구간 밖 쿼리를 제외한 6건만 모인다 + $this->assertCount(6, $collected['queries']); + + $summary = $collector->summarize($collected['queries']); + $this->assertSame(6, $summary['count']); + // 같은 SQL 6회(임계 5회 이상) → N+1 후보로 보고 + $this->assertNotEmpty($summary['n_plus_one']); + $this->assertSame(6, $summary['n_plus_one'][0]['count']); + } + + /** + * 반복이 임계 미만이면 N+1 후보로 보고하지 않습니다. + */ + #[Test] + public function 임계_미만_반복은_후보로_보고하지_않는다(): void + { + $collector = app(QueryCollector::class); + + $collected = $collector->collect(function () { + for ($i = 0; $i < 3; $i++) { + DB::table('users')->where('id', $i)->limit(1)->get(); + } + }); + + $this->assertSame([], $collector->summarize($collected['queries'])['n_plus_one']); + } +} + +/** + * 쓰기 축 계측 대상 스파이 — prepare/callback/cleanup 호출 순서와 인자 전달을 관찰한다. + */ +class BenchmarkWriteSpy +{ + public static int $prepareCalls = 0; + + public static int $createCalls = 0; + + public static int $cleanupCalls = 0; + + /** + * @var array + */ + public static array $receivedContexts = []; + + public static bool $cleanupThrows = false; + + public static int $prepareQueries = 0; + + public static int $createQueries = 0; + + /** + * 관찰 상태를 초기화합니다. + */ + public static function reset(): void + { + self::$prepareCalls = 0; + self::$createCalls = 0; + self::$cleanupCalls = 0; + self::$receivedContexts = []; + self::$cleanupThrows = false; + self::$prepareQueries = 0; + self::$createQueries = 0; + } + + /** + * 선행 준비 (계측 구간 밖). + * + * @param int $run 회차 번호 + * @return int 회차 번호를 그대로 컨텍스트로 넘긴다 + */ + public function prepare(int $run): int + { + self::$prepareCalls++; + $this->runQueries(self::$prepareQueries); + + return $run; + } + + /** + * 계측 대상. + * + * @param mixed $context prepare 반환값 + */ + public function create(mixed $context = null): void + { + self::$createCalls++; + self::$receivedContexts[] = $context; + $this->runQueries(self::$createQueries); + } + + /** + * 관찰용 쿼리를 지정 횟수만큼 실행합니다. + * + * @param int $count 실행할 쿼리 수 + */ + private function runQueries(int $count): void + { + for ($i = 0; $i < $count; $i++) { + DB::table('users')->where('id', -($i + 1))->limit(1)->get(); + } + } + + /** + * 잔여물 정리. + */ + public function cleanup(): void + { + self::$cleanupCalls++; + + if (self::$cleanupThrows) { + throw new \RuntimeException('정리 실패 재현'); + } + } +} diff --git a/tests/Unit/Benchmark/BenchmarkProfileTest.php b/tests/Unit/Benchmark/BenchmarkProfileTest.php new file mode 100644 index 00000000..4cf5c559 --- /dev/null +++ b/tests/Unit/Benchmark/BenchmarkProfileTest.php @@ -0,0 +1,153 @@ + $options 축 고유 옵션 + * @param string $sourceIdentifier 출처 식별자 + * @return BenchmarkProfile 프로파일 + */ + private function profile(BenchmarkAxis $axis, array $options = [], string $sourceIdentifier = 'core'): BenchmarkProfile + { + return new BenchmarkProfile( + key: 'sample', + axis: $axis, + sourceKind: $sourceIdentifier === 'core' ? 'core' : 'module', + sourceIdentifier: $sourceIdentifier, + options: $options, + label: '샘플', + ); + } + + /** + * 정규화 키는 출처와 키를 합쳐 전역 고유해집니다. + */ + #[Test] + public function 정규화_키는_출처를_포함한다(): void + { + $this->assertSame('core/sample', $this->profile(BenchmarkAxis::ListQuery)->qualifiedKey()); + $this->assertSame( + 'vendor-mod/sample', + $this->profile(BenchmarkAxis::ListQuery, [], 'vendor-mod')->qualifiedKey() + ); + } + + /** + * 축 고유 옵션은 기본값과 함께 읽힙니다. + */ + #[Test] + public function 옵션은_기본값과_함께_읽힌다(): void + { + $profile = $this->profile(BenchmarkAxis::ListQuery, ['table' => 'users']); + + $this->assertSame('users', $profile->option('table')); + $this->assertSame(['id'], $profile->option('columns', ['id'])); + $this->assertNull($profile->option('absent')); + } + + /** + * 목록/화면 축은 기본적으로 데이터를 변경하지 않고, 쓰기/배치 축은 변경합니다. + */ + #[Test] + public function 축_기본_변경_판정(): void + { + $this->assertFalse($this->profile(BenchmarkAxis::ListQuery)->mutates()); + $this->assertFalse($this->profile(BenchmarkAxis::Screen)->mutates()); + $this->assertTrue($this->profile(BenchmarkAxis::Write)->mutates()); + $this->assertTrue($this->profile(BenchmarkAxis::Batch)->mutates()); + } + + /** + * 화면 축의 변경 판정은 선언된 HTTP 메서드를 따릅니다. + * + * @effects screen_mutation_verdict_follows_http_method + */ + #[Test] + public function 화면_축은_http_메서드로_변경을_판정한다(): void + { + $this->assertFalse($this->profile(BenchmarkAxis::Screen, ['method' => 'GET'])->mutates()); + // 대소문자와 무관하게 판정한다 + $this->assertFalse($this->profile(BenchmarkAxis::Screen, ['method' => 'get'])->mutates()); + $this->assertTrue($this->profile(BenchmarkAxis::Screen, ['method' => 'POST'])->mutates()); + $this->assertTrue($this->profile(BenchmarkAxis::Screen, ['method' => 'DELETE'])->mutates()); + } + + /** + * 명시 선언이 축 기본값과 메서드 판정을 모두 덮어씁니다. + * + * 읽기 전용 배치(리포트 산출 등)를 `--allow-write` 없이 재려면 이 경로가 필요합니다. + * + * @effects explicit_mutating_declaration_overrides_axis_default + */ + #[Test] + public function 명시_mutating_선언이_기본값을_덮는다(): void + { + $this->assertFalse($this->profile(BenchmarkAxis::Batch, ['mutating' => false])->mutates()); + $this->assertTrue($this->profile(BenchmarkAxis::ListQuery, ['mutating' => true])->mutates()); + $this->assertFalse( + $this->profile(BenchmarkAxis::Screen, ['method' => 'POST', 'mutating' => false])->mutates() + ); + } + + /** + * 직렬화 결과가 리포트/JSON 이 기대하는 키를 담습니다. + */ + #[Test] + public function 직렬화가_약속된_키를_담는다(): void + { + $payload = $this->profile(BenchmarkAxis::ListQuery, ['table' => 'users'])->toArray(); + + foreach (['key', 'qualified_key', 'axis', 'source', 'label', 'options'] as $key) { + $this->assertArrayHasKey($key, $payload); + } + + $this->assertSame('list', $payload['axis']); + $this->assertSame(['kind' => 'core', 'identifier' => 'core'], $payload['source']); + $this->assertSame(['table' => 'users'], $payload['options']); + } + + /** + * 축마다 필수 옵션이 대안 그룹으로 선언됩니다. + * + * 화면 축이 라우트명 또는 URI 중 하나만 있으면 되는 성질이 이 구조에 담깁니다. + * + * @effects screen_axis_accepts_route_or_uri_alternative + */ + #[Test] + public function 축별_필수_옵션은_대안_그룹으로_선언된다(): void + { + $this->assertSame([['table']], BenchmarkAxis::ListQuery->requiredOptions()); + $this->assertSame([['route', 'uri']], BenchmarkAxis::Screen->requiredOptions()); + $this->assertSame([['callback']], BenchmarkAxis::Write->requiredOptions()); + $this->assertSame([['command']], BenchmarkAxis::Batch->requiredOptions()); + } + + /** + * 축 값 목록과 라벨이 정의됩니다. + */ + #[Test] + public function 축_값과_라벨이_정의된다(): void + { + $this->assertSame(['list', 'screen', 'write', 'batch'], BenchmarkAxis::values()); + + foreach (BenchmarkAxis::cases() as $axis) { + $this->assertNotSame('', $axis->label()); + } + } +} diff --git a/tests/scenarios/benchmark-infra.yaml b/tests/scenarios/benchmark-infra.yaml new file mode 100644 index 00000000..4e95643f --- /dev/null +++ b/tests/scenarios/benchmark-infra.yaml @@ -0,0 +1,132 @@ +# audit:allow test-scenario-coverage reason: cross product 면제 전용. 10축 전개 61,200 조합은 docblock @scenario 1:1 매핑이 비현실적이라 면제하고, 회귀 가드는 test_files 4종(커맨드 표면 + 축 실행기 단위 + 프로파일 VO/Enum 계약 + 확장 선언 통합)으로 커버한다. effects 54건은 전수 @effects 마킹돼 있어 이 면제 줄을 지워도 effects 검사는 통과한다 — 면제가 숨기는 항목 없음. + +feature: benchmark_infra + +description: | + 성능 계측 인프라를 4축(목록 조회 / 화면 응답 / 쓰기 작업 / 배치 작업)으로 확장하고, 계측 + 대상 선언을 커맨드 밖으로 빼낸다. 계측 대상을 커맨드에 하드코딩하면 확장이 설치·제거되는 + 설치본마다 "실제로 존재하는 대상"과 어긋나므로, 소유자가 선언하고 코어가 수집한다. + + 핵심 설계: + - 선언 지점 두 곳, 스키마 하나: 코어 `config/benchmark.php`, 확장 `getBenchmarkProfiles()`. + - 잘못된 선언은 조용히 버리지 않는다 — 사유를 경고로 남기고 그 선언만 제외한다. 버려진 + 선언은 곧 계측 사각이 된다. + - 짧은 키가 여러 확장에서 겹치면 후보를 제시하고 거부한다. 임의로 하나를 고르면 어느 + 확장의 대상을 잰 것인지 알 수 없다. + - 화면 축은 라우트를 HTTP 커널로 내부 요청 처리한다. 라우트만 dispatch 하면 전역 + 미들웨어(인증·권한·로케일)를 건너뛰어 화면과 다른 것을 잰다. + - 화면 축 인증은 프로파일이 선언한 권한만 가진 임시 계정이 기본이며 `--as` 로 기존 계정을 + 지목할 수 있다. 전권 계정으로 재면 권한 검사 비용과 분기가 실제와 달라진다. + - 화면/쓰기 축은 계측 계정 생성부터 처리까지 롤백되는 트랜잭션 안에서 실행한다. 트랜잭션이 + 열려 있으면 읽기 쿼리도 write PDO 로 가므로 읽기/쓰기 분리 환경에서도 인증이 성립한다. + - 쓰기 축은 `prepare`(계측 제외) / `callback`(계측) / `cleanup` 세 단계다. 선행 상태 준비 + 비용이 측정값에 섞이면 재려던 것을 재지 못한다. + - 콜백은 `'Fqcn'` 또는 `['Fqcn','method']` 만 허용한다 — 코어 선언이 config:cache 대상이라 + 클로저를 담을 수 없으므로 확장 선언도 형식을 통일한다. + - 배치 축은 트랜잭션으로 감싸지 않는다. 배치는 내부에서 커밋/DDL 을 실행할 수 있고, 대량 + 처리를 긴 트랜잭션에 담으면 락 보유가 계측보다 위험하다. + - 데이터를 변경하는 축은 `--allow-write` 없이 거부한다. 화면 축의 변경 판정은 선언된 HTTP + 메서드를 따르며 `mutating` 선언이 그것을 덮어쓴다. + - 측정하지 못한 프로파일은 목록에서 빠지지 않고 사유와 함께 남으며, 전부 건너뛴 실행은 + 실패로 끝난다. 성공으로 보고하면 "측정했다"로 읽힌다. + - 리포트에는 환경 정보(DB/PHP 버전·OPcache·config:cache·실행 머신)를 반드시 함께 적는다. + 환경이 빠진 수치는 다른 리포트와 비교할 수 없다. 문서로 옮길 수치는 이 경로로만 산출한다. + +axes: + source: [core_config, module_declaration, plugin_declaration] + axis_type: [list, screen, write, batch] + selection: [profile_key, qualified_key, axis_filter, all, none] + declaration_validity: [valid, missing_type, unknown_type, missing_required_option, closure_callback] + key_collision: [unique, ambiguous_short_key] + target_presence: [present, table_missing, route_missing, command_missing] + write_permission: [allow_write_given, allow_write_omitted] + mutation_declaration: [axis_default, declared_mutating, declared_readonly] + environment: [local, production] + output: [table, json, report_default_path, report_given_path] + +exclusions: + - { axis_type: list, write_permission: allow_write_omitted, reason: "목록 축은 읽기 전용이라 --allow-write 판정 대상이 아니다 (시딩은 environment 축이 담당)" } + - { axis_type: batch, target_presence: table_missing, reason: "배치 축은 테이블을 직접 지목하지 않는다 — 부재 판정은 command_missing" } + - { axis_type: list, target_presence: route_missing, reason: "목록 축은 라우트를 쓰지 않는다" } + - { declaration_validity: closure_callback, axis_type: list, reason: "콜백 선언은 write 축만 갖는다" } + - { environment: production, axis_type: screen, reason: "운영 거부 가드는 시딩/비움(목록 축)에만 걸린다 — 화면 축은 롤백 트랜잭션으로 운영에서도 안전" } + - { key_collision: ambiguous_short_key, source: core_config, reason: "충돌은 서로 다른 출처가 같은 키를 선언할 때만 발생 — 한 출처 안에서는 배열 키가 이미 유일" } + +effects: + # ── 선언 수집 (P0) ── + - core_config_profiles_are_collected + - module_declared_profiles_are_collected + - plugin_declared_profiles_are_collected + - profile_source_kind_and_identifier_are_recorded + - extension_declaration_failure_is_isolated_to_that_extension + # ── 선언 검증 (P0) ── + - missing_type_declaration_is_rejected_with_reason + - unknown_axis_type_is_rejected_with_allowed_values + - missing_required_option_is_rejected_naming_the_option + - screen_axis_accepts_route_or_uri_alternative + - closure_callback_is_rejected_with_config_cache_reason + - rejected_declarations_appear_in_warnings_not_silently_dropped + # ── 대상 지목 (P0) ── + - qualified_key_resolves_exactly + - unique_short_key_resolves + - ambiguous_short_key_lists_candidates_and_refuses + - unknown_key_fails_with_list_profiles_hint + - axis_filter_selects_only_that_axis + - no_selection_fails_asking_for_profile_or_axis_or_all + # ── 목록 축 (P0) ── + - list_axis_reports_all_list_and_id_only_columns + - list_axis_ratio_is_derived_from_id_only_baseline + - wildcard_columns_expand_to_actual_schema_columns + - nonexistent_declared_columns_are_filtered_out + - declared_filters_are_applied_to_measured_query + - operator_filters_are_applied_to_measured_query + - equality_filter_is_not_misread_as_operator_form + - unknown_filter_operator_is_refused_before_measuring + - list_profile_filters_match_the_screen_default_predicate + - soft_delete_declaration_adds_deleted_at_predicate + - explain_option_collects_plans_for_both_column_widths + # ── 화면 축 (P0) ── + - screen_axis_dispatches_through_http_kernel_with_middleware + - screen_axis_reports_status_response_time_and_query_count + - repeated_identical_sql_is_reported_as_n_plus_one_candidate + - absence_of_n_plus_one_is_stated_explicitly + - ephemeral_admin_receives_only_declared_permissions + - as_option_measures_with_the_named_existing_account + - container_request_and_auth_guards_are_restored_after_each_dispatch + - route_name_declaration_resolves_uri_without_prefix_assembly + - missing_route_name_fails_instead_of_measuring_a_404 + # ── 쓰기 축 (P0) ── + - prepare_runs_outside_the_measured_window + - prepare_runs_once_per_iteration + - callback_receives_prepare_result + - write_axis_reports_first_run_and_median_separately + - cleanup_failure_does_not_swallow_the_measurement + # ── 배치 축 (P1) ── + - batch_axis_reports_elapsed_time_and_peak_memory + - peak_memory_before_and_after_are_both_reported + - batch_axis_reports_nonzero_exit_code_as_partial_measurement + - unregistered_command_is_skipped_with_reason + # ── 안전장치 (P0) ── + - mutating_axes_refuse_without_allow_write + - screen_mutation_verdict_follows_http_method + - explicit_mutating_declaration_overrides_axis_default + - seeding_and_truncation_are_refused_in_production + - measurement_leaves_no_residual_rows_after_rollback + - database_override_option_repoints_the_connection + # ── 출력 (P0) ── + - skipped_profiles_remain_in_output_with_reason + - all_skipped_run_exits_nonzero + - json_output_carries_environment_options_warnings_results + - report_includes_environment_and_run_conditions + - report_defaults_to_untracked_storage_path + - report_writes_to_given_path + +test_files: + # 코어 — 선언 수집 / 검증 / 대상 지목 / 축 분기 / 가드 / 출력 스키마 + - tests/Feature/Console/BenchCommandTest.php + # 코어 — 축 실행기 단위 (화면 내부 요청·쿼리 수집·쓰기 prepare 분리·배치 메모리) + - tests/Unit/Benchmark/BenchmarkAxisRunnerTest.php + # 코어 — 프로파일 VO / 축 Enum 계약 + - tests/Unit/Benchmark/BenchmarkProfileTest.php + # 이커머스 — 확장 선언 + 주문 생성 쓰기 축 대상 + - modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/OrderCreationBenchmarkTest.php From b10e89bef01b9b097f71c713e728c95396464591 Mon Sep 17 00:00:00 2001 From: HeuJung Date: Sat, 1 Aug 2026 23:06:16 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix(core,board,ecommerce,page,gdpr,ckeditor?= =?UTF-8?q?5,kginicis,admin=5Fbasic,basic):=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=C2=B7=EA=B2=80=EC=A6=9D=20=EA=B3=84=EC=B8=B5?= =?UTF-8?q?=20=EC=A0=95=EB=B9=84=20=EB=B0=8F=20=EA=B0=90=EC=82=AC=20?= =?UTF-8?q?=EC=A7=80=EC=A0=81=20=EC=A0=84=EA=B1=B4=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 목록 조회 성능 축(깊은 OFFSET·정렬·색인)과 그 검증 계층에서 계획서 전수검수와 3회에 걸친 감사가 지적한 항목을 처리했다. 세부 경위는 의 각 회차 문서에 있다. 검증 계층 — 컨트롤러가 base Request 를 직접 주입받던 확장 13곳을 전용 FormRequest 로 옮겼다. 옮기면서 기존 동작 계약은 그대로 두었다: 상한 초과 limit 을 거부하지 않고 상한까지 반환하던 공개 API, per_page 를 범위로 조정하던 관리자 목록, 미지원 period 를 year 로 해석하던 인기글 목록 모두 종전과 같은 응답을 낸다. 상한/폴백을 rules 로 승격시키면 200 이던 응답이 422 가 되어 기존 링크가 깨지므로, 규칙은 타입만 닫고 클램프·폴백은 접근자가 맡는다. period 는 접근자가 닫힌 집합만 반환해 캐시 키 공간도 함께 닫힌다. ckeditor5 이미지 업로드만 ResponseHelper 봉투를 쓰지 않는다. 응답을 파싱하는 주체가 CDN 으로 로드되는 상위 CKEditor5 43.3.1 의 SimpleUploadAdapter 라 규약을 바꿀 수 없어, 각 응답 지점에 사유를 명시한 면제를 부착하고 근거를 API 문서에 남겼다. 하네스 — 룰 5개의 대상 경로 패턴이 매처와 맞지 않아 번들 확장 컨트롤러가 검사 대상에서 통째로 빠져 있었다. 패턴을 고치자 확장 위반 17건이 드러나 전건 처리했다. severity 오타가 요약 집계 양쪽에 안 잡혀 "0 error" 로 보고되던 문제도 런너 사전 검증으로 막았다. 정렬 게이트 대조 하네스는 관계 정렬 변형만 쓰는 저장소를 탐지하지 못한 채 통과시키고 있었다. 탐지·제외·인자 파싱을 함께 고치고 단위 테스트를 신설했다. --- AGENTS.md | 30 +- CHANGELOG.md | 25 +- app/Benchmark/Axes/ListAxisRunner.php | 11 +- app/Benchmark/ListIndexAdvisor.php | 203 +++++++ app/Benchmark/SyntheticSeeder.php | 48 +- .../Concerns/BundledExtensionUpdatePrompt.php | 69 +++ .../Commands/Core/CoreUpdateCommand.php | 3 +- .../Core/ExecuteBundledUpdatesCommand.php | 22 +- .../Module/CheckModuleUpdatesCommand.php | 4 +- .../Commands/Module/InstallModuleCommand.php | 8 +- .../Commands/Module/UpdateModuleCommand.php | 8 +- .../Plugin/CheckPluginUpdatesCommand.php | 4 +- .../Commands/Plugin/InstallPluginCommand.php | 8 +- .../Commands/Plugin/UpdatePluginCommand.php | 8 +- .../Commands/Search/SearchIndexCommand.php | 194 +++++++ .../Template/CheckTemplateUpdatesCommand.php | 4 +- .../Commands/Traits/RebuildsSearchIndex.php | 114 ++++ app/Enums/SearchIndexStatus.php | 59 ++ app/Extension/ModuleManager.php | 4 + app/Extension/PluginManager.php | 4 + app/Extension/TemplateManager.php | 4 + app/Helpers/ResponseHelper.php | 18 +- app/Helpers/TimezoneHelper.php | 27 + .../Controllers/Api/Admin/MenuController.php | 16 +- .../Api/Admin/ModuleController.php | 19 +- .../Api/Admin/PluginController.php | 22 +- .../Concerns/RebuildsSearchIndexOnDemand.php | 46 ++ app/Http/Middleware/StartApiSession.php | 34 +- .../ActivityLog/ActivityLogIndexRequest.php | 6 +- .../Module/PerformModuleUpdateRequest.php | 5 +- .../NotificationLogIndexRequest.php | 4 +- .../Plugin/PerformPluginUpdateRequest.php | 5 +- app/Providers/LanguagePackServiceProvider.php | 37 +- .../Concerns/PaginatesWithDeferredJoin.php | 27 +- .../Concerns/SortsByRelatedColumn.php | 111 ++++ .../IdentityVerificationLogRepository.php | 8 +- app/Repositories/LanguagePackRepository.php | 4 +- app/Repositories/MenuRepository.php | 76 ++- .../NotificationLogRepository.php | 1 + app/Repositories/NotificationRepository.php | 4 +- app/Repositories/RoleRepository.php | 6 +- .../ScheduleHistoryRepository.php | 16 +- .../Contracts/SearchIndexMaintainer.php | 78 +++ app/Search/DTO/FulltextIndexDefinition.php | 65 +++ app/Search/DTO/SearchIndexHealth.php | 67 +++ app/Search/DTO/SearchIndexRepairReport.php | 119 ++++ .../Maintenance/FulltextIndexMaintainer.php | 127 +++++ app/Search/FulltextIndexInspector.php | 367 +++++++++++++ app/Search/SearchIndexMaintenanceManager.php | 232 ++++++++ app/Services/LanguagePackService.php | 9 +- ...ort_indexes_to_notification_logs_table.php | 66 +++ ...0002_add_tiebreak_to_core_list_indexes.php | 86 +++ docs/backend/api/README.md | 4 +- docs/backend/api/modules.md | 6 +- docs/backend/api/notification-logs.md | 2 +- docs/backend/api/plugins.md | 6 +- docs/backend/search-system.md | 123 ++++- docs/backend/service-repository.md | 204 ++++++- docs/backend/validation.md | 61 +++ docs/cheatsheet.md | 25 + docs/extension/layout-extensions.md | 6 +- docs/extension/module-i18n.md | 15 +- docs/extension/plugin-development.md | 18 +- lang-packs/_bundled/g7-core-ja/CHANGELOG.md | 12 + .../_bundled/g7-core-ja/backend/ja/search.php | 42 ++ .../g7-core-ja/frontend/partial/errors.json | 3 +- .../_bundled/g7-core-ja/language-pack.json | 2 +- .../CHANGELOG.md | 10 + .../backend/ja/messages.php | 15 +- .../language-pack.json | 2 +- .../g7-plugin-sirsoft-gdpr-ja/CHANGELOG.md | 4 +- .../backend/ja/messages.php | 5 + .../frontend/ja.json | 9 +- .../CHANGELOG.md | 6 + .../frontend/partial/extensions.json | 5 +- .../language-pack.json | 2 +- lang/en/search.php | 52 +- lang/ko/search.php | 52 +- lang/partial/en/errors.json | 3 +- lang/partial/ko/errors.json | 3 +- .../gnuboard7-hello_module/CHANGELOG.md | 8 + .../gnuboard7-hello_module/composer.json | 2 +- .../gnuboard7-hello_module/docs/api/README.md | 4 +- .../gnuboard7-hello_module/docs/api/memos.md | 313 ++++++++++- .../gnuboard7-hello_module/module.json | 2 +- .../layouts/admin/admin_memo_list.json | 6 +- .../layouts/user/user_memo_list.json | 6 +- .../Http/Controllers/Admin/MemoController.php | 15 +- .../Http/Controllers/Api/MemoController.php | 11 +- .../src/Http/Requests/MemoListRequest.php | 40 ++ .../src/Repositories/MemoRepository.php | 2 + .../gnuboard7-hello_module/src/routes/api.php | 44 +- .../gnuboard7-hello_module/src/routes/web.php | 45 +- modules/_bundled/sirsoft-board/CHANGELOG.md | 7 + ...add_tiebreak_to_board_posts_list_index.php | 87 +++ ...ist_sort_index_to_boards_reports_table.php | 61 +++ .../docs/api/board-activities.md | 2 +- modules/_bundled/sirsoft-board/module.php | 7 +- .../resources/lang/en/activity_log.php | 55 -- .../resources/lang/ko/activity_log.php | 55 -- .../admin/admin_board_reports_index.json | 7 +- .../Controllers/Admin/ReportController.php | 22 +- .../Http/Controllers/User/BoardController.php | 44 +- .../Controllers/User/ReportController.php | 14 +- .../User/UserActivityController.php | 8 +- .../Http/Requests/Admin/ReportersRequest.php | 3 +- .../Http/Requests/User/IndexBoardRequest.php | 50 ++ .../Requests/User/PopularBoardsRequest.php | 50 ++ .../Requests/User/PopularPostsRequest.php | 87 +++ .../Http/Requests/User/RecentPostsRequest.php | 51 ++ .../src/Repositories/BoardRepository.php | 3 +- .../src/Repositories/ReportRepository.php | 2 + .../src/lang/en/activity_log.php | 1 + .../src/lang/ko/activity_log.php | 1 + .../Admin/ReportRequestValidationTest.php | 28 + .../Feature/User/BoardPopularApiTest.php | 36 +- .../Feature/User/UserActivityApiTest.php | 40 +- .../Unit/Benchmark/ListIndexCoverageTest.php | 30 + .../Repositories/PostListDeferredJoinTest.php | 96 ++++ .../04_AddTiebreakToPostListIndex.php | 90 +++ .../migrations/05_AddReportListSortIndex.php | 70 +++ .../_bundled/sirsoft-ecommerce/CHANGELOG.md | 13 + ...dex_to_ecommerce_order_shippings_table.php | 54 ++ ..._list_sort_indexes_to_ecommerce_tables.php | 103 ++++ .../seeders/ActivityLogSampleSeeder.php | 27 +- .../database/seeders/Sample/OrderSeeder.php | 8 + .../sirsoft-ecommerce/docs/api/inquiries.md | 11 +- .../sirsoft-ecommerce/docs/api/orders.md | 2 +- .../docs/api/promotion-coupons.md | 2 +- .../sirsoft-ecommerce/docs/api/wishlist.md | 10 +- .../resources/lang/en/activity_log.php | 159 ------ .../resources/lang/en/enums.php | 19 - .../resources/lang/en/messages.php | 87 --- .../resources/lang/ko/activity_log.php | 159 ------ .../resources/lang/ko/enums.php | 19 - .../resources/lang/ko/messages.php | 87 --- .../_transactions_table.json | 4 +- .../_modal_issue_history.json | 4 +- .../Admin/ProductCommonInfoController.php | 34 +- .../Admin/ShippingCarrierController.php | 28 +- .../Public/ProductInquiryController.php | 20 +- .../Public/PublicCouponController.php | 8 +- .../Controllers/Public/WishlistController.php | 12 +- .../User/ProductInquiryController.php | 53 +- .../Admin/ActiveShippingCarrierRequest.php | 45 ++ .../Http/Requests/Admin/CouponListRequest.php | 6 +- .../Admin/IndexProductCommonInfoRequest.php | 107 ++++ .../Admin/IndexShippingCarrierRequest.php | 69 +++ .../Http/Requests/Admin/OrderListRequest.php | 12 +- .../Public/DownloadableCouponsRequest.php | 34 ++ .../Public/ProductInquiryListRequest.php | 64 +++ .../Requests/User/UserInquiryListRequest.php | 65 +++ .../Requests/User/WishlistListRequest.php | 37 ++ .../Http/Resources/CouponIssueResource.php | 14 +- .../Repositories/CouponIssueRepository.php | 5 +- .../src/Repositories/CouponRepository.php | 15 +- .../MileageTransactionRepository.php | 74 ++- .../src/Repositories/OrderRepository.php | 51 +- .../Repositories/ProductInquiryRepository.php | 8 +- .../src/Repositories/ProductRepository.php | 12 +- .../Repositories/ProductReviewRepository.php | 7 +- .../ProductWishlistRepository.php | 5 + .../src/Services/UserMileageService.php | 8 + .../src/lang/en/messages.php | 2 + .../src/lang/ko/messages.php | 2 + .../Feature/Admin/OrderShippedAtSortTest.php | 163 ++++++ .../Controllers/ListPaginationGuardTest.php | 143 +++++ ...serAddressUpdatePresenceValidationTest.php | 29 + .../ActivityLogDescriptionParamsTest.php | 148 +++++ .../Feature/Seeders/SeederConsistencyTest.php | 54 ++ .../Services/ListDisplayIntegrityTest.php | 136 +++++ .../Unit/Benchmark/ListIndexCoverageTest.php | 27 + .../LayoutSortOptionGateParityTest.php | 35 ++ .../NumericRangeFilterZeroBoundaryTest.php | 79 +++ .../SortWhitelistGateParityTest.php | 142 +++++ .../upgrades/Upgrade_1_0_5.php | 5 + ...BackfillMileageConsumptionDescriptions.php | 79 +++ .../04_AddOrderShippingSortIndex.php | 73 +++ .../migrations/05_AddListSortIndexes.php | 89 +++ modules/_bundled/sirsoft-page/CHANGELOG.md | 3 + modules/_bundled/sirsoft-page/module.php | 3 + .../resources/lang/en/activity_log.php | 20 - .../resources/lang/ko/activity_log.php | 20 - .../layouts/admin/admin_page_list.json | 7 +- .../src/Repositories/PageRepository.php | 36 +- .../Repositories/PageRepositorySearchTest.php | 44 +- .../Unit/Benchmark/ListIndexCoverageTest.php | 27 + .../sirsoft-ckeditor5/docs/api/upload.md | 1 + .../Controllers/ImageUploadController.php | 10 +- plugins/_bundled/sirsoft-gdpr/CHANGELOG.md | 6 + .../sirsoft-gdpr/docs/api/consent-log.md | 4 +- .../sirsoft-gdpr/lang/en/messages.php | 5 + .../sirsoft-gdpr/lang/ko/messages.php | 5 + .../sirsoft-gdpr/resources/lang/en.json | 15 +- .../sirsoft-gdpr/resources/lang/ko.json | 23 +- .../layouts/admin/gdpr_consent_log.json | 92 ++++ .../layouts/admin/plugin_settings.json | 108 +++- .../sirsoft-gdpr/src/Enums/ConsentSource.php | 66 +++ .../Admin/GdprAdminConsentLogController.php | 42 +- .../GdprAdminPolicyVersionController.php | 18 +- .../Public/GdprCookieConsentController.php | 19 +- .../User/GdprConsentController.php | 12 +- .../Requests/CookieConsentStatusRequest.php | 34 ++ .../Http/Requests/IndexConsentLogRequest.php | 137 +++++ .../Requests/IndexPolicyVersionRequest.php | 53 ++ .../Requests/StoreCookieConsentRequest.php | 14 +- .../src/Listeners/GdprAuthConsentListener.php | 21 +- .../GdprPolicyVersionRepository.php | 4 + .../src/Services/GdprConsentService.php | 89 +-- .../ConsentSourceVocabularyParityTest.php | 182 +++++++ .../CHANGELOG.md | 3 +- .../docs/api/identity.md | 43 ++ .../Controllers/InicisCallbackController.php | 11 +- .../InicisPopupBridgeController.php | 15 +- .../Http/Requests/InicisCallbackRequest.php | 39 ++ .../Requests/InicisPopupBridgeRequest.php | 53 ++ public/build/core/template-engine.min.js | 58 +- resources/js/core/TemplateApp.ts | 5 +- .../core/template-engine/ActionDispatcher.ts | 54 +- .../js/core/template-engine/CHANGELOG.md | 21 + .../core/template-engine/DynamicRenderer.tsx | 30 +- ...namicRenderer.localInitGlobalSync.test.tsx | 173 ++++++ ...ionDispatcherNetworkFailureMessage.test.ts | 69 +++ .../troubleshooting-state-global.test.ts | 49 ++ resources/views/dev-dashboard.blade.php | 19 +- .../_bundled/sirsoft-admin_basic/CHANGELOG.md | 5 + ...extension-list-pagination-binding.test.tsx | 44 ++ ...in-extension-update-rebuild-optin.test.tsx | 75 +++ .../dist/css/components.css | 2 +- .../dist/js/components.iife.js | 122 ++--- .../lang/partial/en/extensions.json | 5 +- .../lang/partial/ko/extensions.json | 5 +- .../layouts/admin_module_list.json | 3 +- .../layouts/admin_plugin_list.json | 3 +- .../layouts/admin_user_list.json | 9 +- .../admin_module_list/_modal_update.json | 75 ++- .../admin_plugin_list/_modal_update.json | 75 ++- .../admin_schedule_list/_tab_schedules.json | 2 +- .../src/components/composite/DataGrid.tsx | 54 +- .../composite/__tests__/DataGrid.test.tsx | 235 ++++++++ .../sirsoft-admin_basic/src/styles/main.css | 6 + templates/_bundled/sirsoft-basic/CHANGELOG.md | 3 + ...ypage-notifications-unread-binding.test.ts | 83 +++ .../partials/mypage/notifications/_list.json | 2 +- .../partials/shop/_modal_address_manage.json | 19 +- .../partials/shop/detail/_info_summary.json | 37 +- .../sirsoft-basic/layouts/shop/reorder.json | 8 +- .../Api/Admin/ScheduleControllerTest.php | 46 ++ .../CheckUpdatesCommandReportingTest.php | 61 +++ .../IdentityLogDateRangeFilterTest.php | 131 +++++ .../ModuleLangPathAlignmentTest.php | 92 ++++ .../Menu/MenuListSortAndSearchTest.php | 183 +++++++ .../Search/FulltextIndexMaintainerTest.php | 206 +++++++ .../SearchIndexNoticeReachesClientTest.php | 131 +++++ .../Search/SearchIndexRebuildOptInTest.php | 150 +++++ ...xtension-update-search-index-optin.spec.ts | 154 ++++++ .../checkout-address-overseas-render.spec.ts | 55 ++ .../specs/list-render-integrity.spec.ts | 103 ++++ .../specs/pagination/deep-offset-list.spec.ts | 75 ++- .../pagination/list-reachability.spec.ts | 169 ++++++ .../list-sort-and-page-reset.spec.ts | 283 ++++++++++ .../AssertsLayoutSortOptionGateParity.php | 511 ++++++++++++++++++ .../Concerns/AssertsListIndexCoverage.php | 136 +++++ .../Unit/Benchmark/ListIndexCoverageTest.php | 87 +++ tests/Unit/Benchmark/SyntheticSeederTest.php | 75 +++ .../LayoutSortOptionGateParityTest.php | 40 ++ .../DeferredJoinCallSiteContractTest.php | 82 ++- .../SortableColumnsGateContractTest.php | 71 ++- .../Concerns/SortsByRelatedColumnTest.php | 240 ++++++++ .../PaginationKeyTiebreakTest.php | 175 ++++++ .../SortWhitelistGateParityTest.php | 172 ++++++ .../SearchIndexMaintenanceManagerTest.php | 205 +++++++ tests/scenarios/deep-offset-pagination.yaml | 1 + upgrades/Upgrade_7_0_6.php | 12 + .../04_AddNotificationLogSortIndexes.php | 79 +++ .../05_AddTiebreakToCoreListIndexes.php | 87 +++ 276 files changed, 12519 insertions(+), 1438 deletions(-) create mode 100644 app/Benchmark/ListIndexAdvisor.php create mode 100644 app/Console/Commands/Search/SearchIndexCommand.php create mode 100644 app/Console/Commands/Traits/RebuildsSearchIndex.php create mode 100644 app/Enums/SearchIndexStatus.php create mode 100644 app/Http/Controllers/Concerns/RebuildsSearchIndexOnDemand.php create mode 100644 app/Repositories/Concerns/SortsByRelatedColumn.php create mode 100644 app/Search/Contracts/SearchIndexMaintainer.php create mode 100644 app/Search/DTO/FulltextIndexDefinition.php create mode 100644 app/Search/DTO/SearchIndexHealth.php create mode 100644 app/Search/DTO/SearchIndexRepairReport.php create mode 100644 app/Search/Engines/Maintenance/FulltextIndexMaintainer.php create mode 100644 app/Search/FulltextIndexInspector.php create mode 100644 app/Search/SearchIndexMaintenanceManager.php create mode 100644 database/migrations/2026_07_31_000001_add_sort_indexes_to_notification_logs_table.php create mode 100644 database/migrations/2026_08_01_000002_add_tiebreak_to_core_list_indexes.php create mode 100644 modules/_bundled/gnuboard7-hello_module/src/Http/Requests/MemoListRequest.php create mode 100644 modules/_bundled/sirsoft-board/database/migrations/2026_08_01_000001_add_tiebreak_to_board_posts_list_index.php create mode 100644 modules/_bundled/sirsoft-board/database/migrations/2026_08_01_000002_add_list_sort_index_to_boards_reports_table.php delete mode 100644 modules/_bundled/sirsoft-board/resources/lang/en/activity_log.php delete mode 100644 modules/_bundled/sirsoft-board/resources/lang/ko/activity_log.php create mode 100644 modules/_bundled/sirsoft-board/src/Http/Requests/User/IndexBoardRequest.php create mode 100644 modules/_bundled/sirsoft-board/src/Http/Requests/User/PopularBoardsRequest.php create mode 100644 modules/_bundled/sirsoft-board/src/Http/Requests/User/PopularPostsRequest.php create mode 100644 modules/_bundled/sirsoft-board/src/Http/Requests/User/RecentPostsRequest.php create mode 100644 modules/_bundled/sirsoft-board/tests/Unit/Benchmark/ListIndexCoverageTest.php create mode 100644 modules/_bundled/sirsoft-board/upgrades/data/1.0.3/migrations/04_AddTiebreakToPostListIndex.php create mode 100644 modules/_bundled/sirsoft-board/upgrades/data/1.0.3/migrations/05_AddReportListSortIndex.php create mode 100644 modules/_bundled/sirsoft-ecommerce/database/migrations/2026_07_31_000001_add_shipped_at_index_to_ecommerce_order_shippings_table.php create mode 100644 modules/_bundled/sirsoft-ecommerce/database/migrations/2026_08_01_000001_add_list_sort_indexes_to_ecommerce_tables.php delete mode 100644 modules/_bundled/sirsoft-ecommerce/resources/lang/en/activity_log.php delete mode 100644 modules/_bundled/sirsoft-ecommerce/resources/lang/en/enums.php delete mode 100644 modules/_bundled/sirsoft-ecommerce/resources/lang/en/messages.php delete mode 100644 modules/_bundled/sirsoft-ecommerce/resources/lang/ko/activity_log.php delete mode 100644 modules/_bundled/sirsoft-ecommerce/resources/lang/ko/enums.php delete mode 100644 modules/_bundled/sirsoft-ecommerce/resources/lang/ko/messages.php create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ActiveShippingCarrierRequest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/IndexProductCommonInfoRequest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/IndexShippingCarrierRequest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Public/DownloadableCouponsRequest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Public/ProductInquiryListRequest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UserInquiryListRequest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/WishlistListRequest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Admin/OrderShippedAtSortTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/ListPaginationGuardTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Seeders/ActivityLogDescriptionParamsTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Feature/Services/ListDisplayIntegrityTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/ListIndexCoverageTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Unit/Layouts/LayoutSortOptionGateParityTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Unit/Repositories/NumericRangeFilterZeroBoundaryTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/tests/Unit/Repositories/SortWhitelistGateParityTest.php create mode 100644 modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/03_BackfillMileageConsumptionDescriptions.php create mode 100644 modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/04_AddOrderShippingSortIndex.php create mode 100644 modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/05_AddListSortIndexes.php delete mode 100644 modules/_bundled/sirsoft-page/resources/lang/en/activity_log.php delete mode 100644 modules/_bundled/sirsoft-page/resources/lang/ko/activity_log.php create mode 100644 modules/_bundled/sirsoft-page/tests/Unit/Benchmark/ListIndexCoverageTest.php create mode 100644 plugins/_bundled/sirsoft-gdpr/src/Enums/ConsentSource.php create mode 100644 plugins/_bundled/sirsoft-gdpr/src/Http/Requests/CookieConsentStatusRequest.php create mode 100644 plugins/_bundled/sirsoft-gdpr/src/Http/Requests/IndexConsentLogRequest.php create mode 100644 plugins/_bundled/sirsoft-gdpr/src/Http/Requests/IndexPolicyVersionRequest.php create mode 100644 plugins/_bundled/sirsoft-gdpr/tests/Unit/ConsentSourceVocabularyParityTest.php create mode 100644 plugins/_bundled/sirsoft-verification_kginicis/src/Http/Requests/InicisCallbackRequest.php create mode 100644 plugins/_bundled/sirsoft-verification_kginicis/src/Http/Requests/InicisPopupBridgeRequest.php create mode 100644 resources/js/core/template-engine/__tests__/DynamicRenderer.localInitGlobalSync.test.tsx create mode 100644 resources/js/core/template-engine/__tests__/actionDispatcherNetworkFailureMessage.test.ts create mode 100644 templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-list-pagination-binding.test.tsx create mode 100644 templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-update-rebuild-optin.test.tsx create mode 100644 templates/_bundled/sirsoft-basic/__tests__/layouts/mypage-notifications-unread-binding.test.ts create mode 100644 tests/Feature/Extension/CheckUpdatesCommandReportingTest.php create mode 100644 tests/Feature/Identity/IdentityLogDateRangeFilterTest.php create mode 100644 tests/Feature/LanguagePack/ModuleLangPathAlignmentTest.php create mode 100644 tests/Feature/Menu/MenuListSortAndSearchTest.php create mode 100644 tests/Feature/Search/FulltextIndexMaintainerTest.php create mode 100644 tests/Feature/Search/SearchIndexNoticeReachesClientTest.php create mode 100644 tests/Feature/Search/SearchIndexRebuildOptInTest.php create mode 100644 tests/Playwright/specs/admin/extension-update-search-index-optin.spec.ts create mode 100644 tests/Playwright/specs/checkout-address-overseas-render.spec.ts create mode 100644 tests/Playwright/specs/list-render-integrity.spec.ts create mode 100644 tests/Playwright/specs/pagination/list-reachability.spec.ts create mode 100644 tests/Playwright/specs/pagination/list-sort-and-page-reset.spec.ts create mode 100644 tests/Support/Concerns/AssertsLayoutSortOptionGateParity.php create mode 100644 tests/Support/Concerns/AssertsListIndexCoverage.php create mode 100644 tests/Unit/Benchmark/ListIndexCoverageTest.php create mode 100644 tests/Unit/Benchmark/SyntheticSeederTest.php create mode 100644 tests/Unit/Layouts/LayoutSortOptionGateParityTest.php create mode 100644 tests/Unit/Repositories/Concerns/SortsByRelatedColumnTest.php create mode 100644 tests/Unit/Repositories/PaginationKeyTiebreakTest.php create mode 100644 tests/Unit/Repositories/SortWhitelistGateParityTest.php create mode 100644 tests/Unit/Search/SearchIndexMaintenanceManagerTest.php create mode 100644 upgrades/data/7.0.6/migrations/04_AddNotificationLogSortIndexes.php create mode 100644 upgrades/data/7.0.6/migrations/05_AddTiebreakToCoreListIndexes.php diff --git a/AGENTS.md b/AGENTS.md index 8947f535..e670f7c1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,7 +115,7 @@ | [module-assets.md](docs/extension/module-assets.md) | 모듈 프론트엔드 에셋 시스템 | module.json에 에셋 매니페스트 정의 (js, css, loading strategy) | | [module-basics.md](docs/extension/module-basics.md) | 모듈 개발 기초 | 디렉토리: vendor-module (예: sirsoft-ecommerce) | | [module-commands.md](docs/extension/module-commands.md) | 모듈 Artisan 커맨드 | 목록: php artisan module:list | -| [module-i18n.md](docs/extension/module-i18n.md) | 모듈 다국어 시스템 | 백엔드: /lang/{locale}/*.php → __('vendor-module::key') | +| [module-i18n.md](docs/extension/module-i18n.md) | 모듈 다국어 시스템 | 백엔드: /src/lang/{locale}/*.php → __('vendor-module::key') ... | | [module-identity-settings.md](docs/extension/module-identity-settings.md) | 모듈/플러그인 본인인증(IDV) 설정 통합 가이드 | 정책/목적/메시지: module.php::getIdentity{Policies,Purposes,Mess... | | [module-layouts.md](docs/extension/module-layouts.md) | 모듈 레이아웃 시스템 | 위치: modules/_bundled/vendor-module/resources/layouts/admi... | | [module-routing.md](docs/extension/module-routing.md) | 모듈 라우트 규칙 | URL prefix 자동: /api/modules/[vendor-module]/... | @@ -160,7 +160,7 @@ | 확장 | 유형 | API 문서 목차 | 문서/엔드포인트 | |------|------|--------------|----------------| -| `gnuboard7-hello_module` | 모듈 | [docs/api/](modules/_bundled/gnuboard7-hello_module/docs/api/README.md) | 1 / 2 | +| `gnuboard7-hello_module` | 모듈 | [docs/api/](modules/_bundled/gnuboard7-hello_module/docs/api/README.md) | 1 / 7 | | `sirsoft-board` | 모듈 | [docs/api/](modules/_bundled/sirsoft-board/docs/api/README.md) | 10 / 80 | | `sirsoft-ecommerce` | 모듈 | [docs/api/](modules/_bundled/sirsoft-ecommerce/docs/api/README.md) | 33 / 231 | | `sirsoft-page` | 모듈 | [docs/api/](modules/_bundled/sirsoft-page/docs/api/README.md) | 2 / 17 | @@ -363,12 +363,36 @@ Icon 은 `` 글리프라 박스 크기가 곧 `font-size` 다. `w-N h-N` 은 | `->paginate($perPage)` / `->paginate($perPage, ['*'])` (컬럼 목록 미지정) | 목록이 실제로 쓰는 컬럼만 명시. 깊은 OFFSET 이 가능한 목록은 `PaginatesWithDeferredJoin` | | 요청 값에서 온 정렬 컬럼을 그대로 `orderBy` 에 전달 | `ResolvesSortSpec` 으로 닫힌 집합 해석 (방향만 검사하는 `in_array` 는 보호가 아니다) | | 지연 조인의 `$query` 에 미리 `orderBy`/`with`/`select` 적용 | 필터/where 만 적용해 넘기고, 정렬·관계·컬럼은 trait 인자로 전달 | +| 쿼리에 `with()` 만 하고 `relations:` 인자 생략 | 관계는 `relations:` 로 전달 — trait 이 inner 뿐 아니라 **outer 에서도** eager load 를 지우므로 관계가 조용히 사라진다 (예외·쿼리 오류 없이 응답에서 필드만 없어져 관계를 단언하지 않는 테스트는 전부 통과) | | 목록 SELECT 에 `SUBSTRING(content, 1, N)` 을 두고 프루닝했다고 간주 | 오버플로 페이지 읽기가 그대로 발생 — 잘라내기는 outer(`$columns`)에서만 | | 그룹 쿼리(`groupBy`)의 총 건수를 `count()` 로 계산 | `getCountForPagination()` (서브쿼리로 감싸 그룹 수를 센다) | | raw SQL 안에 테이블명·별칭을 문자열로 조립 | 테이블명은 `(new Model)->getTable()`, 프리픽스는 `DB::getTablePrefix()`, 별칭은 빌더(`join($table.' as uc', …)`)가 만들게 | | `whereRaw('1 = 0')` / `where($c, DB::raw("({$sub->toSql()})"))` + `mergeBindings` | `whereIn($key, [])` / `where($c, '=', $sub)` (빌더가 바인딩까지 처리) | +| 화면 정렬 셀렉트에 게이트가 모르는 컬럼을 넣기 | `화면 옵션 ⊆ FormRequest 게이트 ⊆ Repository 화이트리스트` — 어긋나면 422 후 직전 목록이 남아 정렬된 것처럼 보인다 | +| 분류값 필터의 허용 어휘를 화면·게이트·기록 지점에 각각 리터럴로 적기 | 어휘는 Enum 단일 출처에서 파생 — `화면 필터 옵션 = 라벨 키 = 실제 기록 어휘`. 부분집합이 되면 빠진 값으로 기록된 행이 어떤 필터 조합으로도 도달 불가하고, 라벨 키가 없는 값은 목록 셀에 원시 키 문자열로 노출된다 | +| 목록 응답이 `last_page > 1` 인데 화면에 페이저·총건수가 없음 | 페이지 이동 컨트롤과 총건수를 함께 노출 — 없으면 1페이지 밖 행이 조용히 잘리고, 잘렸다는 사실조차 화면에 나타나지 않는다 | +| 쿼리 파라미터 불리언에 `boolean` 규칙만 부착 | `prepareForValidation()` 으로 `"true"`/`"false"` 정규화 (쿼리는 문자열로 도착 — 화면이 그 형태로 보내면 목록 전체가 422). 단, 해석 불가한 값은 건드리지 말 것 | +| 같은 화면의 개수 배지와 그 배지가 여는 목록이 서로 다른 엔드포인트 | 배지 계산과 목록 조회는 같은 스코프의 같은 데이터소스에서 | +| 관계 테이블 컬럼 정렬을 `join`+`groupBy` 로 구현 | `SortsByRelatedColumn` 의 상관 서브쿼리 (1:N 조인은 원 행을 부풀려 총 건수·페이지 경계를 깨고, INNER 는 자식 없는 행을 지운다) | +| 관계 정렬을 넣고 인덱스는 그대로 | `(외래키, 정렬컬럼)` 복합 인덱스 마이그레이션 동반 (서브쿼리가 행마다 실행된다) | +| 페이지네이션 정렬을 비고유 컬럼(`created_at` 등)으로만 끝내기 | 정렬 마지막에 기본키를 덧붙여 전순서 보장 (동률 구간에서 인접 페이지가 같은 행을 중복 노출하고 다른 행을 누락한다) | -> 상세: [service-repository.md "목록 조회 컬럼 프루닝과 지연 조인" / "정렬 컬럼 화이트리스트" / "허용되는 Raw 쿼리"](docs/backend/service-repository.md) +> 상세: [service-repository.md "목록 조회 컬럼 프루닝과 지연 조인" / "정렬 컬럼 화이트리스트" / "화면 정렬 옵션은 게이트의 부분집합이어야 한다" / "관계 테이블 컬럼 기준 정렬" / "허용되는 Raw 쿼리"](docs/backend/service-repository.md) + +### 검색 인덱스 재생성(리인덱싱) + +| ❌ 금지 | ✅ 올바른 사용 | +|--------|---------------| +| 재생성을 자동 트리거(마이그레이션 종료·확장 업데이트 완료 등)에 연결 | 운영자가 **명시적으로 선택**했을 때만 수행. 인덱스 잠금·전체 재색인 비용이 운영 중 사이트를 멈춘다 | +| 재생성 체크 상태를 전역에 남겨 다음 모달 진입에 이월 | 모달 진입 시드와 제출 후 초기화 **양쪽**에서 해제. 이월되면 운영자가 아무것도 누르지 않았는데 재생성이 수행되고, 서버 옵인 가드는 정상이라 HTTP 테스트로는 드러나지 않는다 | +| 모듈·플러그인 모달이 같은 전역 키 공유 | 면마다 별도 키 (한쪽 체크가 다른 쪽으로 전이 금지) | +| 응답 헬퍼가 `JsonResource::resolve()` 만 호출해 `additional()` 유실 | 부가 데이터를 응답 최상위에 병합 — 색인 누락은 오류 없이 "검색 0건" 으로만 나타나므로 응답 페이로드가 유일한 통로다 | +| 재생성 수행을 곧 복구로 간주 | `remaining` 은 **재생성 후 재점검** 결과 — "재생성했다" 와 "복구됐다" 를 구분해 보고 | +| 점검 커맨드의 비-0 종료를 "실행 실패" 로 표시 | 이상 발견 신호다. 종료 코드와 출력을 그대로 노출 | +| 특정 엔진(FULLTEXT) 전용으로 점검·재생성 구현 | `SearchIndexMaintainer` 계약 + `core.search.index_maintainers` 훅 | +| "점검 대상 0" 과 "점검 불가" 를 같은 문구로 보고 | 구분 보고 — 뭉뚱그리면 "인덱스가 다 정상" 으로 읽힌다 | + +> 상세: [search-system.md](docs/backend/search-system.md) ### Listener 데이터 접근 diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9d5621..11344dc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,17 @@ #### 목록 성능 -- 목록 화면이 뒤쪽 페이지로 갈수록 느려지는 문제를 구조적으로 해결할 수 있도록, 확장이 함께 쓸 수 있는 공통 조회 방식을 코어에 추가했습니다. 목록을 두 단계(먼저 이번 페이지에 해당하는 항목만 추려내고, 그 항목에 대해서만 본문·상세 정보를 읽기)로 나눠 읽으므로 게시글·주문·로그가 수십만 건으로 늘어나도 마지막 페이지 조회 비용이 첫 페이지와 비슷하게 유지됩니다. 목록 정렬 기준을 미리 정해 둔 항목으로만 해석하는 공통 처리도 함께 제공하므로, 확장이 목록 조회를 직접 만들 때 정렬 처리를 매번 새로 구현하지 않아도 됩니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 검색 인덱스가 실제로 내용을 담고 있는지 점검하고, 비어 있으면 다시 만들 수 있는 기능을 추가했습니다. 인덱스가 비면 검색은 오류 없이 "결과 없음" 만 돌려주기 때문에 그동안은 알아차릴 방법이 없었습니다. 관리자 > 개발 도구에서 「검색 인덱스 점검」 으로 확인할 수 있고, `php artisan search:index` 로도 실행됩니다. 점검·재생성 방식은 검색 엔진마다 다르므로, 다른 검색 엔진을 쓰는 확장도 자기 방식의 점검을 등록해 같은 화면에서 함께 다룰 수 있습니다. +- 다시 만들기는 **선택할 때만** 실행됩니다. 인덱스를 다시 만드는 동안에는 해당 인덱스가 잠기거나 전체 재색인이 일어나 운영 중인 사이트에 영향을 주기 때문입니다. 확장·코어 업데이트 화면과 명령어에 「업데이트 후 검색 인덱스 재생성」 선택 항목을 두었고, 선택하지 않아도 색인이 비어 있으면 그 사실은 알려 드립니다. 사이트 규모가 크다면 선택하지 말고 접속이 적은 시간에 따로 실행하시길 권합니다. +- 업데이트 화면의 「검색 인덱스 재생성」 선택이 그 창에서만 유효하도록 바로잡았습니다. 예전에는 한 번 선택하면 창을 닫거나 업데이트를 마친 뒤에도 선택이 남아 있어, 다음 확장을 업데이트할 때 아무것도 고르지 않았는데 인덱스 재생성이 다시 실행됐습니다. +- 확장을 업데이트하면 색인이 비어 있는 검색 인덱스가 있는지 결과가 함께 전달되도록 바로잡았습니다. 예전에는 점검은 이루어졌지만 그 결과가 화면까지 오지 않아 운영자가 알 수 없었습니다. +- 모듈·플러그인 업데이트 완료 안내에 확장 이름과 버전이 표시되지 않던 문제를 수정했습니다. +- 모듈·플러그인 목록의 총 개수와 페이지 이동이 동작하지 않던 문제를 수정했습니다. 설치된 확장이 12개를 넘으면 2페이지 이후 항목이 화면에 나타나지 않았습니다. +- `php artisan module:check-updates` 등 업데이트 확인 명령이 업데이트가 있는 확장을 「최신」 으로 표시하고 요약도 "업데이트 가능 0개" 로 알리던 문제를 수정했습니다. +- 통신이 끊겨 요청이 전송되지 못했을 때 내부 식별 문구가 그대로 표시되던 것을 네트워크 오류 안내로 바꿨습니다. +- 개발 도구에서 점검 명령이 이상을 발견해 알려 준 경우를 「실행 실패」 로 표시하던 것을 종료 코드 안내로 바꿨습니다. 점검은 정상 수행된 것이며, 발견된 내용은 출력에 그대로 표시됩니다. + +- 목록 화면이 뒤쪽 페이지로 갈수록 느려지는 문제를 구조적으로 해결할 수 있도록, 확장이 함께 쓸 수 있는 공통 조회 방식을 코어에 추가했습니다. 목록을 두 단계(먼저 이번 페이지에 해당하는 항목만 추려내고, 그 항목에 대해서만 본문·상세 정보를 읽기)로 나눠 읽으므로 게시글·주문·로그가 수십만 건으로 늘어나도 마지막 페이지 조회 비용이 첫 페이지와 비슷하게 유지됩니다. 목록 정렬 기준을 미리 정해 둔 항목으로만 해석하는 공통 처리도 함께 제공하므로, 확장이 목록 조회를 직접 만들 때 정렬 처리를 매번 새로 구현하지 않아도 됩니다. 주문 목록의 「발송일」처럼 정렬 기준이 다른 표에 있는 값일 때도 총 건수와 페이지 경계가 어긋나지 않게 조회하는 공통 처리를 함께 넣었습니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) #### 성능 점검 @@ -59,6 +69,7 @@ - 활동 로그·알림 발송 이력·본인인증 기록·스케줄 실행 이력·회원 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 기록의 본문·변경 내역까지 함께 읽어 느려졌지만, 이제 현재 페이지에 해당하는 기록만 상세 정보를 읽습니다. 화면에 보이는 내용은 이전과 동일합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 확장이 업로드 허용 형식을 넓힐 수 있도록 `core.attachment.allowed_extensions` 필터를 추가했습니다. 관리자 설정 목록을 기준으로, 확장이 자기 기능에 필요한 형식만 덧붙일 수 있습니다. +- 활동 로그·알림 발송 이력·회원 목록의 정렬 색인을 정비했습니다. 같은 시각에 쌓인 기록이 많은 구간에서 순서를 정하느라 생기던 추가 작업이 사라져, 기록이 많은 사이트일수록 목록이 빨라집니다. 기존 사이트도 업데이트 시 자동으로 반영되며, 기록이 아주 많은 경우 이 과정에 수 분이 걸리고 그동안 해당 기록의 저장이 잠시 대기할 수 있습니다. - 확장이 검증 규칙을 추가할 때 쓰는 필터 이름 10개를 나머지 92개와 같은 규칙(`{대상}.{동작}_validation_rules`)으로 통일했습니다. 본인인증 메시지와 알림 관련 화면이 대상이며, 예전 이름(`...filter_index_rules` 형태)을 사용하던 확장은 새 이름으로 바꿔야 검증 규칙 추가가 계속 동작합니다. 코어와 기본 제공 확장에는 이 이름을 사용하던 곳이 없어 영향이 없습니다. - 같은 규칙에서 벗어나 있던 필터 이름 6개(플러그인 설정 저장, 비밀번호 재설정 토큰 확인, 비밀번호 확인, 변경사항 조회, 통합 검색, 프로필 사진 업로드)를 추가로 통일했습니다. 이번에는 예전 이름도 함께 계속 발행하므로 기존 확장은 수정 없이 그대로 동작하며, 예전 이름을 사용 중인 확장이 있으면 서버 기록에 한 번 안내가 남습니다. 새로 만드는 확장은 새 이름을 사용하세요. @@ -96,6 +107,11 @@ #### 확장·데이터베이스 안정성 +- 관리자 역할 관리·언어팩 관리·알림 목록에서 같은 시각에 등록된 항목이 여러 건일 때, 페이지를 넘기면 같은 항목이 두 번 보이고 다른 항목은 아예 보이지 않던 문제를 수정했습니다. 이제 페이지를 모두 넘겨도 목록에 있는 항목이 빠짐없이 한 번씩만 나옵니다. +- 메뉴 관리 목록에서 검색 조건을 하나도 걸지 않은 채 정렬만 바꾸면 순서가 그대로이던 문제를 수정했습니다. 검색 조건이 있을 때만 정렬이 반영되고 있었으며, 이제 조건 유무와 관계없이 선택한 정렬이 적용됩니다. +- 메뉴 관리 검색에서 한글 메뉴명으로 찾으면 결과가 나오지 않던 문제를 수정했습니다. 이제 한국어·영어 어느 쪽 메뉴명으로도 검색되고, 이름 정렬도 화면에 보이는 글자 기준으로 정렬됩니다. +- 목록의 열 제목을 눌렀을 때 지금 보고 있는 페이지의 줄만 순서가 바뀌면서 전체가 정렬된 것처럼 보이던 문제를 수정했습니다. 페이지를 나눠 불러오는 목록에서 열 제목 정렬을 지원하지 않는 화면은 이제 정렬 표시를 띄우지 않으므로, 정렬 결과를 잘못 읽을 일이 없습니다. 정렬은 화면 위쪽의 정렬 선택으로 이용할 수 있습니다. +- 언어팩 관리 목록이 모듈의 내장 번역을 실제 화면에 쓰이지 않는 위치 기준으로 보여주던 문제를 수정했습니다. 그 결과 일부 모듈의 한국어·영어 내장 번역이 목록에 아예 나오지 않았고, 번역 대상으로 표시된 문구를 고쳐도 화면에 반영되지 않았습니다. 이제 목록과 실제 화면의 문구 출처가 같습니다. - 데이터베이스 연결이 일시적으로 끊기거나 확장 설치가 중간에 실패한 직후 확장 목록을 다시 만들면, 설치된 모듈·플러그인 정보가 통째로 비워지면서 게시판·쇼핑몰 등 모든 확장 화면이 500 오류를 내던 문제를 수정했습니다. 이제 확장 폴더가 그대로 남아 있는데 목록만 비어 있으면 기존 정보를 지우지 않고 보존하며, 왜 갱신하지 않았는지 로그에 남깁니다. - 확장 목록이 손상된 상태에서 복구 명령을 실행하면 목록은 되살아나지만 확장 기능 연결이 비어 있는 채로 남아, 명령을 두 번 실행해야 완전히 복구되던 문제를 수정했습니다. 이제 한 번 실행으로 모두 복구됩니다. - 데이터베이스에서 일부 컬럼의 설명이 비어 있던 문제를 바로잡았습니다. 회원·작성자 등 다른 표를 가리키는 컬럼이 대상이며, 업그레이드하면 이미 운영 중인 데이터베이스에도 설명이 채워집니다(직접 적어 넣은 설명은 그대로 둡니다). @@ -120,6 +136,11 @@ - 일부 안내가 실제 문장 대신 내부 식별자(`messages.auth.unauthenticated` 같은 문자열)로 표시되던 문제를 수정했습니다. 로그인하지 않았거나 역할 권한이 없어 요청이 거절될 때, 메뉴에 역할을 지정할 때, 확장 식별자를 잘못 입력했을 때, 활동 로그를 기간·유형으로 검색할 때가 해당됩니다. - 환경설정 저장이 실패했을 때 안내 문구에 항목 이름이 `security.password min length` 같은 내부 식별자로 표시되던 문제를 수정했습니다. 보안·업로드·SEO·고급·드라이버·메일 등 모든 설정 항목이 화면에 보이는 한글 항목명으로 안내됩니다. - 플러그인을 GitHub 주소나 ZIP 파일로 설치할 때의 입력 안내가 내부 식별자로 표시되던 문제를 수정했습니다. 주소 형식이 잘못됐거나, ZIP이 아닌 파일을 올렸거나, 파일 용량을 초과했을 때 정상 문장으로 안내합니다. +- 목록의 열 제목을 눌러 정렬할 때, 이미 다른 열로 정렬 중이었다면 화살표는 새로 누른 열에 표시되면서 실제 정렬은 이전 열 기준으로 바뀌던 문제를 수정했습니다. 표시와 실제 정렬 기준이 어긋나 결과를 신뢰할 수 없었으며, 이제 누른 열 기준으로 한 번에 정렬됩니다. +- 목록에서 뒤쪽 페이지를 보던 중 정렬을 바꾸면 이전 페이지 번호에 그대로 머물러, 정렬을 바꿨는데 엉뚱한 구간이 보이거나 빈 목록이 나오던 문제를 수정했습니다. 이제 정렬을 바꾸면 항상 첫 페이지부터 보여줍니다. 사용자 관리·게시판 신고현황·페이지 관리 목록이 해당됩니다. +- 사용자 관리 목록에서 "페이지당 개수"가 10개로 표시되는데 실제로는 15개가 나오던 문제를 수정했습니다. 표시된 값과 실제 목록 건수가 일치합니다. 일정 관리 목록에서도 표시가 20개 목록을 10개로 잘못 안내하던 문제를 함께 바로잡았습니다. +- 알림 발송 이력에서 정렬을 "수신자명순" 또는 "제목순"으로 바꾸면 목록이 비면서 오류 안내가 뜨던 문제를 수정했습니다. 화면이 제공하는 정렬인데 서버가 거부하고 있었으며, 이제 두 정렬 모두 정상 동작합니다. 많은 이력에서도 빠르게 정렬되도록 색인을 함께 추가했습니다. +- 마이페이지 활동 목록에서 한 페이지에 표시할 개수를 0이나 음수로 요청하면 서버 오류가 나던 문제를 수정했습니다. 이제 허용 범위(1~100)로 자동 조정됩니다. - 레이아웃 편집기 화면이 정상 동작하는데도 서버가 "페이지 없음"(404)으로 응답하던 문제를 수정했습니다. 화면은 그대로 보였지만 브라우저 캐싱과 서버 접속 기록에서 없는 페이지로 취급되던 상태입니다. - 주소 끝에 슬래시가 붙은 형태(예: `/admin/`)로 접속하면 화면이 "페이지 없음"(404)으로 뜨던 문제를 수정했습니다. 특히 로그인이 풀린 상태에서 이렇게 접속하면 로그인 화면으로 넘어가지 않고 404가 나타났습니다. 이제 끝 슬래시가 있어도 정상적으로 같은 페이지로 연결됩니다. @@ -1500,7 +1521,6 @@ abort 발생 시 운영자에게 수동 재개 명령 (`php artisan core:execute - 휴대폰 인증, 아이핀 인증 등 본인인증 기능 제공 - 사용자 관리 화면 레이아웃 확장 (본인인증 상세/폼) - 역할 기반 접근 제어 (본인인증 관리자) -- 다국어 지원 (ko, en) #### 템플릿: sirsoft-admin_basic (관리자) @@ -1536,7 +1556,6 @@ abort 발생 시 운영자에게 수동 재개 명령 (`php artisan core:execute - CMS 페이지 표시 화면 - 에러 페이지 (403, 404, 500, 503) - 점검 모드(maintenance) 전용 페이지 -- 반응형 레이아웃 (데스크톱/태블릿/모바일) #### 다국어 시스템 (i18n) diff --git a/app/Benchmark/Axes/ListAxisRunner.php b/app/Benchmark/Axes/ListAxisRunner.php index 44d2e27d..ed701f49 100644 --- a/app/Benchmark/Axes/ListAxisRunner.php +++ b/app/Benchmark/Axes/ListAxisRunner.php @@ -71,19 +71,22 @@ class ListAxisRunner implements BenchmarkAxisRunner $notify("비움: {$table}"); } + $columns = $this->existingColumns($table, (array) $profile->option('columns', ['*'])); + $order = $this->existingOrder($table, (array) $profile->option('order', [['id', 'desc']])); + if ($options->seed > 0) { $notify("시딩: {$table} ".number_format($options->seed).' 건'); $this->seeder->seed( $table, $options->seed, (array) $profile->option('seed_overrides', []), - fn (int $inserted, int $total) => $notify(sprintf(' 시딩 %s / %s', number_format($inserted), number_format($total))) + fn (int $inserted, int $total) => $notify(sprintf(' 시딩 %s / %s', number_format($inserted), number_format($total))), + // 정렬 기준 컬럼은 nullable 이어도 채운다 — 비워두면 합성 행이 전부 동률이 되어 + // 계측이 정렬 인덱스가 아니라 tie-break 경로를 잰다. + array_column($order, 0), ); } - $columns = $this->existingColumns($table, (array) $profile->option('columns', ['*'])); - $order = $this->existingOrder($table, (array) $profile->option('order', [['id', 'desc']])); - $rows = []; $notes = []; diff --git a/app/Benchmark/ListIndexAdvisor.php b/app/Benchmark/ListIndexAdvisor.php new file mode 100644 index 00000000..e0123728 --- /dev/null +++ b/app/Benchmark/ListIndexAdvisor.php @@ -0,0 +1,203 @@ + $options 목록 프로파일 옵션 + * @return array 선행 컬럼 순서 (등치 → deleted_at → 정렬 → 기본키) + */ + public function requiredColumns(array $options, string $keyName = 'id'): array + { + $leading = []; + + foreach (($options['filters'] ?? []) as $column => $declared) { + if (is_array($declared) && count($declared) === 2 && is_string($declared[0])) { + // [연산자, 값] 형태 — 등치만 선행 컬럼 자격이 있다 + if (in_array(strtolower($declared[0]), self::EQUALITY_OPERATORS, true)) { + $leading[] = (string) $column; + } + + continue; + } + + // 값만 준 형태는 등가 비교 + $leading[] = (string) $column; + } + + if ($options['soft_delete'] ?? false) { + $leading[] = 'deleted_at'; + } + + foreach (($options['order'] ?? []) as $spec) { + $column = is_array($spec) ? ($spec[0] ?? null) : $spec; + + if (is_string($column) && $column !== '') { + $leading[] = $column; + } + } + + // 정렬 끝에 기본키가 없으면 동률 구간에서 filesort 가 남는다 + if (end($leading) !== $keyName) { + $leading[] = $keyName; + } + + return array_values(array_unique($leading)); + } + + /** + * 도출한 색인이 실제 스키마에 있는지 대조합니다. + * + * @param array $options 목록 프로파일 옵션 + * @return array{ + * table: string|null, + * required: array, + * satisfied_by: string|null, + * partial_by: string|null, + * status: string + * } status: satisfied | tiebreak_missing | missing | table_absent | not_applicable + */ + public function inspect(array $options, string $keyName = 'id'): array + { + $table = $options['table'] ?? null; + $required = $this->requiredColumns($options, $keyName); + + $result = [ + 'table' => $table, + 'required' => $required, + 'satisfied_by' => null, + 'partial_by' => null, + 'status' => 'not_applicable', + ]; + + if (! is_string($table) || $table === '') { + return $result; + } + + if (! Schema::hasTable($table)) { + $result['status'] = 'table_absent'; + + return $result; + } + + // 기본키를 뺀 형태 — 색인은 있으나 tie-break 만 빠진 경우를 구분한다. + // 이 둘은 처방이 다르다: 전자는 색인 신설, 후자는 기존 색인에 기본키를 덧붙이는 교체. + $withoutKey = $required; + if (end($withoutKey) === $keyName) { + array_pop($withoutKey); + } + + foreach (Schema::getIndexes($table) as $index) { + $columns = $index['columns'] ?? []; + + if ($result['satisfied_by'] === null && $this->hasPrefix($columns, $required)) { + $result['satisfied_by'] = $index['name']; + } + + if ($this->hasPrefix($columns, $withoutKey)) { + // UNIQUE 색인은 값이 중복되지 않으므로 그 컬럼만으로 이미 전순서다. + // 기본키를 덧붙일 필요가 없고, 붙여도 filesort 가 줄지 않는다. + if ($result['satisfied_by'] === null && ($index['unique'] ?? false)) { + $result['satisfied_by'] = $index['name']; + } + + if ($result['partial_by'] === null) { + $result['partial_by'] = $index['name']; + } + } + } + + $result['status'] = match (true) { + $result['satisfied_by'] !== null => 'satisfied', + $result['partial_by'] !== null => 'tiebreak_missing', + default => 'missing', + }; + + return $result; + } + + /** + * 프로파일에 선언된 면제 사유를 돌려줍니다. + * + * 행 수가 고정되거나 상한이 작아 색인이 불필요한 목록은 프로파일에 사유를 적어 면제한다. + * 사유 없는 면제를 허용하지 않는 이유는, 면제가 쌓이면 검사 자체가 무의미해지는데 + * 사유가 없으면 나중에 그 판단이 여전히 옳은지 확인할 방법이 없기 때문이다. + * + * @param array $options 목록 프로파일 옵션 + * @return string|null 면제 사유 (없으면 null) + */ + public function exemptionReason(array $options): ?string + { + $reason = $options['index_exempt'] ?? null; + + return is_string($reason) && trim($reason) !== '' ? trim($reason) : null; + } + + /** + * 프로파일 목록을 한 번에 점검합니다. + * + * @param array $profiles 목록 축 프로파일 + * @return array> 프로파일별 점검 결과 + */ + public function inspectAll(array $profiles): array + { + $rows = []; + + foreach ($profiles as $key => $profile) { + $options = $profile->options ?? []; + + $rows[] = array_merge( + ['profile' => is_string($key) ? $key : $profile->qualifiedKey()], + $this->inspect($options), + ['exemption' => $this->exemptionReason($options)] + ); + } + + return $rows; + } + + /** + * 인덱스 컬럼이 요구 목록을 선행 프리픽스로 포함하는지 판정합니다. + * + * @param array $columns 인덱스 컬럼 순서 + * @param array $required 요구 선행 컬럼 + */ + private function hasPrefix(array $columns, array $required): bool + { + if ($required === [] || count($columns) < count($required)) { + return false; + } + + return array_slice(array_values($columns), 0, count($required)) === array_values($required); + } +} diff --git a/app/Benchmark/SyntheticSeeder.php b/app/Benchmark/SyntheticSeeder.php index 71671297..74ba17ac 100644 --- a/app/Benchmark/SyntheticSeeder.php +++ b/app/Benchmark/SyntheticSeeder.php @@ -32,16 +32,18 @@ class SyntheticSeeder * @param int $count 시딩 건수 * @param array $overrides 컬럼별 고정값 (실제 조회 조건과 맞추기 위한 지정) * @param \Closure|null $onProgress 진행 콜백 (int $inserted, int $total) + * @param array $requiredColumns nullable 이어도 반드시 채울 컬럼 (계측이 정렬 기준으로 삼는 컬럼) */ - public function seed(string $table, int $count, array $overrides = [], ?\Closure $onProgress = null): void + public function seed(string $table, int $count, array $overrides = [], ?\Closure $onProgress = null, array $requiredColumns = []): void { $fillable = $this->fillableColumns($table); + $required = $this->requiredColumnDefinitions($table, $requiredColumns, $fillable); $chunk = []; $inserted = 0; for ($i = 1; $i <= $count; $i++) { - $chunk[] = $this->synthesizeRow($table, $fillable, $overrides, $i); + $chunk[] = $this->synthesizeRow($table, array_merge($fillable, $required), $overrides, $i); if (count($chunk) >= self::CHUNK_SIZE) { $this->insert($table, $chunk); @@ -94,6 +96,48 @@ class SyntheticSeeder )); } + /** + * 계측이 정렬 기준으로 삼는 컬럼의 정의를 반환합니다. + * + * `fillableColumns()` 는 "NOT NULL + 기본값 없음" 만 남기므로, Laravel 기본 `timestamps()` + * 처럼 nullable 로 선언된 `created_at` 계열은 여기서 빠진다. 그런데 목록 프로파일 대부분이 + * `created_at` 으로 정렬하므로, 그대로 두면 합성 행이 전부 NULL 동률이 되어 계측이 + * 정렬 인덱스가 아니라 tie-break 경로를 재게 된다. + * + * @param string $table 대상 테이블 + * @param array $names 반드시 채울 컬럼명 목록 + * @param array> $already 이미 채우기로 한 컬럼 목록 + * @return array> 추가로 채울 컬럼 정의 목록 + */ + private function requiredColumnDefinitions(string $table, array $names, array $already): array + { + if ($names === []) { + return []; + } + + $covered = array_column($already, 'name'); + $columns = collect(Schema::getColumns($table))->keyBy('name'); + + $extra = []; + + foreach (array_unique($names) as $name) { + if (in_array($name, $covered, true) || ! $columns->has($name)) { + continue; + } + + $column = $columns->get($name); + + // auto increment 키는 DB 가 채운다 + if ($column['auto_increment'] ?? false) { + continue; + } + + $extra[] = $column; + } + + return $extra; + } + /** * 합성 행 1건을 만듭니다. * diff --git a/app/Console/Commands/Core/Concerns/BundledExtensionUpdatePrompt.php b/app/Console/Commands/Core/Concerns/BundledExtensionUpdatePrompt.php index ee1858cd..768695de 100644 --- a/app/Console/Commands/Core/Concerns/BundledExtensionUpdatePrompt.php +++ b/app/Console/Commands/Core/Concerns/BundledExtensionUpdatePrompt.php @@ -7,6 +7,7 @@ use App\Extension\ModuleManager; use App\Extension\PluginManager; use App\Extension\TemplateManager; use App\Extension\Vendor\VendorMode; +use App\Search\SearchIndexMaintenanceManager; use App\Services\CoreUpdateService; use App\Services\LanguagePackService; use Illuminate\Support\Facades\File; @@ -109,6 +110,10 @@ trait BundledExtensionUpdatePrompt // 확장별 전략 오버라이드 $strategies = $this->collectPerExtensionStrategies($updates, $globalStrategy, $force); + // 검색 인덱스 재생성 여부 — 인덱스 잠금·재색인 비용이 있어 운영 중인 사이트에서는 + // 기본값(아니오)을 유지하고 유지보수 시간에 별도로 수행하는 것이 안전하다. + $rebuildSearchIndex = $this->askRebuildSearchIndex($force); + // 일괄 실행 return $this->executeBulkUpdate( $moduleManager, @@ -116,6 +121,7 @@ trait BundledExtensionUpdatePrompt $templateManager, $updates, $strategies, + $rebuildSearchIndex, ); } @@ -200,8 +206,11 @@ trait BundledExtensionUpdatePrompt TemplateManager $templateManager, array $updates, array $strategies, + bool $rebuildSearchIndex = false, ): array { $manifest = $this->buildBundledUpdateManifest($updates, $strategies); + // 운영자의 선택을 매니페스트에 실어 spawn 자식과 in-process fallback 이 같은 결정을 따르게 한다 + $manifest['rebuild_search_index'] = $rebuildSearchIndex; if ($this->bundledManifestIsEmpty($manifest)) { return ['success' => 0, 'failed' => 0, 'skipped' => 0, 'has_updates' => false]; } @@ -228,6 +237,7 @@ trait BundledExtensionUpdatePrompt $templateManager, $updates, $strategies, + $rebuildSearchIndex, ); } @@ -385,6 +395,7 @@ trait BundledExtensionUpdatePrompt TemplateManager $templateManager, array $updates, array $strategies, + bool $rebuildSearchIndex = false, ): array { $success = 0; $failed = 0; @@ -452,6 +463,8 @@ trait BundledExtensionUpdatePrompt $this->newLine(); $this->info("업데이트 완료: 성공 {$success}, 실패 {$failed}"); + $this->runSearchIndexRebuildIfRequested($rebuildSearchIndex); + return [ 'success' => $success, 'failed' => $failed, @@ -460,4 +473,60 @@ trait BundledExtensionUpdatePrompt ]; } + /** + * 검색 인덱스 재생성 여부를 운영자에게 확인합니다. + * + * 재생성은 인덱스 잠금 또는 전체 재색인을 유발하므로 기본값은 "아니오" 입니다. + * `--force` (무인 실행) 시에도 묻지 않고 수행하지 않습니다 — 무인 실행이 + * 대용량 테이블을 잠그는 일이 없어야 합니다. 필요하면 `core:update + * --rebuild-search-index` 로 명시하거나 이후 `search:index --repair` 를 실행합니다. + * + * @param bool $force 강제(무인) 실행 여부 + * @return bool 재생성 수행 여부 + */ + private function askRebuildSearchIndex(bool $force): bool + { + // 커맨드가 옵션으로 이미 선택을 받았으면 그대로 따른다 + if ($this->hasOption('rebuild-search-index') && (bool) $this->option('rebuild-search-index')) { + return true; + } + + if ($force) { + return false; + } + + $manager = app(SearchIndexMaintenanceManager::class); + + // 점검을 제공하지 않는 엔진에서는 물을 것이 없다 + if (! $manager->hasMaintainer() || $manager->unavailableReason() !== null) { + return false; + } + + $this->newLine(); + $this->line(' '.__('search.index.rebuild_cost_warning')); + + return $this->unifiedConfirm(__('search.index.rebuild_after_bulk_confirm'), false); + } + + /** + * 요청이 있었으면 검색 인덱스를 재생성합니다. + * + * @param bool $requested 재생성 요청 여부 + * @return void + */ + private function runSearchIndexRebuildIfRequested(bool $requested): void + { + if (! $requested) { + return; + } + + $report = app(SearchIndexMaintenanceManager::class)->repairStale(); + + $this->newLine(); + $this->info('검색 인덱스: '.$report->summary()); + + foreach ($report->failed as $identifier => $message) { + $this->warn(' '.__('search.index.rebuild_failed_item', ['index' => $identifier, 'error' => $message])); + } + } } diff --git a/app/Console/Commands/Core/CoreUpdateCommand.php b/app/Console/Commands/Core/CoreUpdateCommand.php index b15986f7..117f0335 100644 --- a/app/Console/Commands/Core/CoreUpdateCommand.php +++ b/app/Console/Commands/Core/CoreUpdateCommand.php @@ -34,7 +34,8 @@ class CoreUpdateCommand extends Command {--local : 로컬 코드베이스를 업데이트 소스로 사용 (GitHub 스킵)} {--source= : 수동 업데이트용 소스 디렉토리 경로 (GitHub 다운로드 대신 지정 디렉토리 사용)} {--zip= : 수동 업데이트용 ZIP 파일 경로 (GitHub 다운로드 대신 지정 ZIP 추출 사용)} - {--vendor-mode=auto : vendor 설치 모드 (auto|composer|bundled)}'; + {--vendor-mode=auto : vendor 설치 모드 (auto|composer|bundled)} + {--rebuild-search-index : 번들 확장 일괄 업데이트 후 색인이 누락된 검색 인덱스를 재생성 (인덱스가 잠기거나 재색인됩니다 — 운영 중에는 유지보수 시간에 수행하세요)}'; protected $description = '그누보드7 코어를 최신 버전으로 업데이트합니다'; diff --git a/app/Console/Commands/Core/ExecuteBundledUpdatesCommand.php b/app/Console/Commands/Core/ExecuteBundledUpdatesCommand.php index 66dfb893..d01375ba 100644 --- a/app/Console/Commands/Core/ExecuteBundledUpdatesCommand.php +++ b/app/Console/Commands/Core/ExecuteBundledUpdatesCommand.php @@ -2,10 +2,12 @@ namespace App\Console\Commands\Core; +use App\Extension\ExtensionManager; use App\Extension\ModuleManager; use App\Extension\PluginManager; use App\Extension\TemplateManager; use App\Extension\Vendor\VendorMode; +use App\Search\SearchIndexMaintenanceManager; use App\Services\LanguagePackService; use Illuminate\Console\Command; use Illuminate\Support\Facades\File; @@ -45,7 +47,8 @@ class ExecuteBundledUpdatesCommand extends Command protected $signature = 'core:execute-bundled-updates {--manifest= : 업데이트 매니페스트 JSON 파일 경로 (필수)} - {--force : 강제 업데이트 플래그 (확장 매니저 update 호출에 전달)}'; + {--force : 강제 업데이트 플래그 (확장 매니저 update 호출에 전달)} + {--rebuild-search-index : 일괄 업데이트 후 검색 인덱스 재생성 (매니페스트의 선택보다 우선)}'; protected $description = '번들 확장 일괄 업데이트를 실행합니다 (CoreUpdateCommand 내부용 — fresh PHP 프로세스에서 호출)'; @@ -63,7 +66,7 @@ class ExecuteBundledUpdatesCommand extends Command // (Artisan::call 대신 직접 메서드 호출 — nested Artisan::call 이 outer 명령의 // output buffer 를 덮어쓰는 Laravel 동작 회피) try { - app(\App\Extension\ExtensionManager::class)->updateComposerAutoload(); + app(ExtensionManager::class)->updateComposerAutoload(); } catch (\Throwable $e) { Log::warning('bundled update spawn 자식: updateComposerAutoload 호출 실패', [ 'error' => $e->getMessage(), @@ -77,6 +80,9 @@ class ExecuteBundledUpdatesCommand extends Command return self::FAILURE; } + // 부모(core:update)가 운영자 선택을 매니페스트에 실어 보낸다 — 자식이 임의로 정하지 않는다 + $rebuildSearchIndex = (bool) $this->option('rebuild-search-index') || (bool) ($manifest['rebuild_search_index'] ?? false); + $modules = $manifest['modules'] ?? []; $plugins = $manifest['plugins'] ?? []; $templates = $manifest['templates'] ?? []; @@ -166,6 +172,17 @@ class ExecuteBundledUpdatesCommand extends Command $this->newLine(); $this->info("업데이트 완료: 성공 {$success}, 실패 {$failed}"); + // 검색 인덱스 재생성 — 운영자가 부모 단계에서 선택했을 때만 수행 (인덱스 잠금·재색인 비용) + if ($rebuildSearchIndex) { + $report = app(SearchIndexMaintenanceManager::class)->repairStale(); + $this->newLine(); + $this->info('검색 인덱스: '.$report->summary()); + + foreach ($report->failed as $identifier => $message) { + $this->warn(' '.__('search.index.rebuild_failed_item', ['index' => $identifier, 'error' => $message])); + } + } + // 부모 프로세스가 결과를 복원할 수 있도록 표식 라인으로 페이로드 출력 $this->line(self::RESULT_PREFIX.json_encode([ 'success' => $success, @@ -174,5 +191,4 @@ class ExecuteBundledUpdatesCommand extends Command return $failed > 0 ? self::FAILURE : self::SUCCESS; } - } diff --git a/app/Console/Commands/Module/CheckModuleUpdatesCommand.php b/app/Console/Commands/Module/CheckModuleUpdatesCommand.php index 45997dbf..f8937df4 100644 --- a/app/Console/Commands/Module/CheckModuleUpdatesCommand.php +++ b/app/Console/Commands/Module/CheckModuleUpdatesCommand.php @@ -116,7 +116,7 @@ class CheckModuleUpdatesCommand extends Command $updateCount = 0; foreach ($result['details'] as $detail) { - $isUpdate = $detail['update_available'] ?? false; + $isUpdate = (bool) ($detail['update_available'] ?? true); if ($isUpdate) { $updateCount++; } @@ -143,7 +143,7 @@ class CheckModuleUpdatesCommand extends Command $this->table($headers, $tableData); $this->newLine(); $this->info(__('modules.commands.check_updates.summary', [ - 'total' => count($result['details']), + 'total' => $result['checked_count'] ?? count($result['details']), 'updates' => $updateCount, ])); diff --git a/app/Console/Commands/Module/InstallModuleCommand.php b/app/Console/Commands/Module/InstallModuleCommand.php index 297e1d71..a681d8a1 100644 --- a/app/Console/Commands/Module/InstallModuleCommand.php +++ b/app/Console/Commands/Module/InstallModuleCommand.php @@ -3,6 +3,7 @@ namespace App\Console\Commands\Module; use App\Console\Commands\Traits\HasProgressBar; +use App\Console\Commands\Traits\RebuildsSearchIndex; use App\Contracts\Repositories\ModuleRepositoryInterface; use App\Enums\ExtensionOwnerType; use App\Extension\ModuleManager; @@ -17,6 +18,7 @@ use Illuminate\Support\Facades\Validator; class InstallModuleCommand extends Command { use HasProgressBar; + use RebuildsSearchIndex; /** * The name and signature of the console command. @@ -24,7 +26,8 @@ class InstallModuleCommand extends Command protected $signature = 'module:install {identifier : 설치할 모듈 식별자} {--vendor-mode=auto : Vendor 설치 모드 (auto|composer|bundled)} - {--force : 이미 설치된 경우에도 _bundled/_pending 원본으로 활성 디렉토리를 덮어쓰고 재설치 (불완전 설치 복구)}'; + {--force : 이미 설치된 경우에도 _bundled/_pending 원본으로 활성 디렉토리를 덮어쓰고 재설치 (불완전 설치 복구)} + {--rebuild-search-index : 완료 후 색인이 누락된 검색 인덱스를 재생성 (인덱스가 잠기거나 재색인됩니다 — 운영 중에는 유지보수 시간에 수행하세요)}'; /** * The console command description. @@ -114,6 +117,9 @@ class InstallModuleCommand extends Command Log::info(__('modules.commands.install.success', ['module' => $identifier])); + // 검색 인덱스 재생성은 운영자가 선택했을 때만 수행한다 (인덱스 잠금·재색인 비용) + $this->handleSearchIndexRebuild(); + return Command::SUCCESS; } diff --git a/app/Console/Commands/Module/UpdateModuleCommand.php b/app/Console/Commands/Module/UpdateModuleCommand.php index d87444c2..e04b8d04 100644 --- a/app/Console/Commands/Module/UpdateModuleCommand.php +++ b/app/Console/Commands/Module/UpdateModuleCommand.php @@ -4,6 +4,7 @@ namespace App\Console\Commands\Module; use App\Console\Commands\Traits\HasProgressBar; use App\Console\Commands\Traits\HasUnifiedConfirm; +use App\Console\Commands\Traits\RebuildsSearchIndex; use App\Contracts\Repositories\ModuleRepositoryInterface; use App\Enums\LayoutSourceType; use App\Extension\ModuleManager; @@ -16,6 +17,7 @@ class UpdateModuleCommand extends Command { use HasProgressBar; use HasUnifiedConfirm; + use RebuildsSearchIndex; /** * The name and signature of the console command. @@ -26,7 +28,8 @@ class UpdateModuleCommand extends Command {--vendor-mode=auto : Vendor 설치 모드 (auto|composer|bundled)} {--layout-strategy=overwrite : 레이아웃 전략 (overwrite|keep)} {--source=auto : 업데이트 소스 (auto|bundled|github) — bundled 는 _bundled 만 사용(GitHub 우회)} - {--zip= : 외부 ZIP 파일 경로 (지정 시 GitHub/번들 우회 + 버전은 module.json 기준)}'; + {--zip= : 외부 ZIP 파일 경로 (지정 시 GitHub/번들 우회 + 버전은 module.json 기준)} + {--rebuild-search-index : 완료 후 색인이 누락된 검색 인덱스를 재생성 (인덱스가 잠기거나 재색인됩니다 — 운영 중에는 유지보수 시간에 수행하세요)}'; /** * The console command description. @@ -203,6 +206,9 @@ class UpdateModuleCommand extends Command 'layout_strategy' => $layoutStrategy, ]); + // 검색 인덱스 재생성은 운영자가 선택했을 때만 수행한다 (인덱스 잠금·재색인 비용) + $this->handleSearchIndexRebuild(); + return Command::SUCCESS; } diff --git a/app/Console/Commands/Plugin/CheckPluginUpdatesCommand.php b/app/Console/Commands/Plugin/CheckPluginUpdatesCommand.php index 8e41bbcc..17356375 100644 --- a/app/Console/Commands/Plugin/CheckPluginUpdatesCommand.php +++ b/app/Console/Commands/Plugin/CheckPluginUpdatesCommand.php @@ -116,7 +116,7 @@ class CheckPluginUpdatesCommand extends Command $updateCount = 0; foreach ($result['details'] as $detail) { - $isUpdate = $detail['update_available'] ?? false; + $isUpdate = (bool) ($detail['update_available'] ?? true); if ($isUpdate) { $updateCount++; } @@ -143,7 +143,7 @@ class CheckPluginUpdatesCommand extends Command $this->table($headers, $tableData); $this->newLine(); $this->info(__('plugins.commands.check_updates.summary', [ - 'total' => count($result['details']), + 'total' => $result['checked_count'] ?? count($result['details']), 'updates' => $updateCount, ])); diff --git a/app/Console/Commands/Plugin/InstallPluginCommand.php b/app/Console/Commands/Plugin/InstallPluginCommand.php index d4ab9488..47869484 100644 --- a/app/Console/Commands/Plugin/InstallPluginCommand.php +++ b/app/Console/Commands/Plugin/InstallPluginCommand.php @@ -3,6 +3,7 @@ namespace App\Console\Commands\Plugin; use App\Console\Commands\Traits\HasProgressBar; +use App\Console\Commands\Traits\RebuildsSearchIndex; use App\Contracts\Repositories\PluginRepositoryInterface; use App\Enums\ExtensionOwnerType; use App\Extension\PluginManager; @@ -16,6 +17,7 @@ use Illuminate\Support\Facades\Validator; class InstallPluginCommand extends Command { use HasProgressBar; + use RebuildsSearchIndex; /** * The name and signature of the console command. @@ -23,7 +25,8 @@ class InstallPluginCommand extends Command protected $signature = 'plugin:install {identifier : 설치할 플러그인 식별자} {--vendor-mode=auto : Vendor 설치 모드 (auto|composer|bundled)} - {--force : 이미 설치된 경우에도 _bundled/_pending 원본으로 활성 디렉토리를 덮어쓰고 재설치 (불완전 설치 복구)}'; + {--force : 이미 설치된 경우에도 _bundled/_pending 원본으로 활성 디렉토리를 덮어쓰고 재설치 (불완전 설치 복구)} + {--rebuild-search-index : 완료 후 색인이 누락된 검색 인덱스를 재생성 (인덱스가 잠기거나 재색인됩니다 — 운영 중에는 유지보수 시간에 수행하세요)}'; /** * The console command description. @@ -111,6 +114,9 @@ class InstallPluginCommand extends Command Log::info(__('plugins.commands.install.success', ['plugin' => $identifier])); + // 검색 인덱스 재생성은 운영자가 선택했을 때만 수행한다 (인덱스 잠금·재색인 비용) + $this->handleSearchIndexRebuild(); + return Command::SUCCESS; } diff --git a/app/Console/Commands/Plugin/UpdatePluginCommand.php b/app/Console/Commands/Plugin/UpdatePluginCommand.php index 82e95e3f..1a5038f9 100644 --- a/app/Console/Commands/Plugin/UpdatePluginCommand.php +++ b/app/Console/Commands/Plugin/UpdatePluginCommand.php @@ -4,6 +4,7 @@ namespace App\Console\Commands\Plugin; use App\Console\Commands\Traits\HasProgressBar; use App\Console\Commands\Traits\HasUnifiedConfirm; +use App\Console\Commands\Traits\RebuildsSearchIndex; use App\Contracts\Repositories\PluginRepositoryInterface; use App\Enums\LayoutSourceType; use App\Extension\PluginManager; @@ -16,6 +17,7 @@ class UpdatePluginCommand extends Command { use HasProgressBar; use HasUnifiedConfirm; + use RebuildsSearchIndex; /** * The name and signature of the console command. @@ -26,7 +28,8 @@ class UpdatePluginCommand extends Command {--vendor-mode=auto : Vendor 설치 모드 (auto|composer|bundled)} {--layout-strategy=overwrite : 레이아웃 전략 (overwrite|keep)} {--source=auto : 업데이트 소스 (auto|bundled|github) — bundled 는 _bundled 만 사용(GitHub 우회)} - {--zip= : 외부 ZIP 파일 경로 (지정 시 GitHub/번들 우회 + 버전은 plugin.json 기준)}'; + {--zip= : 외부 ZIP 파일 경로 (지정 시 GitHub/번들 우회 + 버전은 plugin.json 기준)} + {--rebuild-search-index : 완료 후 색인이 누락된 검색 인덱스를 재생성 (인덱스가 잠기거나 재색인됩니다 — 운영 중에는 유지보수 시간에 수행하세요)}'; /** * The console command description. @@ -202,6 +205,9 @@ class UpdatePluginCommand extends Command 'layout_strategy' => $layoutStrategy, ]); + // 검색 인덱스 재생성은 운영자가 선택했을 때만 수행한다 (인덱스 잠금·재색인 비용) + $this->handleSearchIndexRebuild(); + return Command::SUCCESS; } diff --git a/app/Console/Commands/Search/SearchIndexCommand.php b/app/Console/Commands/Search/SearchIndexCommand.php new file mode 100644 index 00000000..82e0dd10 --- /dev/null +++ b/app/Console/Commands/Search/SearchIndexCommand.php @@ -0,0 +1,194 @@ +driver(); + $filters = $this->parseFilters(); + + if (! $manager->hasMaintainer()) { + $this->components->warn(__('search.index.no_maintainer', ['driver' => $driver])); + + return self::SUCCESS; + } + + if (($reason = $manager->unavailableReason()) !== null) { + $this->components->warn($reason); + + return self::SUCCESS; + } + + $results = $manager->inspect($filters); + + if ($results === []) { + $this->components->warn(__('search.index.no_targets', ['driver' => $driver])); + + return self::SUCCESS; + } + + $report = null; + + if ($this->option('repair')) { + $targets = array_values(array_filter($results, fn (SearchIndexHealth $h) => $h->needsRebuild())); + + if ($targets !== [] && $this->confirmRebuild($targets)) { + $report = $manager->repairStale($filters); + $results = $manager->inspect($filters); + } + } + + if ($this->option('json')) { + $this->line((string) json_encode([ + 'driver' => $driver, + 'results' => array_map(fn (SearchIndexHealth $h) => $h->toArray(), $results), + 'repair' => $report?->toArray(), + ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); + } else { + $this->render($driver, $results, $report?->summary()); + } + + $remaining = array_filter($results, fn (SearchIndexHealth $h) => $h->needsRebuild()); + + return ($remaining === [] && ($report === null || $report->failed === [])) ? self::SUCCESS : self::FAILURE; + } + + /** + * `--filter=key=value` 옵션을 연관 배열로 해석합니다. + * + * @return array + */ + private function parseFilters(): array + { + $filters = []; + + foreach ((array) $this->option('filter') as $raw) { + if (! is_string($raw) || ! str_contains($raw, '=')) { + continue; + } + + [$key, $value] = explode('=', $raw, 2); + $key = trim($key); + + if ($key !== '') { + $filters[$key] = trim($value); + } + } + + return $filters; + } + + /** + * 재생성 대상을 보여 주고 진행 여부를 확인합니다. + * + * @param array $targets 재생성 대상 + * @return bool 진행 여부 + */ + private function confirmRebuild(array $targets): bool + { + $this->newLine(); + $this->components->info(__('search.index.rebuild_targets', ['count' => count($targets)])); + + foreach ($targets as $target) { + $this->line(' - '.$target->identifier.' ('.$target->measurement.')'); + } + + $this->components->warn(__('search.index.rebuild_cost_warning')); + + if ($this->unifiedConfirm(__('search.index.rebuild_confirm'), true)) { + return true; + } + + $this->components->info(__('search.index.rebuild_skipped')); + + return false; + } + + /** + * 판정 결과를 표로 출력합니다. + * + * @param string $driver 드라이버명 + * @param array $results 판정 결과 + * @param string|null $repairSummary 재생성 요약 (수행했을 때만) + * @return void + */ + private function render(string $driver, array $results, ?string $repairSummary): void + { + $rows = array_map(fn (SearchIndexHealth $health) => [ + $health->identifier, + 'status->consoleColor().'>'.$health->status->label().'', + $health->measurement, + ], $results); + + $this->newLine(); + $this->line(' '.__('search.index.driver_label', ['driver' => $driver])); + $this->table([__('search.index.col.index'), __('search.index.col.status'), __('search.index.col.measurement')], $rows); + + $counts = []; + foreach (SearchIndexStatus::cases() as $case) { + $counts[$case->value] = count(array_filter($results, fn (SearchIndexHealth $h) => $h->status === $case)); + } + + $this->line(' '.__('search.index.counts', [ + 'healthy' => $counts[SearchIndexStatus::Healthy->value], + 'degraded' => $counts[SearchIndexStatus::Degraded->value], + 'stale' => $counts[SearchIndexStatus::Stale->value], + 'skipped' => $counts[SearchIndexStatus::Skipped->value], + 'total' => count($results), + ])); + + if ($repairSummary !== null) { + $this->newLine(); + $this->components->info($repairSummary); + } + + if ($counts[SearchIndexStatus::Stale->value] > 0 && ! $this->option('repair')) { + $this->newLine(); + $this->components->warn(__('search.index.stale_hint')); + } + + if ($counts[SearchIndexStatus::Degraded->value] > 0) { + $this->line(' '.__('search.index.degraded_hint').''); + } + } +} diff --git a/app/Console/Commands/Template/CheckTemplateUpdatesCommand.php b/app/Console/Commands/Template/CheckTemplateUpdatesCommand.php index ea17ed92..de4f66bd 100644 --- a/app/Console/Commands/Template/CheckTemplateUpdatesCommand.php +++ b/app/Console/Commands/Template/CheckTemplateUpdatesCommand.php @@ -116,7 +116,7 @@ class CheckTemplateUpdatesCommand extends Command $updateCount = 0; foreach ($result['details'] as $detail) { - $isUpdate = $detail['update_available'] ?? false; + $isUpdate = (bool) ($detail['update_available'] ?? true); if ($isUpdate) { $updateCount++; } @@ -143,7 +143,7 @@ class CheckTemplateUpdatesCommand extends Command $this->table($headers, $tableData); $this->newLine(); $this->info(__('templates.commands.check_updates.summary', [ - 'total' => count($result['details']), + 'total' => $result['checked_count'] ?? count($result['details']), 'updates' => $updateCount, ])); diff --git a/app/Console/Commands/Traits/RebuildsSearchIndex.php b/app/Console/Commands/Traits/RebuildsSearchIndex.php new file mode 100644 index 00000000..1cee0e28 --- /dev/null +++ b/app/Console/Commands/Traits/RebuildsSearchIndex.php @@ -0,0 +1,114 @@ +hasMaintainer() || $manager->unavailableReason() !== null) { + return null; + } + + $requested ??= (bool) $this->option('rebuild-search-index'); + + if ($requested) { + $report = $manager->repairStale(); + $this->reportRebuild($report); + + return $report; + } + + $this->warnStale($manager->inspect()); + + return null; + } + + /** + * 재생성하지 않은 경우, 색인 누락 사실만 안내합니다. + * + * @param array $results 점검 결과 + * @return void + */ + private function warnStale(array $results): void + { + $stale = array_values(array_filter($results, fn (SearchIndexHealth $h) => $h->needsRebuild())); + + if ($stale === []) { + return; + } + + $this->newLine(); + $this->warn('⚠️ '.__('search.index.stale_after_update', ['count' => count($stale)])); + + foreach ($stale as $health) { + $this->line(' - '.$health->identifier); + } + + $this->line(' '.__('search.index.stale_hint')); + } + + /** + * 재생성 보고를 콘솔에 출력합니다. + * + * @param SearchIndexRepairReport $report 재생성 보고 + * @return void + */ + private function reportRebuild(SearchIndexRepairReport $report): void + { + $this->newLine(); + + if (! $report->didRebuild()) { + $this->info('✅ '.$report->summary()); + + return; + } + + $this->info('✅ '.$report->summary()); + + foreach ($report->repaired as $identifier) { + $this->line(' '.__('search.index.rebuilt_item', ['index' => $identifier])); + } + + foreach ($report->failed as $identifier => $message) { + $this->error(' '.__('search.index.rebuild_failed_item', ['index' => $identifier, 'error' => $message])); + } + + foreach ($report->remaining as $identifier) { + $this->warn(' '.__('search.index.still_stale_item', ['index' => $identifier])); + } + } +} diff --git a/app/Enums/SearchIndexStatus.php b/app/Enums/SearchIndexStatus.php new file mode 100644 index 00000000..b6b0cf55 --- /dev/null +++ b/app/Enums/SearchIndexStatus.php @@ -0,0 +1,59 @@ +value); + } + + /** + * 콘솔 출력용 색상명을 반환합니다. + * + * @return string Symfony 콘솔 색상명 + */ + public function consoleColor(): string + { + return match ($this) { + self::Healthy => 'green', + self::Degraded => 'yellow', + self::Stale => 'red', + self::Skipped => 'gray', + }; + } + + /** + * 모든 케이스의 string 값 목록. + * + * @return array + */ + public static function allValues(): array + { + return array_column(self::cases(), 'value'); + } +} diff --git a/app/Extension/ModuleManager.php b/app/Extension/ModuleManager.php index 1c7b2abd..3fb927dd 100644 --- a/app/Extension/ModuleManager.php +++ b/app/Extension/ModuleManager.php @@ -3971,9 +3971,11 @@ class ModuleManager implements ModuleManagerInterface $moduleRecords = $this->moduleRepository->getAllKeyedByIdentifier(); $details = []; $updatedCount = 0; + $checkedCount = 0; foreach ($moduleRecords as $identifier => $record) { $result = $this->checkModuleUpdate($identifier); + $checkedCount++; // DB 갱신 $updateData = [ @@ -3997,6 +3999,7 @@ class ModuleManager implements ModuleManagerInterface $updatedCount++; $details[] = [ 'identifier' => $identifier, + 'update_available' => true, 'current_version' => $result['current_version'], 'latest_version' => $result['latest_version'], 'update_source' => $result['update_source'], @@ -4006,6 +4009,7 @@ class ModuleManager implements ModuleManagerInterface return [ 'updated_count' => $updatedCount, + 'checked_count' => $checkedCount, 'details' => $details, ]; } diff --git a/app/Extension/PluginManager.php b/app/Extension/PluginManager.php index 3d8783a9..47e8272e 100644 --- a/app/Extension/PluginManager.php +++ b/app/Extension/PluginManager.php @@ -4153,9 +4153,11 @@ class PluginManager implements PluginManagerInterface $pluginRecords = $this->pluginRepository->getAllKeyedByIdentifier(); $details = []; $updatedCount = 0; + $checkedCount = 0; foreach ($pluginRecords as $identifier => $record) { $result = $this->checkPluginUpdate($identifier); + $checkedCount++; // DB 갱신 $updateData = [ @@ -4179,6 +4181,7 @@ class PluginManager implements PluginManagerInterface $updatedCount++; $details[] = [ 'identifier' => $identifier, + 'update_available' => true, 'current_version' => $result['current_version'], 'latest_version' => $result['latest_version'], 'update_source' => $result['update_source'], @@ -4188,6 +4191,7 @@ class PluginManager implements PluginManagerInterface return [ 'updated_count' => $updatedCount, + 'checked_count' => $checkedCount, 'details' => $details, ]; } diff --git a/app/Extension/TemplateManager.php b/app/Extension/TemplateManager.php index 42e2c3d0..15b2a6b2 100644 --- a/app/Extension/TemplateManager.php +++ b/app/Extension/TemplateManager.php @@ -2685,9 +2685,11 @@ class TemplateManager implements TemplateManagerInterface $templateRecords = $this->templateRepository->getAllKeyedByIdentifier(); $details = []; $updatedCount = 0; + $checkedCount = 0; foreach ($templateRecords as $identifier => $record) { $result = $this->checkTemplateUpdate($identifier); + $checkedCount++; $updateData = [ 'update_available' => $result['update_available'], @@ -2711,6 +2713,7 @@ class TemplateManager implements TemplateManagerInterface $updatedCount++; $details[] = [ 'identifier' => $identifier, + 'update_available' => true, 'current_version' => $result['current_version'], 'latest_version' => $result['latest_version'], 'update_source' => $result['update_source'], @@ -2720,6 +2723,7 @@ class TemplateManager implements TemplateManagerInterface return [ 'updated_count' => $updatedCount, + 'checked_count' => $checkedCount, 'details' => $details, ]; } diff --git a/app/Helpers/ResponseHelper.php b/app/Helpers/ResponseHelper.php index f83a3ea6..8c062723 100644 --- a/app/Helpers/ResponseHelper.php +++ b/app/Helpers/ResponseHelper.php @@ -335,6 +335,12 @@ class ResponseHelper /** * JSON Resource를 사용한 성공 응답을 생성합니다. * + * `$resource->additional([...])` 로 실은 부가 데이터는 응답 최상위에 함께 나갑니다. + * `JsonResource::resolve()` 는 리소스 본문만 돌려주므로, 그것만 쓰면 호출자가 실은 + * 부가 데이터가 조용히 사라집니다 — 예를 들어 확장 업데이트 응답의 `search_index` + * (색인 누락 안내)가 사라지면 검색이 0건을 돌려주는 사실을 운영자가 알 방법이 없습니다. + * `success`/`message`/`data` 키는 부가 데이터로 덮어쓰지 않습니다. + * * @param string $messageKey 멤시지 키 (기본값: 'messages.success') * @param JsonResource|ResourceCollection|null $resource JSON 리소스 * @param int $statusCode HTTP 상태 코드 (기본값: 200) @@ -351,11 +357,19 @@ class ResponseHelper ): JsonResponse { $data = $resource ? $resource->resolve() : null; - return response()->json([ + $payload = [ 'success' => true, 'message' => self::trans($messageKey, $messageParams, $domain), 'data' => $data, - ], $statusCode, [], self::JSON_ENCODE_OPTIONS); + ]; + + $additional = $resource instanceof JsonResource ? (array) $resource->additional : []; + + if ($additional !== []) { + $payload += array_diff_key($additional, $payload); + } + + return response()->json($payload, $statusCode, [], self::JSON_ENCODE_OPTIONS); } /** diff --git a/app/Helpers/TimezoneHelper.php b/app/Helpers/TimezoneHelper.php index b34a3bfc..7398a140 100644 --- a/app/Helpers/TimezoneHelper.php +++ b/app/Helpers/TimezoneHelper.php @@ -198,4 +198,31 @@ class TimezoneHelper return Carbon::parse($normalized, static::getSiteTimezone())->utc(); } + + /** + * 기간 필터의 **종료값**을 사이트 기본 타임존 기준으로 해석하여 UTC Carbon을 반환합니다. + * + * `` 는 시각 없이 'Y-m-d' 만 보낸다. 이 값을 그대로 datetime 비교에 쓰면 + * '00:00:00' 으로 해석되어 **종료일 당일이 통째로 빠진다**. 시각이 없는 입력만 그날 끝 + * (23:59:59)까지 확장하고, 시각이 함께 온 입력은 그 시각 그대로 둔다. + * + * 예: '2026-03-15' + Asia/Seoul → 2026-03-15 23:59:59 KST → 2026-03-15 14:59:59 UTC + * '2026-03-15 09:30' + Asia/Seoul → 2026-03-15 00:30:00 UTC (확장하지 않음) + * + * @param string|null $dateTimeString Y-m-d 또는 Y-m-d\TH:i / Y-m-d H:i:s 형식 + * @return Carbon|null UTC Carbon 인스턴스 + */ + public static function fromSiteRangeEnd(?string $dateTimeString): ?Carbon + { + if (! $dateTimeString) { + return null; + } + + $normalized = trim(str_replace('T', ' ', $dateTimeString)); + $dateOnly = preg_match('/^\d{4}-\d{2}-\d{2}$/', $normalized) === 1; + + $parsed = Carbon::parse($normalized, static::getSiteTimezone()); + + return ($dateOnly ? $parsed->endOfDay() : $parsed)->utc(); + } } diff --git a/app/Http/Controllers/Api/Admin/MenuController.php b/app/Http/Controllers/Api/Admin/MenuController.php index 2ea4e92f..1672b8b0 100644 --- a/app/Http/Controllers/Api/Admin/MenuController.php +++ b/app/Http/Controllers/Api/Admin/MenuController.php @@ -29,18 +29,22 @@ class MenuController extends AdminBaseController parent::__construct(); } + /** + * 관리용 메뉴 목록을 조회합니다. + * + * @param MenuListRequest $request 목록 필터·정렬 요청 + * @return JsonResponse 메뉴 컬렉션을 담은 JSON 응답 + */ public function index(MenuListRequest $request): JsonResponse { try { $filters = $request->validated(); $user = Auth::user(); - // 필터가 있으면 필터링된 메뉴 조회, 없으면 전체 조회 - if (isset($filters['is_active']) || ! empty($filters['filters'])) { - $menus = $this->menuService->getFilteredMenusForManagement($filters, $user); - } else { - $menus = $this->menuService->getTopLevelMenusForManagement($user); - } + // 필터 유무와 무관하게 같은 경로로 조회한다. 예전에는 필터가 없으면 filters 를 + // 받지 않는 메서드로 분기했는데, 그 경로는 sort_by/sort_order 를 통째로 버려 + // "필터를 하나라도 걸어야 정렬이 먹는" 상태가 됐다 (#492 D-22). + $menus = $this->menuService->getFilteredMenusForManagement($filters, $user); return $this->successWithResource( 'menu.fetch_success', diff --git a/app/Http/Controllers/Api/Admin/ModuleController.php b/app/Http/Controllers/Api/Admin/ModuleController.php index b1d95d8c..74ffb478 100644 --- a/app/Http/Controllers/Api/Admin/ModuleController.php +++ b/app/Http/Controllers/Api/Admin/ModuleController.php @@ -7,6 +7,7 @@ use App\Extension\Vendor\VendorMode; use App\Http\Controllers\Api\Base\AdminBaseController; use App\Http\Controllers\Concerns\InjectsExtensionLanguagePacks; use App\Http\Controllers\Concerns\OrchestratesCascadeInstall; +use App\Http\Controllers\Concerns\RebuildsSearchIndexOnDemand; use App\Http\Requests\Extension\ChangelogRequest; use App\Http\Requests\Module\ActivateModuleRequest; use App\Http\Requests\Module\DeactivateModuleRequest; @@ -38,6 +39,7 @@ class ModuleController extends AdminBaseController { use InjectsExtensionLanguagePacks; use OrchestratesCascadeInstall; + use RebuildsSearchIndexOnDemand; public function __construct( private ModuleService $moduleService, @@ -531,25 +533,36 @@ class ModuleController extends AdminBaseController $force = (bool) ($validated['force'] ?? false); $result = $this->moduleService->updateModule($moduleName, $vendorMode, $layoutStrategy, $force); + // 검색 인덱스 재생성은 운영자가 체크했을 때만 수행한다 — 인덱스 잠금·재색인 비용이 + // 있어 운영 중인 사이트에서 업데이트만으로 발생해서는 안 된다. + $searchIndex = $this->rebuildSearchIndexIfRequested( + (bool) ($validated['rebuild_search_index'] ?? false) + ); + $moduleInfo = $result['module_info'] ?? null; // 메시지 치환 파라미터를 반드시 전달한다 — 누락 시 ":module"/":version" // 플레이스홀더가 그대로 사용자에게 노출된다. $messageParams = [ 'module' => $moduleName, - 'version' => $result['to_version'] ?? ($moduleInfo['version'] ?? ''), + 'version' => (string) ($result['to_version'] ?? data_get($moduleInfo, 'version') ?? ''), ]; if ($moduleInfo) { return $this->successWithResource( 'modules.update_success', - new ModuleResource($moduleInfo), + (new ModuleResource($moduleInfo))->additional(['search_index' => $searchIndex]), 200, $messageParams ); } - return $this->success('modules.update_success', $result, 200, $messageParams); + return $this->success( + 'modules.update_success', + $result + ['search_index' => $searchIndex], + 200, + $messageParams + ); } catch (ValidationException $e) { // Service/Manager에서 이미 번역된 메시지를 errors에 포함하므로 // 첫 번째 에러를 top-level message로 직접 사용 (이중 래핑 방지) diff --git a/app/Http/Controllers/Api/Admin/PluginController.php b/app/Http/Controllers/Api/Admin/PluginController.php index a1d80464..0fcc883e 100644 --- a/app/Http/Controllers/Api/Admin/PluginController.php +++ b/app/Http/Controllers/Api/Admin/PluginController.php @@ -8,6 +8,7 @@ use App\Helpers\PermissionHelper; use App\Http\Controllers\Api\Base\AdminBaseController; use App\Http\Controllers\Concerns\InjectsExtensionLanguagePacks; use App\Http\Controllers\Concerns\OrchestratesCascadeInstall; +use App\Http\Controllers\Concerns\RebuildsSearchIndexOnDemand; use App\Http\Requests\Extension\ChangelogRequest; use App\Http\Requests\Plugin\ActivatePluginRequest; use App\Http\Requests\Plugin\DeactivatePluginRequest; @@ -39,6 +40,7 @@ class PluginController extends AdminBaseController { use InjectsExtensionLanguagePacks; use OrchestratesCascadeInstall; + use RebuildsSearchIndexOnDemand; public function __construct( private PluginService $pluginService, @@ -538,25 +540,35 @@ class PluginController extends AdminBaseController $force = (bool) ($validated['force'] ?? false); $result = $this->pluginService->updatePlugin($pluginName, $vendorMode, $layoutStrategy, $force); + // 검색 인덱스 재생성은 운영자가 체크했을 때만 수행한다 — 인덱스 잠금·재색인 비용이 + // 있어 운영 중인 사이트에서 업데이트만으로 발생해서는 안 된다. + $searchIndex = $this->rebuildSearchIndexIfRequested( + (bool) ($validated['rebuild_search_index'] ?? false) + ); + $pluginInfo = $result['plugin_info'] ?? null; - // 메시지 치환 파라미터를 반드시 전달한다 — 누락 시 ":plugin"/":version" - // 플레이스홀더가 그대로 사용자에게 노출된다. + // 성공 메시지는 ":plugin"/":version" 치환자를 쓰므로 값을 함께 넘긴다 $messageParams = [ 'plugin' => $pluginName, - 'version' => $result['to_version'] ?? ($pluginInfo['version'] ?? ''), + 'version' => (string) ($result['to_version'] ?? data_get($pluginInfo, 'version') ?? ''), ]; if ($pluginInfo) { return $this->successWithResource( 'plugins.update_success', - new PluginResource($pluginInfo), + (new PluginResource($pluginInfo))->additional(['search_index' => $searchIndex]), 200, $messageParams ); } - return $this->success('plugins.update_success', $result, 200, $messageParams); + return $this->success( + 'plugins.update_success', + $result + ['search_index' => $searchIndex], + 200, + $messageParams + ); } catch (ValidationException $e) { // Service/Manager에서 이미 번역된 메시지를 errors에 포함하므로 // 첫 번째 에러를 top-level message로 직접 사용 (이중 래핑 방지) diff --git a/app/Http/Controllers/Concerns/RebuildsSearchIndexOnDemand.php b/app/Http/Controllers/Concerns/RebuildsSearchIndexOnDemand.php new file mode 100644 index 00000000..98ae4497 --- /dev/null +++ b/app/Http/Controllers/Concerns/RebuildsSearchIndexOnDemand.php @@ -0,0 +1,46 @@ +|null 응답에 실을 페이로드 (점검 불가 엔진이면 null) + */ + protected function rebuildSearchIndexIfRequested(bool $requested): ?array + { + $manager = app(SearchIndexMaintenanceManager::class); + + // 점검을 제공하지 않는 엔진에서는 실을 정보가 없다 (검색 자체는 정상 동작) + if (! $manager->hasMaintainer() || $manager->unavailableReason() !== null) { + return null; + } + + if ($requested) { + return ['rebuilt' => true] + $manager->repairStale()->toArray(); + } + + $stale = array_values(array_filter($manager->inspect(), fn ($health) => $health->needsRebuild())); + + return [ + 'rebuilt' => false, + 'driver' => $manager->driver(), + 'stale' => array_map(fn ($health) => $health->identifier, $stale), + 'stale_count' => count($stale), + ]; + } +} diff --git a/app/Http/Middleware/StartApiSession.php b/app/Http/Middleware/StartApiSession.php index 6223191a..b43ac58a 100644 --- a/app/Http/Middleware/StartApiSession.php +++ b/app/Http/Middleware/StartApiSession.php @@ -3,9 +3,11 @@ namespace App\Http\Middleware; use Closure; +use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; +use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Http\Request; use Illuminate\Pipeline\Pipeline; -use Illuminate\Support\Facades\Log; +use Illuminate\Session\Middleware\StartSession; use Symfony\Component\HttpFoundation\Response; /** @@ -23,34 +25,16 @@ class StartApiSession /** * 요청을 처리합니다. * - * @param Request $request HTTP 요청 - * @param Closure $next 다음 미들웨어 + * @param Request $request HTTP 요청 + * @param Closure $next 다음 미들웨어 * @return Response HTTP 응답 */ public function handle(Request $request, Closure $next): Response { - Log::info('[StartApiSession] 미들웨어 시작', ['url' => $request->url()]); - return (new Pipeline(app())) ->send($request) ->through($this->sessionPipeline()) - ->then(function ($request) use ($next) { - Log::info('[StartApiSession] 세션 파이프라인 완료', [ - 'hasSession' => $request->hasSession(), - 'sessionStarted' => $request->hasSession() ? $request->session()->isStarted() : false, - 'sessionId' => $request->hasSession() ? $request->session()->getId() : null, - ]); - - $response = $next($request); - - Log::info('[StartApiSession] 컨트롤러 응답 후', [ - 'status' => $response->getStatusCode(), - 'cookies' => collect($response->headers->getCookies())->map(fn($c) => $c->getName())->all(), - 'webGuardCheck' => \Illuminate\Support\Facades\Auth::guard('web')->check(), - ]); - - return $response; - }); + ->then(fn ($request) => $next($request)); } /** @@ -63,9 +47,9 @@ class StartApiSession protected function sessionPipeline(): array { return [ - \Illuminate\Cookie\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, ]; } } diff --git a/app/Http/Requests/ActivityLog/ActivityLogIndexRequest.php b/app/Http/Requests/ActivityLog/ActivityLogIndexRequest.php index 243fdc43..8b5e82a3 100644 --- a/app/Http/Requests/ActivityLog/ActivityLogIndexRequest.php +++ b/app/Http/Requests/ActivityLog/ActivityLogIndexRequest.php @@ -4,6 +4,7 @@ namespace App\Http\Requests\ActivityLog; use App\Enums\ActivityLogType; use App\Extension\HookManager; +use App\Models\User; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -33,7 +34,7 @@ class ActivityLogIndexRequest extends FormRequest 'log_type' => ['nullable', 'array'], 'log_type.*' => ['string', Rule::in(ActivityLogType::values())], 'action' => ['nullable', 'string', 'max:100'], - 'user_id' => ['nullable', 'integer', Rule::exists('users', 'id')], + 'user_id' => ['nullable', 'integer', Rule::exists(User::class, 'id')], 'loggable_type' => ['nullable', 'string', 'max:255'], 'search' => ['nullable', 'string', 'max:255'], 'search_type' => ['nullable', 'string', Rule::in(['all', 'action', 'description', 'ip_address'])], @@ -41,6 +42,9 @@ class ActivityLogIndexRequest extends FormRequest 'date_from' => ['nullable', 'date'], 'date_to' => ['nullable', 'date', 'after_or_equal:date_from'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], + // 게이트는 화면이 제공하는 정렬만 연다. ActivityLogRepository::SORTABLE_COLUMNS 는 + // 이보다 넓게(id·user_id 포함) 두어, 확장이 이 규칙을 훅으로 넓혔을 때 저장소가 + // 조용히 기본 정렬로 되돌리지 않도록 한다 (service-repository.md "정렬 컬럼 화이트리스트"). 'sort_by' => ['nullable', 'string', Rule::in(['created_at', 'action', 'log_type'])], 'sort_order' => ['nullable', 'string', Rule::in(['asc', 'desc'])], ]; diff --git a/app/Http/Requests/Module/PerformModuleUpdateRequest.php b/app/Http/Requests/Module/PerformModuleUpdateRequest.php index 72dba137..f51e27a5 100644 --- a/app/Http/Requests/Module/PerformModuleUpdateRequest.php +++ b/app/Http/Requests/Module/PerformModuleUpdateRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests\Module; use App\Extension\HookManager; +use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; /** @@ -28,7 +29,7 @@ class PerformModuleUpdateRequest extends FormRequest /** * 요청에 적용할 검증 규칙 * - * @return array|string> + * @return array|string> */ public function rules(): array { @@ -37,6 +38,8 @@ class PerformModuleUpdateRequest extends FormRequest 'vendor_mode' => ['nullable', 'string', 'in:auto,composer,bundled'], // 코어 버전 비호환 강제 우회 플래그 'force' => ['nullable', 'boolean'], + // 업데이트 후 검색 인덱스 재생성 여부 — 인덱스 잠금·재색인 비용이 있어 기본은 미수행 + 'rebuild_search_index' => ['nullable', 'boolean'], ]; return HookManager::applyFilters('core.module.perform_update_validation_rules', $rules, $this); diff --git a/app/Http/Requests/NotificationLog/NotificationLogIndexRequest.php b/app/Http/Requests/NotificationLog/NotificationLogIndexRequest.php index c6a8de4f..f322ca67 100644 --- a/app/Http/Requests/NotificationLog/NotificationLogIndexRequest.php +++ b/app/Http/Requests/NotificationLog/NotificationLogIndexRequest.php @@ -36,7 +36,9 @@ class NotificationLogIndexRequest extends FormRequest 'extension_type' => ['nullable', 'string', 'in:core,module,plugin'], 'status' => ['nullable', 'string', Rule::enum(NotificationLogStatus::class)], 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], - 'sort_by' => ['nullable', 'string', 'in:id,channel,notification_type,status,sent_at,created_at'], + // 허용 집합은 NotificationLogRepository::SORTABLE_COLUMNS 와 동일해야 한다. + // 게이트가 더 좁으면 화면 정렬 셀렉트가 제공하는 옵션(수신자명순/제목순)이 422 로 막힌다. + 'sort_by' => ['nullable', 'string', 'in:id,channel,notification_type,status,sent_at,created_at,recipient_name,subject'], 'sort_order' => ['nullable', 'string', 'in:asc,desc'], ]; diff --git a/app/Http/Requests/Plugin/PerformPluginUpdateRequest.php b/app/Http/Requests/Plugin/PerformPluginUpdateRequest.php index 572ef5b4..df7ec969 100644 --- a/app/Http/Requests/Plugin/PerformPluginUpdateRequest.php +++ b/app/Http/Requests/Plugin/PerformPluginUpdateRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests\Plugin; use App\Extension\HookManager; +use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; /** @@ -28,7 +29,7 @@ class PerformPluginUpdateRequest extends FormRequest /** * 요청에 적용할 검증 규칙 * - * @return array|string> + * @return array|string> */ public function rules(): array { @@ -37,6 +38,8 @@ class PerformPluginUpdateRequest extends FormRequest 'vendor_mode' => ['nullable', 'string', 'in:auto,composer,bundled'], // 코어 버전 비호환 강제 우회 플래그 (위험 인지 후 사용자 명시 필요) 'force' => ['nullable', 'boolean'], + // 업데이트 후 검색 인덱스 재생성 여부 — 인덱스 잠금·재색인 비용이 있어 기본은 미수행 + 'rebuild_search_index' => ['nullable', 'boolean'], ]; return HookManager::applyFilters('core.plugin.perform_update_validation_rules', $rules, $this); diff --git a/app/Providers/LanguagePackServiceProvider.php b/app/Providers/LanguagePackServiceProvider.php index 3454ef02..985d3add 100644 --- a/app/Providers/LanguagePackServiceProvider.php +++ b/app/Providers/LanguagePackServiceProvider.php @@ -304,10 +304,7 @@ class LanguagePackServiceProvider extends ServiceProvider $syncFor = function (string $scope, string $identifier, ?array $info, string $relativeBase) use ($registrar) { $vendor = $this->resolveVendor($identifier, $info); $version = (string) ($info['version'] ?? '1.0.0'); - $langDir = $relativeBase.'/'.$identifier.'/lang'; - if (! File::isDirectory(base_path($langDir))) { - $langDir = $relativeBase.'/'.$identifier.'/resources/lang'; - } + $langDir = $this->resolveExtensionLangDir($scope, $identifier, $relativeBase); $registrar->syncFromExtension($scope, $identifier, $vendor, $version, $langDir); }; @@ -359,6 +356,38 @@ class LanguagePackServiceProvider extends ServiceProvider }); } + /** + * 확장의 백엔드 다국어 디렉토리를 반환합니다. + * + * **런타임이 실제로 로드하는 경로와 같아야 한다** (`TranslationServiceProvider`). + * 모듈은 `src/lang`, 플러그인은 `lang` 이며, 코어의 확장 설치 검증 + * (`ValidatesTranslationPath`)도 같은 규칙을 강제한다. 여기서 다른 경로를 잡으면 + * 언어팩에 등록된 키와 화면에 나오는 문구의 출처가 어긋난다. + * + * 존재하지 않으면 관례 경로들을 순서대로 확인해 실제 있는 디렉토리를 돌려준다 + * (제3자 확장이 다른 배치를 쓰는 경우 대비). + * + * @param string $scope 확장 스코프 (module|plugin|template) + * @param string $identifier 확장 식별자 + * @param string $relativeBase base_path 기준 상위 디렉토리 (modules|plugins|templates) + * @return string base_path 기준 상대 lang 디렉토리 + */ + private function resolveExtensionLangDir(string $scope, string $identifier, string $relativeBase): string + { + $primary = $scope === 'module' ? 'src/lang' : 'lang'; + $candidates = array_unique([$primary, 'lang', 'src/lang', 'resources/lang']); + + foreach ($candidates as $candidate) { + $dir = $relativeBase.'/'.$identifier.'/'.$candidate; + + if (File::isDirectory(base_path($dir))) { + return $dir; + } + } + + return $relativeBase.'/'.$identifier.'/'.$primary; + } + /** * 확장 manifest 또는 identifier 로부터 vendor 를 추출합니다. * diff --git a/app/Repositories/Concerns/PaginatesWithDeferredJoin.php b/app/Repositories/Concerns/PaginatesWithDeferredJoin.php index 90195eac..3d5f74a4 100644 --- a/app/Repositories/Concerns/PaginatesWithDeferredJoin.php +++ b/app/Repositories/Concerns/PaginatesWithDeferredJoin.php @@ -3,8 +3,10 @@ namespace App\Repositories\Concerns; use Closure; +use Illuminate\Contracts\Database\Query\Expression; use Illuminate\Contracts\Pagination\Paginator as PaginatorContract; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\Paginator; use Illuminate\Support\Collection; @@ -27,7 +29,10 @@ use Illuminate\Support\Collection; * * 호출 계약: * - `$query` 에는 **필터/where 만** 적용한다 (`orderBy`/`with`/`select` 미적용) - * - `whereHas` 는 결과 집합을 결정하므로 그대로 둔다 — `with` 만 inner 에서 제거된다 + * - `whereHas` 는 결과 집합을 결정하므로 그대로 둔다 — 제거되는 것은 `with`(eager load) 뿐이다 + * - 관계는 **반드시 `$relations` 인자로** 넘긴다. `setEagerLoads([])` 는 inner 뿐 아니라 + * **outer 에도** 적용되므로, `$query->with()` 만 하고 인자를 생략하면 관계가 로드되지 않는다. + * 이 실패는 예외도 쿼리 오류도 내지 않고 응답에서 관계 필드만 사라진다 * - 정렬은 닫힌 집합이어야 한다 (ResolvesSortSpec 참조). 인덱스 없는 컬럼 정렬은 inner 도 * 전체 스캔하므로 개선 폭이 보장되지 않는다 * - 키 컬럼은 정렬 마지막에 자동으로 덧붙는다 (동률 시 페이지 경계가 흔들리는 것을 방지) @@ -180,8 +185,12 @@ trait PaginatesWithDeferredJoin /** * 정렬 스펙을 쿼리에 적용합니다. * + * 컬럼이 상관 서브쿼리(Builder)면 `orderBy()` 가 서브 select 로 전개한다. 서브쿼리는 + * 원 행의 키에 상관되므로 inner/outer 어느 쪽에 적용해도 같은 순서를 만든다 — + * outer 에서는 이번 페이지 건수만큼만 실행된다. + * * @param Builder $query 대상 쿼리 - * @param array $sort 정렬 스펙 + * @param array $sort 정렬 스펙 */ private function applySortSpec(Builder $query, array $sort): void { @@ -196,9 +205,13 @@ trait PaginatesWithDeferredJoin * 정렬 컬럼에 중복이 많으면(상태/조회수/평점 등) 동률 행의 순서가 실행마다 달라져 페이지 * 경계에서 행이 중복되거나 누락된다. inner 와 outer 가 같은 순서를 갖도록 하는 전제이기도 하다. * - * @param array $sort 정렬 스펙 + * 정렬 컬럼은 문자열 외에 **상관 서브쿼리(Builder)** 나 Expression 도 올 수 있다 + * (관계 테이블 컬럼 기준 정렬 — `SortsByRelatedColumn`). 이 경우 키 컬럼일 수 없으므로 + * 전순서 보장을 위해 키 컬럼이 항상 덧붙는다. + * + * @param array $sort 정렬 스펙 * @param string $keyName 키 컬럼명 - * @return array 키 컬럼이 보장된 정렬 스펙 + * @return array 키 컬럼이 보장된 정렬 스펙 */ private function normalizeSortSpecForKey(array $sort, string $keyName): array { @@ -207,7 +220,7 @@ trait PaginatesWithDeferredJoin foreach ($sort as $spec) { $column = $spec['column'] ?? null; - if (! is_string($column) || $column === '') { + if (is_string($column) ? $column === '' : ! ($column instanceof Builder || $column instanceof QueryBuilder || $column instanceof Expression)) { continue; } @@ -219,6 +232,10 @@ trait PaginatesWithDeferredJoin foreach ($normalized as $spec) { // 이미 키 컬럼으로 정렬한다면 그것으로 전순서가 성립한다 + if (! is_string($spec['column'])) { + continue; + } + if ($spec['column'] === $keyName || str_ends_with($spec['column'], '.'.$keyName)) { return $normalized; } diff --git a/app/Repositories/Concerns/SortsByRelatedColumn.php b/app/Repositories/Concerns/SortsByRelatedColumn.php new file mode 100644 index 00000000..93552f3e --- /dev/null +++ b/app/Repositories/Concerns/SortsByRelatedColumn.php @@ -0,0 +1,111 @@ + $allowedColumns 원 테이블 허용 정렬 컬럼 + * @param array, foreign_key: string, column: string}> $relatedMap 정렬 키 → 관계 스펙 + * @param Model $parent 정렬 대상 원 모델 (상관 조건의 좌변) + * @param string $defaultColumn 기본 정렬 컬럼 + * @param string $defaultDirection 기본 정렬 방향 + * @param string $columnKey 정렬 컬럼이 담긴 필터 키 + * @param string $directionKey 정렬 방향이 담긴 필터 키 + * @return array 정렬 스펙 배열 + */ + protected function resolveSortSpecWithRelated( + array $filters, + array $allowedColumns, + array $relatedMap, + Model $parent, + string $defaultColumn, + string $defaultDirection = 'desc', + string $columnKey = 'sort_by', + string $directionKey = 'sort_order', + ): array { + $requested = $filters[$columnKey] ?? null; + $requested = is_string($requested) ? trim($requested) : ''; + + if ($requested === '' || ! isset($relatedMap[$requested])) { + return $this->resolveSortSpec( + $filters, + $allowedColumns, + $defaultColumn, + $defaultDirection, + columnKey: $columnKey, + directionKey: $directionKey, + ); + } + + $direction = $this->normalizeSortDirection($filters[$directionKey] ?? null, $defaultDirection); + + return [[ + 'column' => $this->relatedSortSubquery($relatedMap[$requested], $parent, $direction), + 'direction' => $direction, + ]]; + } + + /** + * 관계 테이블에서 정렬 기준값 한 건을 뽑는 상관 서브쿼리를 만듭니다. + * + * 정렬 방향과 같은 방향으로 관계 행을 정렬해 첫 건만 취한다 — + * `desc` 면 가장 늦은 값, `asc` 면 가장 이른 값이 기준이 된다. + * + * @param array{model: class-string, foreign_key: string, column: string} $spec 관계 스펙 + * @param Model $parent 원 모델 + * @param string $direction 정렬 방향 (asc|desc) + * @return Builder 상관 서브쿼리 + */ + protected function relatedSortSubquery(array $spec, Model $parent, string $direction): Builder + { + /** @var Model $related */ + $related = new $spec['model']; + + return $related->newQuery() + ->select($related->qualifyColumn($spec['column'])) + ->whereColumn($related->qualifyColumn($spec['foreign_key']), $parent->qualifyColumn($parent->getKeyName())) + ->orderBy($related->qualifyColumn($spec['column']), $direction) + ->limit(1); + } +} diff --git a/app/Repositories/IdentityVerificationLogRepository.php b/app/Repositories/IdentityVerificationLogRepository.php index 6e51bd4d..52c4c917 100644 --- a/app/Repositories/IdentityVerificationLogRepository.php +++ b/app/Repositories/IdentityVerificationLogRepository.php @@ -4,6 +4,7 @@ namespace App\Repositories; use App\Contracts\Repositories\IdentityVerificationLogRepositoryInterface; use App\Enums\IdentityVerificationStatus; +use App\Helpers\TimezoneHelper; use App\Models\IdentityPolicy; use App\Models\IdentityVerificationLog; use App\Repositories\Concerns\PaginatesWithDeferredJoin; @@ -195,12 +196,15 @@ class IdentityVerificationLogRepository implements IdentityVerificationLogReposi } } + // 기간 필터는 사이트 타임존 기준으로 해석한다 — created_at 은 UTC 로 저장되고 + // 화면은 사이트 타임존으로 보여주므로, 입력 문자열을 그대로 비교하면 하루가 어긋난다. + // 종료값은 시각 없이 날짜만 오는 경우(``) 그날 끝까지 포함한다. if (! empty($filters['date_from'])) { - $query->where('created_at', '>=', $filters['date_from']); + $query->where('created_at', '>=', TimezoneHelper::fromSiteDateTime($filters['date_from'])); } if (! empty($filters['date_to'])) { - $query->where('created_at', '<=', $filters['date_to']); + $query->where('created_at', '<=', TimezoneHelper::fromSiteRangeEnd($filters['date_to'])); } $sortBy = in_array($filters['sort_by'] ?? null, ['created_at', 'attempts'], true) diff --git a/app/Repositories/LanguagePackRepository.php b/app/Repositories/LanguagePackRepository.php index 3e29ff0e..338b1c34 100644 --- a/app/Repositories/LanguagePackRepository.php +++ b/app/Repositories/LanguagePackRepository.php @@ -195,7 +195,9 @@ class LanguagePackRepository implements LanguagePackRepositoryInterface return $query ->orderBy('scope') ->orderBy('target_identifier') - ->orderBy('locale'); + ->orderBy('locale') + // 전순서 보장 — 위 세 컬럼 조합이 유니크하더라도 정렬 계약을 키로 닫아 둔다 + ->orderBy('id'); } /** diff --git a/app/Repositories/MenuRepository.php b/app/Repositories/MenuRepository.php index f98854ed..843c32ad 100644 --- a/app/Repositories/MenuRepository.php +++ b/app/Repositories/MenuRepository.php @@ -21,6 +21,13 @@ class MenuRepository implements MenuRepositoryInterface /** 허용 정렬 컬럼 (MenuListRequest 와 동일 집합) */ private const SORTABLE_COLUMNS = ['created_at', 'name', 'slug', 'order']; + /** + * 다국어 JSON 컬럼 목록 (검색·정렬 시 로케일 경로로 풀어야 하는 컬럼) + * + * @var array + */ + private const TRANSLATABLE_COLUMNS = ['name']; + /** * 모든 메뉴를 조회합니다. * @@ -434,19 +441,19 @@ class MenuRepository implements MenuRepositoryInterface $query->where(function ($q) use ($searchableFields, $value, $operator) { foreach ($searchableFields as $searchField) { $q->orWhere(function ($subQ) use ($searchField, $value, $operator) { - $this->applyFilterOperator($subQ, $searchField, $value, $operator); + $this->applySearchableField($subQ, $searchField, $value, $operator); }); } }); } elseif (in_array($field, $searchableFields)) { - $this->applyFilterOperator($query, $field, $value, $operator); + $this->applySearchableField($query, $field, $value, $operator); } } } // 정렬 (허용 컬럼 화이트리스트로 해석) foreach ($this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'order', 'asc') as $sort) { - $query->orderBy($sort['column'], $sort['direction']); + $query->orderBy($this->qualifySortColumn($sort['column']), $sort['direction']); } // 자식 메뉴도 동일한 조건 적용하여 로드 @@ -475,6 +482,69 @@ class MenuRepository implements MenuRepositoryInterface * @param string $value 검색 값 * @param string $operator 연산자 (like, eq, starts_with, ends_with) */ + /** + * 검색 대상 필드에 조건을 적용합니다. (다국어 JSON 컬럼 인지) + * + * `name` 은 다국어 JSON 컬럼이다. JSON 은 유니코드 이스케이프(`\uXXXX`)로 저장되므로 + * 컬럼 전체에 LIKE 를 걸면 한글·일본어 검색어가 **절대 매칭되지 않는다** + * (저장된 바이트는 `{"ko":"대시보드"}` 이다). + * 지원 로케일별 JSON 경로(`name->ko`)로 나눠 검색한다 — 저장소 공통 관례다. + * + * @param Builder $query 쿼리 빌더 + * @param string $field 필드명 + * @param string $value 검색 값 + * @param string $operator 연산자 (like, eq, starts_with, ends_with) + */ + private function applySearchableField($query, string $field, string $value, string $operator): void + { + if (! in_array($field, self::TRANSLATABLE_COLUMNS, true)) { + $this->applyFilterOperator($query, $field, $value, $operator); + + return; + } + + $query->where(function ($q) use ($field, $value, $operator) { + foreach ($this->translatableLocales() as $locale) { + $q->orWhere(function ($subQ) use ($field, $locale, $value, $operator) { + $this->applyFilterOperator($subQ, "{$field}->{$locale}", $value, $operator); + }); + } + }); + } + + /** + * 정렬 컬럼이 다국어 JSON 컬럼이면 현재 로케일 경로로 바꿉니다. + * + * JSON 원문으로 정렬하면 `{"ko":"\uXXXX...` 문자열 순서가 되어 사람이 읽는 순서와 + * 무관해진다. 화면에 보이는 값(현재 로케일)으로 정렬해야 목록 순서가 납득 가능해진다. + * + * @param string $column 정렬 컬럼 + * @return string 실제 정렬에 쓸 컬럼 표현 + */ + private function qualifySortColumn(string $column): string + { + if (! in_array($column, self::TRANSLATABLE_COLUMNS, true)) { + return $column; + } + + return $column.'->'.app()->getLocale(); + } + + /** + * 다국어 필드가 값을 가질 수 있는 로케일 목록을 반환합니다. + * + * 다국어 JSON 필드의 언어 집합은 `translatable_locales` 다 — 번역 파일이 없어도 + * 데이터는 저장될 수 있으므로 UI 표시 언어(`supported_locales`)보다 넓다. + * + * @return array 로케일 코드 목록 + */ + private function translatableLocales(): array + { + $locales = config('app.translatable_locales', config('app.supported_locales', [])); + + return empty($locales) ? [app()->getLocale()] : array_values($locales); + } + private function applyFilterOperator($query, string $field, string $value, string $operator): void { switch ($operator) { diff --git a/app/Repositories/NotificationLogRepository.php b/app/Repositories/NotificationLogRepository.php index 6dcb1edd..735b8fb5 100644 --- a/app/Repositories/NotificationLogRepository.php +++ b/app/Repositories/NotificationLogRepository.php @@ -33,6 +33,7 @@ class NotificationLogRepository implements NotificationLogRepositoryInterface 'channel', 'notification_type', 'recipient_name', + 'subject', ]; /** diff --git a/app/Repositories/NotificationRepository.php b/app/Repositories/NotificationRepository.php index 50998432..b7e102aa 100644 --- a/app/Repositories/NotificationRepository.php +++ b/app/Repositories/NotificationRepository.php @@ -30,7 +30,9 @@ class NotificationRepository implements NotificationRepositoryInterface // audit:allow repository-paginate-column-pruning reason: 사용자 1명에 종속된 알림함 — // $user->notifications() 로 이미 좁혀지고, 목록이 표시하는 본문 자체가 data(JSON) 라 // 걷어낼 넓은 컬럼이 없다 - return $query->orderBy('created_at', 'desc')->paginate($perPage); + // 정렬 마지막의 기본키는 전순서 보장용이다 — created_at 동률에서 페이지 경계가 + // 흔들려 인접 페이지가 같은 알림을 중복 노출하고 다른 알림을 누락하는 것을 막는다. + return $query->orderBy('created_at', 'desc')->orderBy('id', 'desc')->paginate($perPage); } /** diff --git a/app/Repositories/RoleRepository.php b/app/Repositories/RoleRepository.php index 782fa7dc..325dbd07 100644 --- a/app/Repositories/RoleRepository.php +++ b/app/Repositories/RoleRepository.php @@ -215,8 +215,12 @@ class RoleRepository implements RoleRepositoryInterface } // 정렬 (코어/확장 소유 역할 먼저, 그 다음 사용자 생성 역할) + // 정렬 마지막의 기본키는 전순서 보장용이다. 시더가 일괄 생성한 역할은 created_at 이 + // 한 타임스탬프에 몰려 있어(실측: 한 값에 최대 10행) 키가 없으면 동률 구간의 순서가 + // 페이지마다 달라지고, 인접 페이지가 같은 역할을 중복 노출하면서 다른 역할을 누락한다. $query->orderByRaw('CASE WHEN extension_type IS NOT NULL THEN 0 ELSE 1 END') - ->orderBy('created_at', 'desc'); + ->orderBy('created_at', 'desc') + ->orderBy('id', 'desc'); // audit:allow repository-paginate-column-pruning reason: 역할 정의 테이블 — 행 수가 운영상 고정(수십 건)이고 넓은 컬럼이 없다 return $query->paginate($perPage); diff --git a/app/Repositories/ScheduleHistoryRepository.php b/app/Repositories/ScheduleHistoryRepository.php index 66379a82..f2e858cb 100644 --- a/app/Repositories/ScheduleHistoryRepository.php +++ b/app/Repositories/ScheduleHistoryRepository.php @@ -6,6 +6,7 @@ use App\Contracts\Repositories\ScheduleHistoryRepositoryInterface; use App\Models\ScheduleHistory; use App\Repositories\Concerns\PaginatesWithDeferredJoin; use App\Repositories\Concerns\ResolvesSortSpec; +use Carbon\Carbon; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; @@ -68,7 +69,11 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface */ public function getPaginatedByScheduleId(int $scheduleId, array $filters = []): LengthAwarePaginator { - $query = ScheduleHistory::with('triggeredBy') + // 관계는 쿼리에 미리 붙이지 않는다 — 지연 조인 트레이트가 inner/outer 양쪽에서 + // setEagerLoads([]) 로 지우므로, 여기서 with() 하면 outer 에서도 사라져 + // ScheduleHistoryResource 의 whenLoaded('triggeredBy') 가 항상 비게 된다. + // 로드는 relations: 인자로 넘긴다. + $query = ScheduleHistory::query() ->where('schedule_id', $scheduleId); // 상태 필터 @@ -81,13 +86,15 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface $query->where('trigger_type', $filters['trigger_type']); } - // 날짜 필터 + // 기간 필터 — whereDate 는 컬럼에 DATE() 를 씌워 인덱스를 못 쓰게 만든다. + // 같은 결과를 내는 범위 조건으로 바꿔 started_at 인덱스를 살린다 + // (종료일은 그날 23:59:59.999999 까지 포함해야 whereDate 와 동일한 경계를 갖는다). if (! empty($filters['started_from'])) { - $query->whereDate('started_at', '>=', $filters['started_from']); + $query->where('started_at', '>=', Carbon::parse($filters['started_from'])->startOfDay()); } if (! empty($filters['started_to'])) { - $query->whereDate('started_at', '<=', $filters['started_to']); + $query->where('started_at', '<=', Carbon::parse($filters['started_to'])->endOfDay()); } // 정렬 — 요청 값은 허용 목록으로만 해석한다. @@ -108,6 +115,7 @@ class ScheduleHistoryRepository implements ScheduleHistoryRepositoryInterface columns: ['*'], sort: $sort, perPage: $perPage, + relations: ['triggeredBy'], ); } diff --git a/app/Search/Contracts/SearchIndexMaintainer.php b/app/Search/Contracts/SearchIndexMaintainer.php new file mode 100644 index 00000000..60f98e84 --- /dev/null +++ b/app/Search/Contracts/SearchIndexMaintainer.php @@ -0,0 +1,78 @@ + 구현 클래스` 로 합니다. + * + * ```php + * HookManager::addFilter('core.search.index_maintainers', function (array $maintainers) { + * $maintainers['meilisearch'] = MeilisearchIndexMaintainer::class; + * + * return $maintainers; + * }); + * ``` + * + * 구현하지 않은 엔진은 점검 대상에서 제외될 뿐 검색 자체는 정상 동작합니다. + */ +interface SearchIndexMaintainer +{ + /** + * 이 유지보수기가 담당하는 Scout 드라이버명을 반환합니다. + * + * `config('scout.driver')` 값과 대조해 활성 엔진의 유지보수기를 고릅니다. + * + * @return string 드라이버명 (예: mysql-fulltext) + */ + public function driver(): string; + + /** + * 현재 환경에서 점검을 수행할 수 있는지 반환합니다. + * + * 예) FULLTEXT 미지원 DBMS, 외부 검색 서버 미연결 등에서 false. + * + * @return bool 점검 가능 여부 + */ + public function isAvailable(): bool; + + /** + * 점검 불가 사유를 반환합니다. + * + * `isAvailable()` 이 false 일 때 운영자에게 보여 줄 문장입니다. + * 사유를 남기지 않으면 "점검 대상 0" 과 "점검할 수 없었음" 이 구분되지 않습니다. + * + * @return string|null 사유 (사용 가능하면 null) + */ + public function unavailableReason(): ?string; + + /** + * 관리 중인 인덱스의 건강도를 판정합니다. + * + * @param array $filters 엔진별 필터 (예: ['table' => 'pages']) + * @return array 인덱스별 판정 결과 + */ + public function inspect(array $filters = []): array; + + /** + * 재생성이 필요하다고 판정된 인덱스를 재생성합니다. + * + * 재생성은 비용이 크고 서비스에 영향을 줄 수 있으므로, 호출자는 반드시 + * 운영자의 명시적 선택을 받은 뒤에만 호출합니다. + * + * @param SearchIndexHealth $health 재생성 대상 판정 결과 + * @return void + * + * @throws \Throwable 재생성 실패 시 + */ + public function rebuild(SearchIndexHealth $health): void; +} diff --git a/app/Search/DTO/FulltextIndexDefinition.php b/app/Search/DTO/FulltextIndexDefinition.php new file mode 100644 index 00000000..dd1f50d3 --- /dev/null +++ b/app/Search/DTO/FulltextIndexDefinition.php @@ -0,0 +1,65 @@ + $columns 색인 컬럼 목록 + * @param string|null $parser 파서명 (예: ngram). 지정되지 않은 인덱스는 null + */ + public function __construct( + public string $table, + public string $name, + public array $columns, + public ?string $parser = null, + ) {} + + /** + * 백틱을 두른 테이블명을 반환합니다. + * + * @return string 예: `g7_pages` + */ + public function quotedTable(): string + { + return '`'.$this->table.'`'; + } + + /** + * 백틱을 두른 컬럼 목록을 반환합니다. + * + * @return string 예: `title`, `content` + */ + public function quotedColumns(): string + { + return '`'.implode('`, `', $this->columns).'`'; + } + + /** + * `ALTER TABLE ... ADD FULLTEXT` 에 붙일 파서 절을 반환합니다. + * + * @return string 파서가 없으면 빈 문자열 + */ + public function parserClause(): string + { + return $this->parser === null ? '' : ' WITH PARSER '.$this->parser; + } + + /** + * 사람이 읽는 식별자를 반환합니다. + * + * @return string 예: g7_pages.ft_pages_title + */ + public function label(): string + { + return $this->table.'.'.$this->name; + } +} diff --git a/app/Search/DTO/SearchIndexHealth.php b/app/Search/DTO/SearchIndexHealth.php new file mode 100644 index 00000000..cfbc203d --- /dev/null +++ b/app/Search/DTO/SearchIndexHealth.php @@ -0,0 +1,67 @@ + $details 엔진별 부가 정보 (표시 전용) + * @param array $context 재생성에 필요한 엔진 내부 정보 + */ + public function __construct( + public string $driver, + public string $identifier, + public SearchIndexStatus $status, + public string $measurement = '', + public array $details = [], + public array $context = [], + ) {} + + /** + * 재생성 대상인지 여부. + * + * @return bool + */ + public function needsRebuild(): bool + { + return $this->status === SearchIndexStatus::Stale; + } + + /** + * 기계 판독용 배열로 반환합니다. + * + * `context` 는 엔진 내부 정보라 응답에 싣지 않습니다. + * + * @return array + */ + public function toArray(): array + { + return [ + 'driver' => $this->driver, + 'identifier' => $this->identifier, + 'status' => $this->status->value, + 'status_label' => $this->status->label(), + 'measurement' => $this->measurement, + 'details' => $this->details, + ]; + } +} diff --git a/app/Search/DTO/SearchIndexRepairReport.php b/app/Search/DTO/SearchIndexRepairReport.php new file mode 100644 index 00000000..c3020976 --- /dev/null +++ b/app/Search/DTO/SearchIndexRepairReport.php @@ -0,0 +1,119 @@ + $repaired 재생성 성공 인덱스 식별자 + * @param array $failed 재생성 실패 [식별자 => 사유] + * @param array $remaining 재생성 후에도 색인 누락으로 남은 식별자 + * @param bool $available 이 엔진에서 점검을 수행할 수 있었는지 + * @param string|null $reason 수행 불가 사유 (available=false 일 때) + */ + public function __construct( + public string $driver, + public int $inspected = 0, + public array $repaired = [], + public array $failed = [], + public array $remaining = [], + public bool $available = true, + public ?string $reason = null, + ) {} + + /** + * 점검을 수행할 수 없었던 경우의 보고를 만듭니다. + * + * @param string $driver 드라이버명 + * @param string|null $reason 사유 + * @return self + */ + public static function unavailable(string $driver, ?string $reason): self + { + return new self(driver: $driver, available: false, reason: $reason); + } + + /** + * 재생성 대상이 없었을 때의 보고를 만듭니다. + * + * @param string $driver 드라이버명 + * @param int $inspected 점검한 인덱스 수 + * @return self + */ + public static function nothingToDo(string $driver, int $inspected): self + { + return new self(driver: $driver, inspected: $inspected); + } + + /** + * 재생성을 실제로 수행했는지 여부. + * + * @return bool + */ + public function didRebuild(): bool + { + return $this->repaired !== [] || $this->failed !== []; + } + + /** + * 전부 복구되었는지 여부 (실패·잔존 모두 없음). + * + * @return bool + */ + public function isClean(): bool + { + return $this->failed === [] && $this->remaining === []; + } + + /** + * 한 줄 요약을 반환합니다. + * + * @return string + */ + public function summary(): string + { + if (! $this->available) { + return $this->reason ?? __('search.index.unavailable', ['driver' => $this->driver]); + } + + if (! $this->didRebuild()) { + return __('search.index.report.nothing_to_do', ['count' => $this->inspected]); + } + + return __('search.index.report.rebuilt', [ + 'inspected' => $this->inspected, + 'repaired' => count($this->repaired), + 'failed' => count($this->failed), + 'remaining' => count($this->remaining), + ]); + } + + /** + * 기계 판독용 배열로 반환합니다. + * + * @return array + */ + public function toArray(): array + { + return [ + 'driver' => $this->driver, + 'available' => $this->available, + 'reason' => $this->reason, + 'inspected' => $this->inspected, + 'repaired' => $this->repaired, + 'failed' => $this->failed, + 'remaining' => $this->remaining, + 'summary' => $this->summary(), + ]; + } +} diff --git a/app/Search/Engines/Maintenance/FulltextIndexMaintainer.php b/app/Search/Engines/Maintenance/FulltextIndexMaintainer.php new file mode 100644 index 00000000..9624ade9 --- /dev/null +++ b/app/Search/Engines/Maintenance/FulltextIndexMaintainer.php @@ -0,0 +1,127 @@ +isAvailable() + ? null + : __('search.index.fulltext.unsupported_driver'); + } + + /** + * {@inheritDoc} + * + * 지원 필터: `table`(테이블명, 프리픽스 생략 가능) · `index`(인덱스명) · `samples`(표본 행 수) + */ + public function inspect(array $filters = []): array + { + $sampleSize = (int) ($filters['samples'] ?? FulltextIndexInspector::DEFAULT_SAMPLE_SIZE); + + $results = $this->inspector->inspectAll( + table: isset($filters['table']) ? (string) $filters['table'] : null, + index: isset($filters['index']) ? (string) $filters['index'] : null, + sampleSize: max(1, $sampleSize), + ); + + return array_map(fn (array $result) => $this->toHealth($result), $results); + } + + /** + * {@inheritDoc} + */ + public function rebuild(SearchIndexHealth $health): void + { + $context = $health->context; + + $this->inspector->rebuild(new FulltextIndexDefinition( + table: (string) ($context['table'] ?? ''), + name: (string) ($context['name'] ?? ''), + columns: (array) ($context['columns'] ?? []), + parser: isset($context['parser']) ? (string) $context['parser'] : null, + )); + } + + /** + * 점검기 결과를 엔진 중립 판정 DTO 로 옮깁니다. + * + * @param array{definition: FulltextIndexDefinition, status: SearchIndexStatus, probed: int, found: int, reason: ?string} $result 점검기 결과 + * @return SearchIndexHealth + */ + private function toHealth(array $result): SearchIndexHealth + { + $definition = $result['definition']; + $status = $result['status']; + + $measurement = $status === SearchIndexStatus::Skipped + ? (string) $result['reason'] + : __('search.index.fulltext.self_match', ['found' => $result['found'], 'probed' => $result['probed']]); + + return new SearchIndexHealth( + driver: self::DRIVER, + identifier: $definition->label(), + status: $status, + measurement: $measurement, + details: [ + 'table' => $definition->table, + 'index' => $definition->name, + 'columns' => $definition->columns, + 'parser' => $definition->parser, + 'probed' => $result['probed'], + 'found' => $result['found'], + ], + context: [ + 'table' => $definition->table, + 'name' => $definition->name, + 'columns' => $definition->columns, + 'parser' => $definition->parser, + ], + ); + } +} diff --git a/app/Search/FulltextIndexInspector.php b/app/Search/FulltextIndexInspector.php new file mode 100644 index 00000000..b62a48ad --- /dev/null +++ b/app/Search/FulltextIndexInspector.php @@ -0,0 +1,367 @@ + + */ + public function discover(?string $table = null, ?string $index = null): array + { + if (! DatabaseFulltextEngine::supportsFulltext()) { + return []; + } + + $rows = DB::select( + 'SELECT TABLE_NAME AS table_name, INDEX_NAME AS index_name, + GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS columns + FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND INDEX_TYPE = ? + GROUP BY TABLE_NAME, INDEX_NAME + ORDER BY TABLE_NAME, INDEX_NAME', + ['FULLTEXT'] + ); + + $prefix = DB::getTablePrefix(); + $wanted = $table === null ? null : [$table, $prefix.$table]; + + $definitions = []; + foreach ($rows as $row) { + if ($wanted !== null && ! in_array($row->table_name, $wanted, true)) { + continue; + } + if ($index !== null && $row->index_name !== $index) { + continue; + } + + $definitions[] = new FulltextIndexDefinition( + table: $row->table_name, + name: $row->index_name, + columns: explode(',', (string) $row->columns), + parser: $this->resolveParser($row->table_name, $row->index_name), + ); + } + + return $definitions; + } + + /** + * 인덱스 하나의 건강도를 판정합니다. + * + * 엔진 중립 DTO 로의 변환은 호출자(`FulltextIndexMaintainer`)가 합니다 — 이 클래스는 + * FULLTEXT 고유의 판정만 담당하고 표현 형식을 알지 않습니다. + * + * @param FulltextIndexDefinition $definition 인덱스 정의 + * @param int $sampleSize 표본 행 수 + * @return array{definition: FulltextIndexDefinition, status: SearchIndexStatus, probed: int, found: int, reason: string|null} + */ + public function inspect(FulltextIndexDefinition $definition, int $sampleSize = self::DEFAULT_SAMPLE_SIZE): array + { + $key = $this->resolvePrimaryKey($definition->table); + + if ($key === null) { + return $this->skipped($definition, __('search.index.fulltext.skip.no_single_pk')); + } + + $samples = $this->sampleRows($definition, $key, $sampleSize); + + if ($samples === []) { + return $this->skipped($definition, __('search.index.fulltext.skip.no_rows')); + } + + $probed = 0; + $found = 0; + + foreach ($samples as $sample) { + $tokens = $this->buildTokens((string) $sample->body); + + if ($tokens === []) { + continue; + } + + $probed++; + + if ($this->rowFindsItself($definition, $key, $sample->pk, $tokens)) { + $found++; + } + } + + if ($probed === 0) { + return $this->skipped($definition, __('search.index.fulltext.skip.no_tokens')); + } + + return [ + 'definition' => $definition, + 'status' => match (true) { + $found === 0 => SearchIndexStatus::Stale, + $found < $probed => SearchIndexStatus::Degraded, + default => SearchIndexStatus::Healthy, + }, + 'probed' => $probed, + 'found' => $found, + 'reason' => null, + ]; + } + + /** + * 전체(또는 필터된) 인덱스의 건강도를 판정합니다. + * + * @param string|null $table 테이블명 필터 + * @param string|null $index 인덱스명 필터 + * @param int $sampleSize 표본 행 수 + * @return array + */ + public function inspectAll(?string $table = null, ?string $index = null, int $sampleSize = self::DEFAULT_SAMPLE_SIZE): array + { + return array_map( + fn (FulltextIndexDefinition $definition) => $this->inspect($definition, $sampleSize), + $this->discover($table, $index) + ); + } + + /** + * 판정 불가 결과를 만듭니다. + * + * @param FulltextIndexDefinition $definition 인덱스 정의 + * @param string $reason 사유 + * @return array{definition: FulltextIndexDefinition, status: SearchIndexStatus, probed: int, found: int, reason: string} + */ + private function skipped(FulltextIndexDefinition $definition, string $reason): array + { + return [ + 'definition' => $definition, + 'status' => SearchIndexStatus::Skipped, + 'probed' => 0, + 'found' => 0, + 'reason' => $reason, + ]; + } + + /** + * 인덱스를 같은 정의로 재생성합니다. + * + * 컬럼 구성과 파서를 그대로 유지하므로 검색 동작이 달라지지 않습니다. + * 재생성 중에는 대상 테이블이 잠기므로 대용량 테이블에서는 시간이 걸립니다. + * + * @param FulltextIndexDefinition $definition 인덱스 정의 + * @return void + * + * @throws Throwable 재생성 실패 시 (드롭까지만 되고 끝나지 않도록 예외를 그대로 전파) + */ + public function rebuild(FulltextIndexDefinition $definition): void + { + $table = $definition->quotedTable(); + $name = $definition->name; + + DB::statement("ALTER TABLE {$table} DROP INDEX `{$name}`"); + + try { + DB::statement( + "ALTER TABLE {$table} ADD FULLTEXT INDEX `{$name}` ({$definition->quotedColumns()})" + .$definition->parserClause() + ); + } catch (Throwable $e) { + // 드롭만 되고 끝나면 검색이 아예 죽는다 — 원 정의로 되돌린 뒤 예외를 전파한다 + $this->restoreQuietly($definition); + + throw $e; + } + } + + /** + * 인덱스의 파서를 조회합니다. + * + * `INFORMATION_SCHEMA` 는 파서를 노출하지 않으므로 `SHOW CREATE TABLE` 정의에서 읽습니다. + * + * @param string $table 테이블명 (프리픽스 포함) + * @param string $index 인덱스명 + * @return string|null 파서명 (없으면 null) + */ + private function resolveParser(string $table, string $index): ?string + { + try { + $create = DB::selectOne('SHOW CREATE TABLE `'.$table.'`'); + } catch (Throwable) { + return null; + } + + $sql = (array) $create; + $ddl = (string) (($sql['Create Table'] ?? $sql['Create View'] ?? '')); + + $pattern = '/FULLTEXT\s+KEY\s+`'.preg_quote($index, '/').'`[^\n]*?WITH\s+PARSER\s+`?(\w+)`?/i'; + + return preg_match($pattern, $ddl, $m) === 1 ? $m[1] : null; + } + + /** + * 테이블의 단일 컬럼 기본키를 조회합니다. + * + * @param string $table 테이블명 (프리픽스 포함) + * @return string|null 기본키 컬럼명 (복합키·부재 시 null) + */ + private function resolvePrimaryKey(string $table): ?string + { + $rows = DB::select( + 'SELECT COLUMN_NAME AS column_name FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND INDEX_NAME = ? + ORDER BY SEQ_IN_INDEX', + [$table, 'PRIMARY'] + ); + + return count($rows) === 1 ? (string) $rows[0]->column_name : null; + } + + /** + * 색인 컬럼에 내용이 있는 표본 행을 뽑습니다. + * + * @param FulltextIndexDefinition $definition 인덱스 정의 + * @param string $key 기본키 컬럼명 + * @param int $sampleSize 표본 행 수 + * @return array `pk`, `body` 를 가진 행 배열 + */ + private function sampleRows(FulltextIndexDefinition $definition, string $key, int $sampleSize): array + { + $body = "CONCAT_WS(' ', {$definition->quotedColumns()})"; + + return DB::select( + "SELECT `{$key}` AS pk, {$body} AS body + FROM {$definition->quotedTable()} + WHERE {$body} <> '' AND {$body} IS NOT NULL + ORDER BY `{$key}` DESC + LIMIT ".max(1, $sampleSize) + ); + } + + /** + * 행 내용에서 검색 토큰 후보를 만듭니다. + * + * 비-라틴(한글·한자 등) 조각을 먼저 담습니다 — 기본 불용어 목록이 영어라 + * 라틴 토큰보다 토크나이저 영향을 덜 받습니다. + * + * @param string $text 행 내용 + * @return array 토큰 후보 목록 + */ + private function buildTokens(string $text): array + { + // 부울 모드 연산자(+ - > < ( ) ~ * " @)와 구분자를 제거한다 + $normalized = preg_replace('/[^\p{L}\p{N}]+/u', ' ', $text) ?? ''; + $pieces = array_values(array_filter( + explode(' ', $normalized), + fn (string $piece) => mb_strlen($piece) >= 2 + )); + + if ($pieces === []) { + return []; + } + + // 길이 내림차순 — 짧은 조각일수록 불용어·잡음일 확률이 높다 + usort($pieces, fn (string $a, string $b) => mb_strlen($b) <=> mb_strlen($a)); + + $nonLatin = []; + $latin = []; + + foreach ($pieces as $piece) { + $token = mb_substr($piece, 0, self::MAX_TOKEN_LENGTH); + + if (preg_match('/^[\x00-\x7F]+$/', $token) === 1) { + $latin[] = $token; + } else { + $nonLatin[] = $token; + } + } + + return array_slice(array_values(array_unique([...$nonLatin, ...$latin])), 0, self::MAX_TOKENS_PER_ROW); + } + + /** + * 한 행이 자기 토큰 중 하나로 자신을 찾을 수 있는지 확인합니다. + * + * @param FulltextIndexDefinition $definition 인덱스 정의 + * @param string $key 기본키 컬럼명 + * @param mixed $pk 행의 기본키 값 + * @param array $tokens 토큰 후보 + * @return bool 하나라도 찾으면 true + */ + private function rowFindsItself(FulltextIndexDefinition $definition, string $key, mixed $pk, array $tokens): bool + { + foreach ($tokens as $token) { + $hit = DB::selectOne( + "SELECT COUNT(*) AS hits FROM {$definition->quotedTable()} + WHERE `{$key}` = ? AND MATCH({$definition->quotedColumns()}) AGAINST(? IN BOOLEAN MODE)", + [$pk, '"'.$token.'"'] + ); + + if ((int) ($hit->hits ?? 0) > 0) { + return true; + } + } + + return false; + } + + /** + * 재생성 실패 시 원 정의로 되돌립니다 (실패해도 원 예외를 가리지 않도록 조용히 처리). + * + * @param FulltextIndexDefinition $definition 인덱스 정의 + * @return void + */ + private function restoreQuietly(FulltextIndexDefinition $definition): void + { + try { + DB::statement( + "ALTER TABLE {$definition->quotedTable()} ADD FULLTEXT INDEX `{$definition->name}` " + ."({$definition->quotedColumns()})".$definition->parserClause() + ); + } catch (Throwable) { + // 되돌리기까지 실패하면 원 예외가 더 중요한 정보다 + } + } +} diff --git a/app/Search/SearchIndexMaintenanceManager.php b/app/Search/SearchIndexMaintenanceManager.php new file mode 100644 index 00000000..b9c4c0ec --- /dev/null +++ b/app/Search/SearchIndexMaintenanceManager.php @@ -0,0 +1,232 @@ +> 드라이버명 => 구현 클래스 + */ + public function registered(): array + { + $maintainers = [ + 'mysql-fulltext' => FulltextIndexMaintainer::class, + ]; + + $maintainers = HookManager::applyFilters(self::MAINTAINERS_FILTER, $maintainers); + + return is_array($maintainers) ? $maintainers : []; + } + + /** + * 활성 드라이버의 유지보수기를 반환합니다. + * + * @return SearchIndexMaintainer|null 등록되지 않았으면 null + */ + public function maintainer(): ?SearchIndexMaintainer + { + if ($this->resolvedAttempted) { + return $this->resolved; + } + + $this->resolvedAttempted = true; + $class = $this->registered()[$this->driver()] ?? null; + + if ($class === null) { + return $this->resolved = null; + } + + try { + $instance = app($class); + } catch (Throwable $e) { + Log::warning('검색 인덱스 유지보수기 생성 실패', [ + 'driver' => $this->driver(), + 'class' => $class, + 'error' => $e->getMessage(), + ]); + + return $this->resolved = null; + } + + if (! $instance instanceof SearchIndexMaintainer) { + Log::warning('검색 인덱스 유지보수기가 계약을 구현하지 않음', [ + 'driver' => $this->driver(), + 'class' => $class, + ]); + + return $this->resolved = null; + } + + return $this->resolved = $instance; + } + + /** + * 활성 엔진이 점검을 제공하는지 여부. + * + * @return bool + */ + public function hasMaintainer(): bool + { + return $this->maintainer() !== null; + } + + /** + * 활성 엔진의 인덱스를 점검합니다. + * + * @param array $filters 엔진별 필터 + * @return array + */ + public function inspect(array $filters = []): array + { + $maintainer = $this->maintainer(); + + if ($maintainer === null || ! $maintainer->isAvailable()) { + return []; + } + + return $maintainer->inspect($filters); + } + + /** + * 점검 불가 사유를 반환합니다. + * + * @return string|null 점검 가능하면 null + */ + public function unavailableReason(): ?string + { + $maintainer = $this->maintainer(); + + if ($maintainer === null) { + return __('search.index.no_maintainer', ['driver' => $this->driver()]); + } + + return $maintainer->isAvailable() ? null : $maintainer->unavailableReason(); + } + + /** + * 색인이 누락된 인덱스를 재생성합니다. + * + * 재생성 후 **다시 점검해서** 잔존 여부를 담습니다 — 재생성했다는 사실만으로 + * 복구됐다고 단정하지 않습니다. + * + * @param array $filters 엔진별 필터 + * @return SearchIndexRepairReport 실행 보고 + */ + public function repairStale(array $filters = []): SearchIndexRepairReport + { + $maintainer = $this->maintainer(); + + if ($maintainer === null) { + return SearchIndexRepairReport::unavailable($this->driver(), $this->unavailableReason()); + } + + if (! $maintainer->isAvailable()) { + return SearchIndexRepairReport::unavailable($this->driver(), $maintainer->unavailableReason()); + } + + $before = $maintainer->inspect($filters); + $targets = array_values(array_filter($before, fn (SearchIndexHealth $h) => $h->needsRebuild())); + + if ($targets === []) { + return SearchIndexRepairReport::nothingToDo($this->driver(), count($before)); + } + + $repaired = []; + $failed = []; + + foreach ($targets as $target) { + try { + $maintainer->rebuild($target); + $repaired[] = $target->identifier; + } catch (Throwable $e) { + $failed[$target->identifier] = $e->getMessage(); + Log::error('검색 인덱스 재생성 실패', [ + 'driver' => $this->driver(), + 'index' => $target->identifier, + 'error' => $e->getMessage(), + ]); + } + } + + $after = $maintainer->inspect($filters); + $remaining = array_values(array_map( + fn (SearchIndexHealth $h) => $h->identifier, + array_filter($after, fn (SearchIndexHealth $h) => $h->needsRebuild()) + )); + + Log::info('검색 인덱스 재생성 수행', [ + 'driver' => $this->driver(), + 'inspected' => count($after), + 'repaired' => $repaired, + 'failed' => array_keys($failed), + 'remaining' => $remaining, + ]); + + return new SearchIndexRepairReport( + driver: $this->driver(), + inspected: count($after), + repaired: $repaired, + failed: $failed, + remaining: $remaining, + ); + } +} diff --git a/app/Services/LanguagePackService.php b/app/Services/LanguagePackService.php index 0abe49b9..248860ec 100644 --- a/app/Services/LanguagePackService.php +++ b/app/Services/LanguagePackService.php @@ -256,9 +256,10 @@ class LanguagePackService * 코어/번들 확장의 `lang/{ko,en}/` 디렉토리를 자동 스캔하여 가상 보호 LanguagePack * 인스턴스 컬렉션을 반환합니다 (요구사항 #1, #2). * - * 스캔 대상: + * 스캔 대상 — **런타임이 실제로 로드하는 경로**와 같아야 한다. 어긋나면 관리자 화면이 + * "내장 번역" 으로 보여주는 것과 화면에 나오는 문구의 출처가 달라진다. * - 코어: lang/{ko,en}/ - * - 번들 모듈: modules/_bundled/{id}/resources/lang/{ko,en}/ + * - 번들 모듈: modules/_bundled/{id}/src/lang/{ko,en}/ (TranslationServiceProvider 기준) * - 번들 플러그인: plugins/_bundled/{id}/lang/{ko,en}/ * - 번들 템플릿: templates/_bundled/{id}/lang/{ko,en}.json * @@ -288,11 +289,11 @@ class LanguagePackService } } - // 번들 모듈 + // 번들 모듈 (런타임 등록 경로 = src/lang) $packs = $packs->concat($this->scanBundledExtensionLang( bundledRoot: 'modules/_bundled', scope: LanguagePackScope::Module->value, - langSubpath: 'resources/lang/'.$locale, + langSubpath: 'src/lang/'.$locale, locale: $locale, filters: $filters, )); diff --git a/database/migrations/2026_07_31_000001_add_sort_indexes_to_notification_logs_table.php b/database/migrations/2026_07_31_000001_add_sort_indexes_to_notification_logs_table.php new file mode 100644 index 00000000..42ab4e58 --- /dev/null +++ b/database/migrations/2026_07_31_000001_add_sort_indexes_to_notification_logs_table.php @@ -0,0 +1,66 @@ +index('recipient_name', 'idx_notification_logs_recipient_name'); + } + + if (! in_array('idx_notification_logs_subject', $existingIndexes)) { + $table->index('subject', 'idx_notification_logs_subject'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (! Schema::hasTable('notification_logs')) { + return; + } + + $existingIndexes = array_column(Schema::getIndexes('notification_logs'), 'name'); + + Schema::table('notification_logs', function (Blueprint $table) use ($existingIndexes) { + $indexes = [ + 'idx_notification_logs_recipient_name', + 'idx_notification_logs_subject', + ]; + + foreach ($indexes as $index) { + if (in_array($index, $existingIndexes)) { + $table->dropIndex($index); + } + } + }); + } +}; diff --git a/database/migrations/2026_08_01_000002_add_tiebreak_to_core_list_indexes.php b/database/migrations/2026_08_01_000002_add_tiebreak_to_core_list_indexes.php new file mode 100644 index 00000000..5bd5186d --- /dev/null +++ b/database/migrations/2026_08_01_000002_add_tiebreak_to_core_list_indexes.php @@ -0,0 +1,86 @@ + [신규 색인명, 컬럼, 교체 대상 기존 색인명(없으면 null)]] + * + * @var array, 2: string|null}> + */ + private const TARGETS = [ + 'activity_logs' => ['idx_activity_logs_created_id', ['created_at', 'id'], 'g7_activity_logs_created_at_index'], + 'notification_logs' => ['idx_notification_logs_created_id', ['created_at', 'id'], null], + 'users' => ['idx_users_created_id', ['created_at', 'id'], 'idx_users_created_at'], + ]; + + /** + * Run the migrations. + */ + public function up(): void + { + foreach (self::TARGETS as $table => [$newIndex, $columns, $oldIndex]) { + if (! Schema::hasTable($table)) { + continue; + } + + $existing = array_column(Schema::getIndexes($table), 'name'); + + Schema::table($table, function (Blueprint $blueprint) use ($existing, $newIndex, $columns, $oldIndex) { + if (! in_array($newIndex, $existing, true)) { + $blueprint->index($columns, $newIndex); + } + + if ($oldIndex !== null && in_array($oldIndex, $existing, true)) { + $blueprint->dropIndex($oldIndex); + } + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + foreach (self::TARGETS as $table => [$newIndex, $columns, $oldIndex]) { + if (! Schema::hasTable($table)) { + continue; + } + + $existing = array_column(Schema::getIndexes($table), 'name'); + + Schema::table($table, function (Blueprint $blueprint) use ($existing, $newIndex, $oldIndex) { + if ($oldIndex !== null && ! in_array($oldIndex, $existing, true)) { + $blueprint->index(['created_at'], $oldIndex); + } + + if (in_array($newIndex, $existing, true)) { + $blueprint->dropIndex($newIndex); + } + }); + } + } +}; diff --git a/docs/backend/api/README.md b/docs/backend/api/README.md index 9a6e257f..10aa5cc9 100644 --- a/docs/backend/api/README.md +++ b/docs/backend/api/README.md @@ -201,11 +201,11 @@ location ~* \.(js|css|json)$ { expires max; access_log off; } > 각 확장이 자신의 API 문서를 소유합니다. 아래 표는 자동 생성됩니다. -- **확장 수**: 13 · **엔드포인트 수**: 385 +- **확장 수**: 13 · **엔드포인트 수**: 390 | 확장 | 유형 | API 문서 목차 | 문서/엔드포인트 | | --- | --- | --- | --- | -| `gnuboard7-hello_module` | 모듈 | [docs/api/](../../../modules/_bundled/gnuboard7-hello_module/docs/api/README.md) | 1 / 2 | +| `gnuboard7-hello_module` | 모듈 | [docs/api/](../../../modules/_bundled/gnuboard7-hello_module/docs/api/README.md) | 1 / 7 | | `sirsoft-board` | 모듈 | [docs/api/](../../../modules/_bundled/sirsoft-board/docs/api/README.md) | 10 / 80 | | `sirsoft-ecommerce` | 모듈 | [docs/api/](../../../modules/_bundled/sirsoft-ecommerce/docs/api/README.md) | 33 / 231 | | `sirsoft-page` | 모듈 | [docs/api/](../../../modules/_bundled/sirsoft-page/docs/api/README.md) | 2 / 17 | diff --git a/docs/backend/api/modules.md b/docs/backend/api/modules.md index 1b617fb4..1486aadf 100644 --- a/docs/backend/api/modules.md +++ b/docs/backend/api/modules.md @@ -3456,6 +3456,7 @@ Authorization: Bearer {YOUR_TOKEN} | layout_strategy | body | string | 아니오 | `overwrite`, `keep` | 업데이트 시 레이아웃 처리 전략 (overwrite: 새 버전으로 교체, keep: 사용자 수정본 유지) | | vendor_mode | body | string | 아니오 | `auto`, `composer`, `bundled` | 벤더 설치 모드 (auto/composer/bundled) | | force | body | boolean | 아니오 | — | 강제 실행 여부 (안전 확인/선행 검사 우회) | +| rebuild_search_index | body | boolean | 아니오 | — | 업데이트 후 색인이 누락된 검색 인덱스를 재생성할지 여부 (기본 false). 재생성 중에는 대상 인덱스가 잠기거나 재색인되므로 운영 중인 사이트에서는 접속이 적은 시간에 별도로 수행하는 것을 권장 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.module.perform_update_validation_rules`). @@ -3471,7 +3472,8 @@ Content-Type: application/json { "layout_strategy": "overwrite", "vendor_mode": "auto", - "force": true + "force": true, + "rebuild_search_index": false } ``` @@ -3494,7 +3496,7 @@ Content-Type: application/json -**설명** 특정 모듈을 최신 버전으로 업데이트합니다. `core.modules.install` 권한이 필요합니다. `layout_strategy` 로 레이아웃 처리 방식을(overwrite: 새 버전으로 교체, keep: 사용자 수정본 유지) 지정하며, `vendor_mode` 로 Composer 의존성 처리 방식을 선택합니다. 버전 제약·호환성 문제로 막힐 경우 `force: true` 로 강제 진행할 수 있습니다. `keep` 을 지정하면 사용자가 수정한 레이아웃(원본 해시와 현재 내용이 다른 레이아웃)은 갱신 대상에서 제외되어 현재 내용이 그대로 유지되고, 나머지 레이아웃만 파일 기준으로 갱신됩니다. 성공 응답 메시지에는 대상 식별자와 적용된 버전이 채워집니다. +**설명** 특정 모듈을 최신 버전으로 업데이트합니다. `core.modules.install` 권한이 필요합니다. `layout_strategy` 로 레이아웃 처리 방식을(overwrite: 새 버전으로 교체, keep: 사용자 수정본 유지) 지정하며, `vendor_mode` 로 Composer 의존성 처리 방식을 선택합니다. 버전 제약·호환성 문제로 막힐 경우 `force: true` 로 강제 진행할 수 있습니다. `keep` 을 지정하면 사용자가 수정한 레이아웃(원본 해시와 현재 내용이 다른 레이아웃)은 갱신 대상에서 제외되어 현재 내용이 그대로 유지되고, 나머지 레이아웃만 파일 기준으로 갱신됩니다. 성공 응답 메시지에는 대상 식별자와 적용된 버전이 채워집니다. `rebuild_search_index: true` 를 함께 보내면 업데이트 후 색인이 누락된 검색 인덱스를 재생성합니다 — 인덱스 잠금·재색인 비용이 있어 기본은 수행하지 않으며, 보내지 않아도 응답의 `search_index` 에 현재 누락 여부가 담깁니다. ### GET /api/modules/assets/{identifier}/{path} diff --git a/docs/backend/api/notification-logs.md b/docs/backend/api/notification-logs.md index 79bf7e59..6c15c53a 100644 --- a/docs/backend/api/notification-logs.md +++ b/docs/backend/api/notification-logs.md @@ -35,7 +35,7 @@ | extension_type | query | string | 아니오 | `core`, `module`, `plugin` | 확장 유형 (core/module/plugin/template) | | status | query | string | 아니오 | — | 상태 필터 (해당 상태의 항목만 조회) | | per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | -| sort_by | query | string | 아니오 | `id`, `channel`, `notification_type`, `status`, `sent_at`, `created_at` | 정렬 기준 필드명 | +| sort_by | query | string | 아니오 | `id`, `channel`, `notification_type`, `status`, `sent_at`, `created_at`, `recipient_name`, `subject` | 정렬 기준 필드명 (`recipient_name`·`subject` 는 관리자 화면의 "수신자명순"·"제목순" 정렬에 대응) | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.notification_log.index_validation_rules`). diff --git a/docs/backend/api/plugins.md b/docs/backend/api/plugins.md index 75a1c561..f32be2ad 100644 --- a/docs/backend/api/plugins.md +++ b/docs/backend/api/plugins.md @@ -1208,6 +1208,7 @@ Authorization: Bearer {YOUR_TOKEN} | layout_strategy | body | string | 아니오 | `overwrite`, `keep` | 레이아웃 처리 전략 (overwrite: 새 버전으로 교체, keep: 사용자 수정본 유지) | | vendor_mode | body | string | 아니오 | `auto`, `composer`, `bundled` | 벤더 설치 모드 (auto/composer/bundled) | | force | body | boolean | 아니오 | — | 강제 실행 여부 (안전 확인/선행 검사 우회) | +| rebuild_search_index | body | boolean | 아니오 | — | 업데이트 후 색인이 누락된 검색 인덱스를 재생성할지 여부 (기본 false). 재생성 중에는 대상 인덱스가 잠기거나 재색인되므로 운영 중인 사이트에서는 접속이 적은 시간에 별도로 수행하는 것을 권장 | > 이 엔드포인트는 확장이 파라미터를 추가할 수 있습니다 (`core.plugin.perform_update_validation_rules`). @@ -1223,7 +1224,8 @@ Content-Type: application/json { "layout_strategy": "overwrite", "vendor_mode": "auto", - "force": true + "force": true, + "rebuild_search_index": false } ``` @@ -1246,7 +1248,7 @@ Content-Type: application/json -**설명** 특정 플러그인을 최신 버전으로 업데이트합니다. `core.plugins.install` 권한이 필요합니다. `layout_strategy` 로 레이아웃 처리 방식을(overwrite: 새 버전으로 교체, keep: 사용자 수정본 유지) 지정하며, `vendor_mode` 로 Composer 의존성 처리 방식을 선택합니다. 버전 제약·호환성 문제로 막힐 경우 `force: true` 로 강제 진행할 수 있습니다. `keep` 을 지정하면 사용자가 수정한 레이아웃(원본 해시와 현재 내용이 다른 레이아웃)은 갱신 대상에서 제외되어 현재 내용이 그대로 유지되고, 나머지 레이아웃만 파일 기준으로 갱신됩니다. 성공 응답 메시지에는 대상 식별자와 적용된 버전이 채워집니다. +**설명** 특정 플러그인을 최신 버전으로 업데이트합니다. `core.plugins.install` 권한이 필요합니다. `layout_strategy` 로 레이아웃 처리 방식을(overwrite: 새 버전으로 교체, keep: 사용자 수정본 유지) 지정하며, `vendor_mode` 로 Composer 의존성 처리 방식을 선택합니다. 버전 제약·호환성 문제로 막힐 경우 `force: true` 로 강제 진행할 수 있습니다. `keep` 을 지정하면 사용자가 수정한 레이아웃(원본 해시와 현재 내용이 다른 레이아웃)은 갱신 대상에서 제외되어 현재 내용이 그대로 유지되고, 나머지 레이아웃만 파일 기준으로 갱신됩니다. 성공 응답 메시지에는 대상 식별자와 적용된 버전이 채워집니다. `rebuild_search_index: true` 를 함께 보내면 업데이트 후 색인이 누락된 검색 인덱스를 재생성합니다 — 인덱스 잠금·재색인 비용이 있어 기본은 수행하지 않으며, 보내지 않아도 응답의 `search_index` 에 현재 누락 여부가 담깁니다. ### GET /api/plugins/assets/{identifier}/{path} diff --git a/docs/backend/search-system.md b/docs/backend/search-system.md index ead43a8c..e8a8c479 100644 --- a/docs/backend/search-system.md +++ b/docs/backend/search-system.md @@ -11,8 +11,9 @@ 1. Laravel Scout + DatabaseFulltextEngine: MySQL FULLTEXT + ngram 기반 검색 (기본 드라이버) 2. FulltextSearchable 인터페이스: searchableColumns() + searchableWeights() 구현 필수 3. LIKE fallback 자동 적용: FULLTEXT 미지원 DBMS(SQLite, PostgreSQL)에서 자동 전환 -4. 확장 포인트: core.search.engine_drivers 필터 훅으로 Meilisearch 등 커스텀 엔진 등록 가능 -5. AsUnicodeJson 캐스트: JSON 컬럼 FULLTEXT 검색 시 한글 \uXXXX 이스케이프 방지 필수 +4. 확장 포인트: core.search.engine_drivers(엔진) + core.search.index_maintainers(인덱스 점검) 필터 훅 +5. 인덱스 재생성은 언제나 선택 사항 — 자동 트리거 없음 (테이블 잠금·전체 재색인 비용) +6. AsUnicodeJson 캐스트: JSON 컬럼 FULLTEXT 검색 시 한글 \uXXXX 이스케이프 방지 필수 ``` --- @@ -56,6 +57,10 @@ DatabaseFulltextEngine | `app/Providers/ScoutServiceProvider.php` | 엔진 등록 + 필터 훅 처리 | | `app/Casts/AsUnicodeJson.php` | FULLTEXT ngram용 UTF-8 JSON 캐스트 | | `config/scout.php` | Scout 설정 (드라이버, 큐, 소프트삭제 등) | +| `app/Search/Contracts/SearchIndexMaintainer.php` | 인덱스 점검·재생성 계약 (엔진 중립) | +| `app/Search/SearchIndexMaintenanceManager.php` | 활성 드라이버의 점검기 해석 + 재생성 진입점 | +| `app/Search/Engines/Maintenance/FulltextIndexMaintainer.php` | mysql-fulltext 드라이버의 점검기 구현 | +| `app/Console/Commands/Search/SearchIndexCommand.php` | `search:index` 점검·재생성 커맨드 | **설계 원칙**: @@ -225,6 +230,120 @@ $this->app->resolving(EngineManager::class, function (EngineManager $manager) us }); ``` +### core.search.index_maintainers 필터 훅 + +검색 엔진은 "인덱스가 있는데 내용이 색인되어 있지 않은" 상태가 될 수 있습니다. 이때 검색은 +**오류 없이 0건**을 돌려주므로 예외도 로그도 남지 않고, 운영자는 "원래 검색이 안 되는 줄" 알고 +지나갑니다. `SearchIndexMaintainer` 계약이 그 상태를 점검·복구하는 방법을 엔진마다 정의합니다. + +인덱스의 실체가 엔진마다 다르므로(FULLTEXT = 테이블에 붙은 인덱스, Meilisearch/Elasticsearch = +외부 서버의 인덱스) 코어는 판정 방법을 알지 못한 채 계약만 호출하고, 등급(`SearchIndexStatus`)만 +보고 재생성 대상을 고릅니다. + +```php +use App\Extension\HookManager; +use App\Search\SearchIndexMaintenanceManager; + +HookManager::addFilter(SearchIndexMaintenanceManager::MAINTAINERS_FILTER, function (array $maintainers) { + $maintainers['meilisearch'] = MeilisearchIndexMaintainer::class; + + return $maintainers; +}); +``` + +구현할 메서드 + +| 메서드 | 반환 | 설명 | +|--------|------|------| +| `driver()` | `string` | 담당 Scout 드라이버명 (`config('scout.driver')` 와 대조) | +| `isAvailable()` | `bool` | 현재 환경에서 점검 가능 여부 (미지원 DBMS·서버 미연결 등) | +| `unavailableReason()` | `?string` | 점검 불가 사유. **"점검 대상 0" 과 "점검할 수 없었음" 은 구분되어야 한다** | +| `inspect(array $filters)` | `SearchIndexHealth[]` | 인덱스별 판정. 엔진별 세부는 `details`, 재생성에 필요한 자기 정보는 `context` 에 담는다 | +| `rebuild(SearchIndexHealth $health)` | `void` | 재생성 (`context` 를 그대로 되돌려받는다) | + +판정 등급 (`App\Enums\SearchIndexStatus`) + +| 등급 | 뜻 | 재생성 대상 | +|------|-----|:---:| +| `healthy` | 색인된 내용으로 검색이 성립 | — | +| `degraded` | 일부만 성립. 토크나이저 특성(불용어 등)일 수 있다 | ✕ | +| `stale` | 검색이 성립하지 않음 | ✅ | +| `skipped` | 표본·연결 부재로 판정 불가 (사유 기재) | — | + +유지보수기를 등록하지 않은 엔진은 점검 대상에서 빠질 뿐 **검색은 그대로 동작**합니다. 화면·커맨드는 +그 경우 "이 엔진은 점검을 제공하지 않는다" 를 명시합니다. + +### 재생성은 언제나 선택 사항이다 + +재생성 비용은 엔진에 따라 테이블 잠금(FULLTEXT)이나 전체 재색인(외부 엔진)입니다. **운영 중인 +사이트에서 확장을 업데이트했다는 이유만으로 그 비용이 발생해서는 안 되므로**, 재생성은 어떤 자동 +트리거에도 연결하지 않고 운영자가 명시적으로 선택했을 때만 수행합니다. + +| 경로 | 선택 방법 | 기본값 | +|------|----------|:---:| +| `search:index --repair` | 옵션 + 확인 프롬프트 | 점검만 | +| `module:update` / `plugin:update` / `module:install` / `plugin:install` | `--rebuild-search-index` | 미수행 | +| `core:update` (번들 확장 일괄 업데이트 단계) | 대화형 확인 또는 `--rebuild-search-index` | 미수행 | +| 관리자 화면 확장 업데이트 모달 | 「업데이트 후 색인이 누락된 검색 인덱스를 재생성」 체크박스 | 미체크 | + +선택하지 않아도 **누락 사실은 안내**합니다 — 알려주지 않으면 운영자가 알 방법이 없기 때문입니다. + +`--force`(무인 실행)에서는 묻지도 재생성하지도 않습니다. 무인 실행이 대용량 테이블을 잠그는 일이 +없어야 합니다. + +### 선택은 그 창에서만 유효하다 — 화면 상태를 이월하지 않는다 + +재생성 체크는 **모달을 열 때마다 해제된 상태**로 시작해야 합니다. 체크 상태를 전역 상태에 남겨 두면 +한 번 체크한 운영자가 다음 확장을 업데이트할 때 **아무것도 누르지 않았는데 재생성이 다시 수행**됩니다. +서버의 옵인 가드(요청에 실린 값만 신뢰)는 정상 동작하므로 HTTP 레벨 테스트로는 드러나지 않습니다 — +화면이 이미 체크된 값을 보내기 때문입니다. + +| ❌ 금지 | ✅ 올바른 사용 | +| --- | --- | +| 모달을 여는 `setState` 시드에 재생성 키를 빼 둠 | 시드와 제출 후 초기화 **양쪽**에 `RebuildSearchIndex: false` | +| 제출 성공 후 다른 상태만 되돌리고 재생성 체크는 그대로 둠 | `onSuccess` 초기화 목록에 재생성 키 포함 | +| 모듈·플러그인이 같은 전역 키를 공유 | 면마다 별도 키 (한쪽 체크가 다른 쪽으로 전이되면 안 됨) | + +정적 고정: `templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-update-rebuild-optin.test.tsx` +종단 고정: `tests/Playwright/specs/admin/extension-update-search-index-optin.spec.ts` + +### 점검 결과는 반드시 호출자에게 도달해야 한다 + +색인이 비면 검색은 **오류 없이 0건**을 돌려줍니다. 운영자가 알 수 있는 유일한 통로가 응답에 실린 +`search_index` 페이로드이므로, 그 페이로드가 중간에서 사라지면 점검 기능 자체가 무의미해집니다. + +| ❌ 금지 | ✅ 올바른 사용 | +| --- | --- | +| 응답 헬퍼가 `JsonResource::resolve()` 만 호출 (부가 데이터 유실) | `ResponseHelper::successWithResource` 가 `additional()` 을 응답 최상위에 병합 | +| 재생성 여부만 알리고 잔존 여부는 생략 | `rebuilt` / `stale`·`stale_count` (미수행) 또는 `repaired`·`failed`·`remaining` (수행) | +| 재생성 성공을 곧 복구로 간주 | **`remaining` 은 재생성 후 재점검 결과** — "재생성했다" 와 "복구됐다" 를 구분 | + +### 점검의 비-0 종료는 "실패" 가 아니다 + +`search:index` 는 색인 누락이 남아 있으면 종료 코드 1 을 돌려줍니다. 점검 자체는 정상 수행된 것이며, +이는 CI 에서 이상을 감지하기 위한 신호입니다. 실행 결과를 화면에 표시하는 도구(개발 도구 대시보드 등)가 +이를 「실행 실패」로 적으면 운영자는 도구가 고장난 것으로 읽고 **정작 발견된 색인 누락을 놓칩니다**. +종료 코드와 출력을 그대로 보여 주고, 실패로 단정하지 않습니다. + +### mysql-fulltext 의 판정 방법 — 자기 매칭(self-match) + +기본 드라이버의 유지보수기는 표본 행을 뽑아 **그 행 자신의 내용에서 만든 검색어로 그 행을 찾을 수 +있는지** 봅니다. 특정 키워드를 사람이 골라 넣지 않으므로 어떤 테이블·언어에도 그대로 적용됩니다. + +토큰은 행마다 여러 개 만들고, **한 행이 어떤 토큰으로도 자신을 찾지 못할 때만** 실패로 셉니다. +하나만 쓰면 토크나이저 특성을 색인 누락으로 오판합니다 — 예를 들어 ngram(`ngram_token_size=2`)은 +`Basic` 을 `Ba/as/si/ic` 로 쪼개는데 그중 `as` 가 기본 불용어라 구문 검색이 깨집니다. + +대상은 하드코딩하지 않고 `INFORMATION_SCHEMA` 에서 전수 수집하므로, 확장이 나중에 추가하는 +FULLTEXT 인덱스도 커맨드 수정 없이 포함됩니다. + +```bash +php artisan search:index # 활성 엔진의 인덱스 점검 (읽기 전용) +php artisan search:index --repair # 색인 누락 인덱스 재생성 +php artisan search:index --filter=table=pages # 엔진별 필터 (FULLTEXT: table, index, samples) +php artisan search:index --json # 기계 판독용 출력 +``` + --- ## 마이그레이션 diff --git a/docs/backend/service-repository.md b/docs/backend/service-repository.md index b09b3b49..668464eb 100644 --- a/docs/backend/service-repository.md +++ b/docs/backend/service-repository.md @@ -27,6 +27,7 @@ - [상태 변경 자동 처리](#상태-변경-자동-처리) - [Repository 클래스](#repository-클래스) - [목록 조회 컬럼 프루닝과 지연 조인](#목록-조회-컬럼-프루닝과-지연-조인) +- [페이지네이션 정렬의 전순서 보장](#페이지네이션-정렬의-전순서-보장) - [다중 검색 필터 Trait](#다중-검색-필터-trait-hasmultiplesearchfilters) - [모듈에서 Repository 인터페이스 바인딩](#모듈에서-repository-인터페이스-바인딩) - [중첩 리소스 스코프](#중첩-리소스-스코프) @@ -859,9 +860,7 @@ php artisan g7:bench --profile=sirsoft-board/board_posts --seed=200000 --offsets 같은 계측의 실행 계획을 보면 원인이 하나 더 드러난다. OFFSET 0 은 `(board_id, created_at)` 인덱스를 역방향으로 훑어 정렬 없이 끝나지만, OFFSET 이 커지면 옵티마이저가 소프트 삭제 단독 인덱스(`deleted_at`)로 갈아타면서 `filesort` 가 붙는다. 넓은 컬럼을 걷어내는 것과 정렬을 인덱스로 덮는 것은 함께 가야 한다. -`SUBSTRING(content, 1, N)` 을 목록 컬럼에 두는 것도 프루닝이 아니다. 앞 N 바이트를 얻기 위해 오버플로 페이지를 그대로 읽는다. - -`SUBSTRING(content, 1, 200)` 을 목록 컬럼에 두는 것은 프루닝이 아니다. 앞 200바이트를 얻기 위해 오버플로 페이지를 그대로 읽기 때문이다. 잘라내기는 지연 조인의 outer 로 옮긴다. +`SUBSTRING(content, 1, N)` 을 목록 컬럼에 두는 것은 프루닝이 아니다. 앞 N 바이트를 얻기 위해 오버플로 페이지를 그대로 읽기 때문이다. 잘라내기는 지연 조인의 outer 로 옮긴다. 계측 대상 프로파일 선언 방법과 나머지 3축(화면 응답·쓰기·배치)은 [benchmark.md](benchmark.md) 를 참조한다. @@ -897,10 +896,13 @@ class OrderRepository implements OrderRepositoryInterface | 항목 | 규칙 | | ------ | ------ | | `$query` | 필터/where 만 적용한다. `orderBy`/`with`/`select` 는 Trait 이 담당한다 | -| `whereHas` | 결과 집합을 결정하는 조건이므로 그대로 둔다. Trait 은 `with` 만 inner 에서 제거한다 | +| `whereHas` | 결과 집합을 결정하는 조건이므로 그대로 둔다. Trait 이 지우는 것은 `with`(eager load) 뿐이다 | +| 관계 | **반드시 `relations:` 인자로 넘긴다.** Trait 은 inner 뿐 아니라 **outer 에서도** `setEagerLoads([])` 로 eager load 를 지우므로, `$query->with()` 만 하고 인자를 생략하면 관계가 로드되지 않는다 | | 정렬 | 닫힌 집합이어야 한다. 인덱스 없는 임의 컬럼 정렬은 inner 도 전체 스캔하므로 개선 폭이 보장되지 않는다 | | 키 컬럼 | 정렬 스펙 끝에 자동으로 덧붙는다. 동률 행의 순서가 흔들려 페이지 경계에서 행이 중복·누락되는 것을 막는다 | +관계 계약을 어겨도 예외나 쿼리 오류는 나지 않는다. 응답에서 관계 필드만 사라지므로, 그 필드를 단언하지 않는 테스트는 전부 통과한다. 실제로 스케줄 실행 이력의 실행자(`triggered_by`)가 이 형태로 유실됐다. `DeferredJoinCallSiteContractTest` 가 호출처를 스캔해 이 조합을 차단한다. + `paginate` / `simplePaginate` / 캐시 total 주입은 인자로 구분한다 — inner 쿼리는 동일하고 COUNT 수행 여부와 반환 클래스만 달라진다. | 호출자 상황 | `$simple` | `$total` | 반환 | @@ -944,6 +946,137 @@ foreach ($this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at') `repository-sort-column-whitelist` 룰은 **정렬 컬럼 변수 자체를 인자로 받는** `in_array`/`match`/`array_key_exists` 또는 `resolveSortSpec` 만 화이트리스트로 인정한다. +### 화면 정렬 옵션은 게이트의 부분집합이어야 한다 + +앞 절의 불변식(게이트 ⊆ 저장소)은 백엔드 두 계층 사이의 관계다. 그 위에 하나가 더 있다. + +```text +화면 정렬 옵션 ⊆ FormRequest 게이트 ⊆ Repository 화이트리스트 +``` + +레이아웃의 정렬 셀렉트가 게이트에 없는 컬럼을 제공하면 그 옵션을 고르는 순간 **422 가 나고 목록은 직전 결과 그대로 남는다.** 셀렉트 라벨만 새 값으로 바뀌므로 운영자는 정렬이 적용됐다고 읽는다. 토스트도 뜨지 않는다(목록 데이터소스의 오류 경로라 화면에 노출되지 않는다). + +정렬 옵션을 추가할 때는 세 곳을 함께 본다. + +1. 레이아웃의 `options[].value` (`{col}_{asc|desc}` 형태로 `sort_by` 를 만든다) +2. FormRequest 의 `sort_by` `in:` / `Rule::in` +3. Repository 의 `SORTABLE_COLUMNS` (원 테이블 컬럼) 또는 `RELATED_SORTABLE_COLUMNS` (관계 테이블 컬럼) + +정렬 기준 컬럼이 **원 테이블에 없으면** 다음 절의 관계 정렬을 쓴다. 화면에서 옵션을 지우는 것은 마지막 수단이다 — 운영자가 쓰던 기능이 사라진다. + +`layout-sort-option-gate-parity` 룰이 레이아웃 정렬 옵션과 게이트를 정적으로 대조한다. + +### 분류값 필터의 허용 어휘도 같은 불변식을 따른다 + +정렬에서 성립하는 포함관계는 **분류값 필터**(출처·유형·상태 등)에서도 그대로 성립한다. 다만 어긋나는 방향이 반대다 — 정렬은 화면이 게이트보다 넓어서 422 가 나고, 필터는 화면이 **좁아서** 아무 오류 없이 일부 행이 사라진다. + +```text +화면 필터 옵션 = 라벨 키 = 실제 기록 어휘 (부분집합이 아니라 일치여야 한다) +``` + +빠진 값으로 기록된 행에는 두 가지가 동시에 일어난다. + +| 어긋난 지점 | 증상 | 화면에 오류가 나는가 | +|---|---|---| +| 화면 필터 옵션에 그 값이 없음 | 그 값의 행은 **어떤 필터 조합으로도 도달 불가** — "전체" 로만 스쳐 지나간다 | 아니다 | +| 라벨 키가 없음 | 목록 셀에 `vendor-plugin.admin.x.source.그값` 원시 키 문자열이 노출 | 아니다 | + +그래서 어휘는 **Enum 하나에서 파생**시킨다. FormRequest 의 `in:` 문자열, 리스너·서비스의 기록 리터럴, 레이아웃 체크박스, lang 파일에 같은 목록을 네 번 적으면 한 곳만 늘어난 순간 위 두 증상이 조용히 생긴다. + +```php +enum ConsentSource: string +{ + case Banner = 'banner'; + case Register = 'register'; // 리스너가 기록하는 값도 반드시 여기에 + case MypageRenewAll = 'mypage_renew_all'; // 서버 자체 기록 경로도 마찬가지 + + /** 공개 요청으로 지정 가능한 값 — 서버 자체 기록 경로는 제외 */ + public static function requestSelectableValues(): array { /* ... */ } +} +``` + +라벨은 **백엔드와 프론트 두 표면 모두**에 정의한다. 관리자 화면의 `$t:` 는 `resources/lang/{locale}.json` 에서 해석되므로 `lang/{locale}/messages.php` 만 채우면 화면에는 여전히 원시 키가 남는다. 치환 문법도 다르다 — 백엔드는 `:name`, 프론트 엔진(`TranslationEngine.replaceParams`)은 `{name}` / `{{name}}` 만 치환한다. + +어느 enum 이 어느 화면의 어휘인지는 의미 추론이 필요해 정적 검출이 어렵다(`coverage.json` 의 `filter-vocabulary-enum-parity` = `manual-only`). 확장별로 정합 테스트를 둔다 — 예: `ConsentSourceVocabularyParityTest` (기록 리터럴 ⊆ enum / 화면 옵션 = enum / ko·en × 백엔드·프론트 라벨 전수). + +### 목록에 `last_page > 1` 이면 화면에도 페이저가 있어야 한다 + +응답이 페이지네이션인데 화면이 1페이지만 렌더하고 페이지 이동 컨트롤을 두지 않으면, 나머지 페이지는 **어떤 조작으로도 도달할 수 없다.** 총건수 표시까지 없으면 잘렸다는 사실 자체가 화면에 나타나지 않아 "이력이 그것뿐" 으로 읽힌다(감사 이력·정책 버전처럼 입증 책임이 있는 목록에서는 특히 위험하다). + +데이터가 얕은 개발 환경에서는 `last_page` 가 늘 1 이라 이 결함이 드러나지 않는다. 페이지네이션 응답을 소비하는 화면을 만들 때는 `per_page` 를 줄이거나 행을 늘려 **2페이지 이상인 상태를 한 번은 눈으로 확인**한다. + +### 관계 테이블 컬럼 기준 정렬 (`SortsByRelatedColumn`) + +정렬 기준이 원 테이블이 아니라 관계 테이블에 있는 경우가 있다. 주문 목록의 "발송일" 이 그렇다 — `shipped_at` 은 주문이 아니라 배송 테이블에 있고, 한 주문에 배송 행이 여러 건일 수 있다. + +이때 **조인으로 해결하지 않는다.** 두 가지가 함께 깨진다. + +| 방식 | 무엇이 깨지는가 | +|---|---| +| `join` + `groupBy` | 1:N 조인이 원 행을 부풀린다 → 총 건수가 늘고 페이지 경계가 어긋난다 | +| `leftJoin` 만 | 자식이 여러 건인 부모가 목록에 중복 노출된다 | +| `join` (INNER) | 자식이 없는 부모(미발송 주문)가 목록에서 사라진다 | +| inner 쿼리에 집계 | 건너뛸 행 전체에 집계가 돌아 OFFSET 이 깊어질수록 비용이 누적된다 — 지연 조인을 넣은 이유가 사라진다 | + +**상관 서브쿼리 정렬**을 쓴다. 원 행 수를 바꾸지 않으므로 총 건수·페이지 경계가 그대로 유지되고, 자식이 없는 행도 남는다(값이 `NULL` 로 정렬될 뿐이다). + +```php +use App\Repositories\Concerns\PaginatesWithDeferredJoin; +use App\Repositories\Concerns\ResolvesSortSpec; +use App\Repositories\Concerns\SortsByRelatedColumn; + +class OrderRepository implements OrderRepositoryInterface +{ + use PaginatesWithDeferredJoin; + use ResolvesSortSpec; + use SortsByRelatedColumn; + + /** 원 테이블 컬럼 */ + private const SORTABLE_COLUMNS = ['ordered_at', 'paid_at', 'total_amount']; + + /** 관계 테이블 컬럼 — `(외래키, 정렬컬럼)` 복합 인덱스가 전제 */ + private const RELATED_SORTABLE_COLUMNS = [ + 'shipped_at' => [ + 'model' => OrderShipping::class, + 'foreign_key' => 'order_id', + 'column' => 'shipped_at', + ], + ]; + + public function getListWithFilters(array $filters, int $perPage): LengthAwarePaginator + { + // ... 필터/where 적용 ... + + $sort = $this->resolveSortSpecWithRelated( + $filters, + self::SORTABLE_COLUMNS, + self::RELATED_SORTABLE_COLUMNS, + $this->model, + 'ordered_at', + ); + + return $this->paginateWithDeferredJoin( + query: $query, + columns: self::LIST_COLUMNS, + sort: $sort, + perPage: $perPage, + ); + } +} +``` + +**집계 함수를 인자로 받지 않는 이유.** 서브쿼리를 정렬 방향과 같은 방향으로 정렬해 한 건만 취하면, `desc` 는 자연히 최댓값(가장 늦은 발송), `asc` 는 최솟값(가장 이른 발송)이 된다. 운영자가 "최근 발송순" 을 골랐을 때 기대하는 값과 일치하고, `MAX()`/`MIN()` 을 따로 지정하다 방향과 어긋나는 실수가 생기지 않는다. + +**복합 인덱스는 선택이 아니다.** 서브쿼리는 목록 행마다 한 번씩 실행되므로 `(외래키, 정렬컬럼)` 인덱스가 없으면 행 수만큼 관계 테이블을 스캔한다. 외래키 단일 인덱스만 있으면 정렬 컬럼이 인덱스에 없어 매번 행을 읽어 정렬한다. 관계 정렬을 추가하는 변경에는 **인덱스 마이그레이션을 함께 넣는다.** + +```php +$table->index(['order_id', 'shipped_at'], 'ecommerce_order_shippings_order_id_shipped_at_index'); +``` + +정렬 스펙의 `column` 은 문자열 외에 `Builder`/`Expression` 도 올 수 있다. `PaginatesWithDeferredJoin` 이 이를 그대로 받아 inner/outer 양쪽에 적용한다 — 서브쿼리가 원 행의 키에 상관되므로 어느 쪽에 적용해도 같은 순서가 나오고, outer 에서는 이번 페이지 건수만큼만 실행된다. 키 컬럼은 전순서 보장을 위해 항상 마지막에 덧붙는다. + +**게이트에도 함께 올린다.** `RELATED_SORTABLE_COLUMNS` 의 키는 FormRequest 의 `sort_by` 허용 목록에 들어가야 한다. `SortWhitelistGateParityTest` 가 두 상수를 합쳐 게이트 ⊆ 저장소 불변식을 검사한다. + ### 예외 설정/정의성 테이블처럼 행 수가 고정이고 넓은 컬럼이 없으면 컬럼 목록 없이 페이지네이션해도 된다. 다만 그 판단 근거를 코드 옆에 남긴다. @@ -982,12 +1115,73 @@ return $this->model->newQuery() 다만 `columns:` 자체를 생략하면 outer 가 무엇을 읽는지 호출처에서 드러나지 않으므로 항상 명시한다. `DeferredJoinCallSiteContractTest` 가 호출처를 스캔해 `columns:`/`sort:` 명시 여부를 전수 검사한다. +### 페이지네이션 정렬의 전순서 보장 + +페이지네이션 목록의 정렬은 **고유 키로 닫는다**. 정렬이 비고유 컬럼만으로 끝나면 동률 구간의 +행 순서가 페이지마다 달라질 수 있고(SQL 표준은 동률의 순서를 보장하지 않는다), 그 결과 +**인접 페이지가 같은 행을 중복 노출하면서 다른 행은 어느 페이지에도 나오지 않는다.** + +```php +// ❌ created_at 동률 구간에서 페이지 경계가 흔들린다 +$query->orderBy('created_at', 'desc')->paginate($perPage); + +// ✅ 정렬 마지막에 기본키를 덧붙여 전순서를 만든다 +$query->orderBy('created_at', 'desc')->orderBy('id', 'desc')->paginate($perPage); +``` + +`ResolvesSortSpec` / `PaginatesWithDeferredJoin` 를 경유하는 목록은 트레이트가 키 컬럼을 자동으로 +덧붙이므로 이 규율을 이미 만족한다. 손으로 `orderBy` 를 조립한 뒤 `paginate()` 하는 지점만 +위험하다. + +동률은 드문 일이 아니다 — 시더·일괄 등록·대량 마이그레이션은 여러 행에 같은 `created_at` 을 +남긴다(#492 실측: `roles.created_at` 한 타임스탬프에 최대 10행, 110건 목록에서 고유 키 109개). + +판정은 눈으로 하지 않는다. 전 페이지를 순회해 키를 모으고 **합집합 크기 = 총건수**, 인접 페이지 +교집합 = ∅ 를 확인한다. + +### 필요한 색인은 선언에서 도출한다 + +지연 조인은 inner 가 키 컬럼만 읽게 만들지만, 그 inner 가 **인덱스 순서 그대로** 끝나야 깊은 OFFSET 이 싸진다. 정렬을 덮는 색인이 없으면 inner 도 filesort 로 전체를 훑으므로 개선 폭이 사라진다. 즉 지연 조인과 색인은 함께 가야 하며, 한쪽만 해두면 화면에서는 "여전히 뒤로 갈수록 느리다" 로만 드러난다. + +어떤 색인이 필요한지는 계측 프로파일 선언에 이미 들어 있다. `filters` · `order` · `soft_delete` 를 이으면 그대로 레시피가 된다: + +```text +(등치 필터 컬럼들 → soft_delete 면 deleted_at → 정렬 컬럼들 → 기본키) +``` + +`App\Benchmark\ListIndexAdvisor` 가 이 도출을 담당하고, `ListIndexCoverageTest` 가 전 목록을 검사한다. **새 목록이 프로파일을 선언하면 필요한 색인이 자동으로 검사 대상이 되므로**, 색인 설계를 테이블마다 손으로 반복하지 않아도 된다. + +| 판정 | 뜻 | 처방 | +| ------ | ------ | ------ | +| `satisfied` | 도출한 선행 컬럼을 그대로 덮는 색인이 있다 | — | +| `tiebreak_missing` | 색인이 정렬 컬럼에서 끝나 동률 구간에 filesort 가 남는다 | 기존 색인 끝에 기본키를 덧붙여 **교체** (좌측 프리픽스라 남기면 쓰기 비용만 늘어난다) | +| `missing` | 정렬을 덮는 색인이 없다 | 도출한 순서대로 색인 신설 | + +주의할 점 둘. + +- **등치가 아닌 필터는 선행 컬럼이 될 수 없다.** `in` / `not in` / 범위 조건은 그 지점에서 등치 사슬을 끊으므로 뒤따르는 정렬 컬럼을 인덱스 순서로 쓸 수 없다. 이 판정을 틀리면 "색인이 있는데도 filesort" 라는, 눈으로는 구분되지 않는 상태를 정상으로 보고하게 된다. +- **UNIQUE 색인은 기본키를 덧붙일 필요가 없다.** 값이 중복되지 않아 그 컬럼만으로 이미 전순서다. + +색인이 불필요한 목록(행 수가 고정이거나 상한이 작은 정의성 데이터)은 프로파일에 사유와 함께 면제를 선언한다. 사유 없는 면제를 받지 않는 이유는, 면제가 쌓이면 검사 자체가 무의미해지는데 사유가 없으면 그 판단이 여전히 옳은지 나중에 확인할 방법이 없기 때문이다. + +```php +'index_exempt' => '페이지는 사이트 구조를 구성하는 정의성 데이터라 ... 수천을 넘기 시작하면 (created_at, id) 색인을 추가할 것.', +``` + +모듈/플러그인 프로파일은 **그 확장의 테스트 스위트에서** 검사한다. 확장 테이블은 그 스위트에서만 마이그레이션되므로 코어 스위트에 몰면 테이블이 없어 건너뛰고 초록인 채 미검사가 된다. 같은 이유로 "테이블이 없으면 skip" 도 하지 않고 실패로 보고한다. + ### 정적 검사 대상 | 검출 대상 | 처리 | |---|---| | `->paginate($perPage)` / `->paginate($perPage, ['*'])` — 컬럼 목록 미지정 또는 전체 컬럼 | 차단 | -| 요청 값에서 온 정렬 컬럼을 닫힌 집합 검증 없이 `orderBy` 에 전달 | 차단 | +| 페이지네이션 정렬이 비고유 컬럼으로만 끝남 (기본키 타이브레이크 부재) | 차단 | +| 그룹 쿼리의 총 건수를 `count()` 로 계산 (`getCountForPagination()` 필요) | 차단 | +| 요청 값에서 온 정렬 컬럼을 닫힌 집합 검증 없이 `orderBy` 에 전달 | 경고 | + +검사 범위는 `app/Repositories/**`, `modules|plugins/_bundled/**/Repositories/**` 와 컨트롤러 3계열이다. 미수정 파일까지 확인하려면 변경분이 아니라 저장소 전체를 대상으로 검사한다 — 변경 파일만 보는 방식으로는 이미 저장소에 있던 위반이 드러나지 않는다. + +호출 계약 중 `relations:` 항목은 정적 룰이 아니라 테스트(`DeferredJoinCallSiteContractTest`)가 검사한다. 쿼리에 `with()` 를 붙이고 `relations:` 를 넘기지 않으면 관계가 조용히 사라지는데, 이 실패는 예외도 쿼리 오류도 내지 않아 관계 필드를 단언하지 않는 HTTP 테스트는 전부 통과한다. 검사 범위는 `app/Repositories/**`, `modules|plugins/_bundled/**/Repositories/**` 와 컨트롤러 3계열이다. 미수정 파일까지 확인하려면 변경분이 아니라 저장소 전체를 대상으로 검사한다 — 변경 파일만 보는 방식으로는 이미 저장소에 있던 위반이 드러나지 않는다. diff --git a/docs/backend/validation.md b/docs/backend/validation.md index 62fa7f2f..4b31237b 100644 --- a/docs/backend/validation.md +++ b/docs/backend/validation.md @@ -14,6 +14,8 @@ 5. 훅 확장: HookManager::applyFilters()로 동적 규칙 추가 6. 필수: Rule::exists(Model::class, 'col') 사용 (문자열 테이블명 사용 금지) 7. 런타임 조건부 검증: 드라이버/모드별 분기 시 메서드 추출 패턴 사용 +8. Store/Update 조건부 규칙은 대칭 — 한쪽 누락 시 과잉 검증(수정 실패) 또는 검증 우회 +9. min/size 가 붙은 필드는 required_if 로 조건부화 불가 → exclude_if (조건 필드가 nullable 이면 exclude_unless) ``` --- @@ -26,6 +28,9 @@ - [prepareForValidation() 데이터 전처리](#prepareforvalidation-데이터-전처리) - [훅 기반 동적 Validation Rules 확장](#훅-기반-동적-validation-rules-확장) - [런타임 조건부 Validation Rules (드라이버/모드별 분기)](#런타임-조건부-validation-rules-드라이버모드별-분기) + - [조건부 Validation 원칙](#조건부-validation-원칙) + - [`exclude_if` vs `exclude_unless` — 조건 필드가 nullable 일 때](#exclude_if-vs-exclude_unless--조건-필드가-nullable-일-때) + - [조건 필드가 요청에 없을 때 — 저장값 주입](#조건-필드가-요청에-없을-때--저장값-주입) - [권한 체크 방식](#권한-체크-방식) - [Custom Rule 검증 메시지 다국어 처리](#custom-rule-검증-메시지-다국어-처리) - [다국어 필드 검증 규칙](#다국어-필드-검증-규칙) @@ -34,6 +39,7 @@ - [exists/unique 검증 규칙](#existsunique-검증-규칙) - [계층 리소스 순환 참조](#계층-리소스-순환-참조) - [배열 항목의 상위 스코프](#배열-항목의-상위-스코프) +- [관대한 기존 동작을 규칙으로 승격하지 않는다](#관대한-기존-동작을-규칙으로-승격하지-않는다) - [Custom Rule 개발 체크리스트](#custom-rule-개발-체크리스트) --- @@ -1151,6 +1157,61 @@ Service 에도 동일한 검증을 두어 FormRequest 를 우회한 내부/훅 --- +## 관대한 기존 동작을 규칙으로 승격하지 않는다 + +컨트롤러가 base `Request` 를 주입받던 코드를 FormRequest 로 옮길 때, 그 컨트롤러가 하던 일이 +**거부**였는지 **수용**이었는지 먼저 읽는다. 수용하던 동작(클램핑·폴백·별칭 매핑)을 `rules()` 로 +올리면 200 이던 응답이 422 가 된다. 위반을 없애는 것이 목적이지 계약을 바꾸는 것이 아니다. + +```php +// 원본 컨트롤러 — 상한 초과를 거부하지 않고 상한까지 반환한다 +$limit = min((int) $request->input('limit', 20), 50); + +// ❌ 규칙으로 승격 — `?limit=100` 이 200 → 422 (기존 링크·북마크가 깨진다) +public function rules(): array +{ + return ['limit' => ['nullable', 'integer', 'max:50']]; +} + +// ✅ 규칙은 타입만 닫고, 상한은 접근자가 클램프한다 +public function rules(): array +{ + return ['limit' => ['nullable', 'integer', 'min:0']]; +} + +public function limit(): int +{ + return min((int) $this->validated('limit', 20), self::MAX_LIMIT); +} +``` + +같은 원칙이 어휘에도 적용된다. 저장소가 미지원 값을 기본 분기로 처리해 왔다면 `Rule::in` 으로 +막는 순간 그 값을 담은 URL 이 전부 깨진다. 어휘를 닫아야 할 별도 이유(예: 요청 값이 캐시 키에 +그대로 들어가 키 공간이 무한히 늘어남)가 있어도, **접근자가 닫힌 집합만 반환**하게 하면 응답 +계약을 건드리지 않고 목적을 달성한다. + +```php +// ✅ 응답은 종전과 동일(미지원 값 → year), 캐시 키만 정규화된다 +public function period(): string +{ + $period = $this->validated('period'); + + if ($period === null || $period === '') { + return self::DEFAULT_PERIOD; + } + + return in_array((string) $period, self::RESOLVED_PERIODS, true) ? (string) $period : 'year'; +} +``` + +`Rule::in` 승격이 적절한 경우는 **입력원이 화면 컨트롤 하나뿐**일 때다 — 관리자 DataGrid 의 필터 +select 처럼 사용자가 URL 을 손으로 만들 일이 없는 면. 판정 기준은 "북마크 가능한 공개 경로인가". + +기존 테스트가 이 전환에서 RED 가 되면 그 테스트가 무엇을 고정하고 있는지 먼저 읽는다. 리팩토링에서 +테스트가 깨질 때의 기본 해석은 "테스트가 낡았다" 가 아니라 "계약을 바꿨다" 이다. + +--- + ## Custom Rule 개발 체크리스트 - [ ] `/lang/ko/validation.php`에 한국어 메시지 추가 diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index aa7ec63e..dfcd3546 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -108,6 +108,31 @@ vendor/bin/pint --dirty --- +## 목록 조회 (컬럼 프루닝 · 지연 조인) + +```php +// 계속 쌓이는 목록 = 지연 조인. 정렬 컬럼이 요청 값이면 닫힌 집합으로 해석. +use App\Repositories\Concerns\{PaginatesWithDeferredJoin, ResolvesSortSpec}; + +return $this->paginateWithDeferredJoin( + query: $query, // 필터/where 만. orderBy·with·select 금지 + columns: self::LIST_COLUMNS, // ['*'] 도 위반 아님 + sort: $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'created_at'), + perPage: $perPage, + relations: ['user'], // 관계는 반드시 이 인자로 (with() 는 지워진다) +); +``` + +| 함정 | 결과 | +| ------ | ------ | +| `$query->with()` 만 하고 `relations:` 생략 | 관계가 조용히 사라짐 — 예외·오류 없음 | +| `SUBSTRING(content,1,N)` 을 inner 에 둠 | 오버플로 페이지 읽기 그대로 발생 (프루닝 아님) | +| 정렬을 비고유 컬럼으로만 끝냄 | 페이지 경계에서 행 중복·누락 (trait 이 PK 자동 append) | + +상세: [service-repository.md](backend/service-repository.md#목록-조회-컬럼-프루닝과-지연-조인) + +--- + ## 마이그레이션 ```bash diff --git a/docs/extension/layout-extensions.md b/docs/extension/layout-extensions.md index 7250428e..892f4f9d 100644 --- a/docs/extension/layout-extensions.md +++ b/docs/extension/layout-extensions.md @@ -75,9 +75,9 @@ injection 또는 modals/data_sources/scripts/init_actions 보유, EP: 병합할 필수: Layout Extension 시스템을 통한 동적 UI 주입 필수: 모듈 비활성화 시 관련 UI 자동 숨김 ✅ 필수: 확장 컴포넌트에 ExtensionBadge 표시 (관리자 UI) -필수: 플러그인은 완전한 레이아웃 등록 불가 → 확장 지점(layout_extensions)만 사용 - (예외: settings.json 환경설정 레이아웃은 registerPluginLayouts()로 등록) -필수: 모듈만 완전한 레이아웃 등록 가능 (admin/user 모두) +필수: 플러그인이 자기 화면을 가질 때 라우트는 plugins/{identifier}/ 네임스페이스 안에 선언 + (다른 확장이 소유한 경로를 선언하면 설치 순서에 따라 화면이 조용히 바뀐다) +필수: 다른 확장의 화면에 UI 를 끼워 넣을 때는 확장 지점(layout_extensions) 사용 ``` --- diff --git a/docs/extension/module-i18n.md b/docs/extension/module-i18n.md index 89c17a5a..1e52a001 100644 --- a/docs/extension/module-i18n.md +++ b/docs/extension/module-i18n.md @@ -7,8 +7,8 @@ ## TL;DR (5초 요약) ```text -1. 백엔드: /lang/{locale}/*.php → __('vendor-module::key') -2. 프론트엔드: /resources/lang/{locale}.json → $t:key +1. 백엔드: /src/lang/{locale}/*.php → __('vendor-module::key') — 이 경로만 로드된다 +2. 프론트엔드: /resources/lang/{locale}.json → $t:key (resources/lang 아래 *.php 는 미로드) 3. JSON에 moduleIdentifier 포함 금지! (자동 병합됨) 4. 지원 언어: ko, en 필수 5. 키 충돌 방지: 모듈별 고유 prefix 사용 권장 @@ -36,9 +36,18 @@ | 구분 | 파일 경로 | 형식 | 사용처 | |------|----------|------|--------| -| 백엔드 | `/lang/{locale}/*.php` | PHP 배열 | Laravel `__()` 함수 | +| 백엔드 | `/src/lang/{locale}/*.php` | PHP 배열 | Laravel `__()` 함수 | | 프론트엔드 | `/resources/lang/{locale}.json` | JSON | 레이아웃 JSON `$t:` 문법 | +> 모듈의 백엔드 다국어는 `src/lang` **한 곳**이다. `TranslationServiceProvider` 가 그 경로만 +> 등록하고, 확장 설치 검증(`ValidatesTranslationPath`)도 같은 경로를 강제한다. 언어팩 시스템 +> (`LanguagePackService` · `LanguagePackServiceProvider`) 도 같은 경로를 스캔한다. +> +> `resources/lang/{locale}/*.php` 에 백엔드 문구를 두면 **아무도 읽지 않는다** — 화면에는 +> `src/lang` 값이 나오고, 그 파일을 고쳐도 반영되지 않으며, 그 사실이 화면에 드러나지 않는다. +> `resources/lang` 아래에서 유효한 것은 프런트엔드용 `{locale}.json` 과 `partial/{locale}/*.json` +> 뿐이다. (플러그인은 `lang/{locale}/*.php`, 템플릿은 `lang/{locale}.json` 이 각각 그 자리다.) + > **코어 자체 다국어 자원도 동일 구조** — 코어는 `lang/{ko,en}/*.php` (백엔드) + > `lang/{ko,en}.json` (+ `lang/partial/{ko,en}/*.json`) (프론트엔드) 를 사용한다. > 모듈/플러그인/템플릿/코어 모두 같은 디렉토리 구조와 $partial 디렉티브 메커니즘을 diff --git a/docs/extension/plugin-development.md b/docs/extension/plugin-development.md index 6217e6c5..4ccc36fb 100644 --- a/docs/extension/plugin-development.md +++ b/docs/extension/plugin-development.md @@ -1040,19 +1040,25 @@ $response = Http::withToken($token) // 변경 시에만 캐시 갱신 ``` -#### 6. 레이아웃 제한 (Layout Restriction) +#### 6. 레이아웃 소유와 라우트 네임스페이스 (Layout Ownership) -플러그인은 모듈과 달리 완전한 레이아웃을 등록할 수 없습니다. +플러그인도 자기 화면(완전한 레이아웃)을 가질 수 있습니다. 다만 그 화면의 라우트는 +플러그인 네임스페이스 안에 선언해야 합니다. ```text -필수: 페이지 레이아웃은 모듈만 등록 가능 (플러그인은 layout_extensions만 사용) -허용: admin/plugin_settings.json (환경설정 UI) — 유일한 완전 레이아웃 -✅ 필수: 설정 외 UI 확장은 layout_extensions(확장 지점/Overlay)로만 가능 +필수: 플러그인이 소유하는 화면의 라우트는 plugins/{identifier}/ 아래에 선언 + 예) "*/admin/plugins/sirsoft-gdpr/consent-log" +필수: 다른 확장이 소유한 화면에 UI 를 끼워 넣을 때는 layout_extensions(확장 지점/Overlay) 사용 ``` +라우트 네임스페이스를 지키지 않으면, 같은 경로를 소유한 모듈/템플릿과 어느 쪽이 이기는지가 +설치 순서에 좌우됩니다. 화면이 조용히 다른 것으로 바뀌고, 어느 확장 때문인지 화면만 봐서는 +알 수 없습니다. 정적 검사가 이 위반을 차단합니다. + | 방식 | 모듈 | 플러그인 | |------|------|---------| -| 임의 페이지 레이아웃 (admin/user) | 가능 | 금지 | +| 자기 네임스페이스 화면 (`plugins/{id}/...`) | 해당 없음 | 가능 | +| 임의 경로 페이지 레이아웃 (`admin/users` 등) | 가능 | 금지 (경로 침범) | | admin/plugin_settings.json (환경설정) | 가능 | 가능 | | layout_extensions (확장 지점) | 가능 | 가능 | diff --git a/lang-packs/_bundled/g7-core-ja/CHANGELOG.md b/lang-packs/_bundled/g7-core-ja/CHANGELOG.md index af6e4982..07309182 100644 --- a/lang-packs/_bundled/g7-core-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-core-ja/CHANGELOG.md @@ -4,6 +4,18 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.9] - 2026-08-01 + +### Added + +- 통신이 끊겼을 때 표시되는 네트워크 오류 안내 문구 일본어 번역 추가 — 종전에는 내부 식별 문구가 영문 그대로 노출됐습니다. + +## [1.0.8] - 2026-08-01 + +### Added + +- 검색 인덱스 점검·재생성(`search:index`) 화면 문구 일본어 번역 추가 — 판정 등급(정상/부분적/미반영/판정불가), 점검 결과 표, 재생성 확인·경고 문구가 일본어 로케일에서 자연스럽게 표시됩니다. + ## [1.0.7] - 2026-07-22 ### Added diff --git a/lang-packs/_bundled/g7-core-ja/backend/ja/search.php b/lang-packs/_bundled/g7-core-ja/backend/ja/search.php index 8ee409a6..16730df9 100644 --- a/lang-packs/_bundled/g7-core-ja/backend/ja/search.php +++ b/lang-packs/_bundled/g7-core-ja/backend/ja/search.php @@ -14,4 +14,46 @@ return [ 'per_page_min' => 'ページあたりの項目数は1以上である必要があります。', 'per_page_max' => 'ページあたりの項目数は最大100個まで可能です。', ], + 'index' => [ + 'status' => [ + 'healthy' => '正常', + 'degraded' => '部分的', + 'stale' => 'インデックス未反映', + 'skipped' => '判定不可', + ], + 'no_maintainer' => '現在の検索エンジン(:driver)はインデックス点検を提供していません。エンジン提供元が点検機能を登録すると、この画面で併せて扱えます。', + 'unavailable' => '現在の検索エンジン(:driver)のインデックスを点検できません。', + 'no_targets' => '検索エンジン(:driver)に点検対象のインデックスがありません。', + 'driver_label' => '検索エンジン: :driver', + 'col' => [ + 'index' => 'インデックス', + 'status' => '判定', + 'measurement' => '測定', + ], + 'counts' => '正常 :healthy · 部分的 :degraded · 未反映 :stale · 判定不可 :skipped (合計 :total)', + 'rebuild_targets' => '再生成対象 :count件', + 'rebuild_cost_warning' => '再生成中は対象インデックスがロックされるか再インデックスされます。稼働中のサイトではメンテナンス時間帯に実行してください。', + 'rebuild_confirm' => '上記のインデックスを再生成しますか?', + 'rebuild_after_bulk_confirm' => '一括アップデート後に検索インデックスを再生成しますか? (既定: いいえ — 稼働中であればメンテナンス時間帯に別途実行してください)', + 'rebuild_skipped' => '再生成をスキップします。', + 'stale_hint' => 'インデックスが未反映のものがあります。`php artisan search:index --repair` で再生成してください。', + 'stale_after_update' => 'インデックスが未反映の検索インデックスが :count 件あり、該当する検索が結果を返しません。', + 'rebuilt_item' => '再生成: :index', + 'rebuild_failed_item' => '再生成失敗: :index — :error', + 'still_stale_item' => '再生成後も未反映: :index', + 'degraded_hint' => '「部分的」はエンジンのトークン処理特性の可能性があるため、自動再生成の対象外です。必要であれば個別に確認してください。', + 'report' => [ + 'nothing_to_do' => '点検 :count件 · 再生成対象なし', + 'rebuilt' => '点検 :inspected件 · 再生成 :repaired件 · 失敗 :failed件 · 残存 :remaining件', + ], + 'fulltext' => [ + 'unsupported_driver' => '現在のDBドライバはFULLTEXTをサポートしていません (LIKE検索で動作し、点検対象はありません)。', + 'self_match' => ':found/:probed 行が自分自身を検出', + 'skip' => [ + 'no_single_pk' => '単一カラムの主キーがないため、行単位の判定ができません', + 'no_rows' => 'インデックス対象カラムに内容がある行がありません', + 'no_tokens' => 'サンプル行から検索トークンを作成できません (内容が短すぎます)', + ], + ], + ], ]; diff --git a/lang-packs/_bundled/g7-core-ja/frontend/partial/errors.json b/lang-packs/_bundled/g7-core-ja/frontend/partial/errors.json index 4e213026..f9288c24 100644 --- a/lang-packs/_bundled/g7-core-ja/frontend/partial/errors.json +++ b/lang-packs/_bundled/g7-core-ja/frontend/partial/errors.json @@ -6,5 +6,6 @@ "component_load_failed_with_name": "コンポーネント({componentName})の読み込みに失敗しました", "translation_load_failed": "多言語ファイルの読み込みに失敗しました", "translation_load_failed_with_locale": "{locale}多言語ファイルの読み込みに失敗しました", - "template_init_error": "テンプレートエンジンの初期化中にエラーが発生しました" + "template_init_error": "テンプレートエンジンの初期化中にエラーが発生しました", + "network_request_failed": "リクエストを送信できませんでした。ネットワーク接続を確認してから再度お試しください。" } diff --git a/lang-packs/_bundled/g7-core-ja/language-pack.json b/lang-packs/_bundled/g7-core-ja/language-pack.json index e7ad4030..eddb5bd1 100644 --- a/lang-packs/_bundled/g7-core-ja/language-pack.json +++ b/lang-packs/_bundled/g7-core-ja/language-pack.json @@ -12,7 +12,7 @@ "en": "G7 core Japanese language pack (bundled)", "ja": "G7 コア 日本語 言語パック(バンドル)" }, - "version": "1.0.7", + "version": "1.0.9", "license": "MIT", "scope": "core", "target_identifier": null, diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md index fc82863e..7714efaf 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/CHANGELOG.md @@ -4,6 +4,16 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.7] - 2026-07-31 + +### Added + +- 상품 문의 처리가 실패했을 때 그 사유를 그대로 보여주는 안내 문구의 일본어 번역을 추가했습니다. + +### Removed + +- 어디에서도 사용하지 않는 안내 문구 11건의 일본어 번역을 정리했습니다. 한국어 원본이 실제 화면에 쓰이지 않는 위치에 있어 번역만 남아 있던 항목입니다. + ## [1.0.6] - 2026-07-22 ### Added diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/messages.php b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/messages.php index 04c88ba0..d1661982 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/messages.php +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/backend/ja/messages.php @@ -279,8 +279,6 @@ return [ 'select_items_message' => '商品を選択してください', 'checkout' => '注文する', 'continue_shopping' => 'ショッピングを続ける', - 'updated' => 'カートが修正されました。', - 'removed' => 'カートから削除されました。', 'reorder_added' => '過去の注文の商品をカートに追加しました。', 'reorder_failed' => '再注文に失敗しました。', 'reorder_option_not_found' => '商品オプションはもう存在しません。', @@ -319,9 +317,6 @@ return [ 'invalid_target' => '当該商品には適用できないクーポンです。', 'already_used' => '既に使用されたクーポンです。', 'not_found' => 'クーポンが見つかりません。', - 'applied' => 'クーポンが適用されました。', - 'removed' => 'クーポンが解除されました。', - 'invalid' => '無効なクーポンです。', 'per_user_limit_exceeded' => 'このクーポンの使用可能回数を超過しました。', ], 'mileage' => [ @@ -1102,6 +1097,7 @@ return [ 'reply_deleted' => '回答が削除されました。', 'reply_delete_failed' => '回答の削除に失敗しました。', 'reply_not_found' => '回答が見つかりません。', + 'operation_failed_reason' => ':reason', ], 'address' => [ 'created' => '配送先が追加されました。', @@ -1121,15 +1117,8 @@ return [ 'name_duplicate' => '同じ名前の配送先が既に存在します。上書きしますか?', 'auto_saved_label' => '新しい配送先', ], - 'product' => [ - 'not_found' => '商品が見つかりません。', - 'not_available' => '現在、購入できない商品です。', - 'option_not_found' => '商品オプションが見つかりません。', - ], + 'product' => [], 'payment' => [ - 'method_not_supported' => 'サポートされていない決済方法です。', - 'dbank_account_required' => '銀行振込の口座を選択してください。', - 'depositor_name_required' => '振込人名を入力してください。', 'provider_not_found' => '決済プロバイダーが見つかりません。', 'client_config_success' => '決済クライアント設定を取得しました。', ], diff --git a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/language-pack.json b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/language-pack.json index ee68cb82..79f8ebbd 100644 --- a/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/language-pack.json +++ b/lang-packs/_bundled/g7-module-sirsoft-ecommerce-ja/language-pack.json @@ -12,7 +12,7 @@ "en": "G7 module (sirsoft-ecommerce) Japanese language pack (bundled)", "ja": "G7 モジュール (sirsoft-ecommerce) 日本語 言語パック(バンドル)" }, - "version": "1.0.6", + "version": "1.0.7", "license": "MIT", "scope": "module", "target_identifier": "sirsoft-ecommerce", diff --git a/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/CHANGELOG.md b/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/CHANGELOG.md index 2d8c92ba..1d123a0b 100644 --- a/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/CHANGELOG.md @@ -4,10 +4,12 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. -## [1.0.2] - 2026-07-29 +## [1.0.2] - 2026-08-01 ### Added +- 동의 이력의 출처 "会員登録" / "マイページ一括再同意" 일본어 라벨 추가 +- 정책 버전 이력 페이지 이동(前へ / 次へ)과 총 건수 표시 일본어 번역 추가 - 자동 차단 정책 카드에 보호 범위 한계 안내 문구(운영자가 직접 삽입한 외부 스크립트는 보호 범위 밖) 일본어 번역 추가 ## [1.0.1] - 2026-07-27 diff --git a/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/backend/ja/messages.php b/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/backend/ja/messages.php index 9f840ab4..1f01be16 100644 --- a/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/backend/ja/messages.php +++ b/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/backend/ja/messages.php @@ -140,6 +140,9 @@ return [ 'snapshot_action' => '本文', ], ], + 'history_page_summary' => '全 :total 件 · :current / :last ページ', + 'history_prev' => '前へ', + 'history_next' => '次へ', 'snapshot_view_short' => '表示', 'material_modal' => [ 'title' => 'ポリシーバージョン公開 + 再同意トリガー', @@ -197,7 +200,9 @@ return [ 'source' => [ 'banner' => 'バナー', 'preference_center' => '環境設定', + 'register' => '会員登録', 'mypage' => 'マイページ', + 'mypage_renew_all' => 'マイページ一括再同意', ], 'col' => [ 'created_at' => '時点', diff --git a/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/frontend/ja.json b/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/frontend/ja.json index c3bfeb2c..917a2e3f 100644 --- a/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/frontend/ja.json +++ b/lang-packs/_bundled/g7-plugin-sirsoft-gdpr-ja/frontend/ja.json @@ -201,7 +201,10 @@ "open_policy_page": "本文ページを開く", "no_policy_slug": "登録されたポリシー本文ページがありません。" }, - "not_found": "そのポリシーバージョンが見つかりません。" + "not_found": "そのポリシーバージョンが見つかりません。", + "history_page_summary": "全体 {total}件 · {current} / {last} ページ", + "history_prev": "前へ", + "history_next": "次へ" } }, "admin": { @@ -233,7 +236,9 @@ "source": { "banner": "バナー", "preference_center": "環境設定", - "mypage": "マイページ" + "mypage": "マイページ", + "register": "会員登録", + "mypage_renew_all": "マイページ一括再同意" }, "col": { "created_at": "時点", diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md index 0861bd7d..d2b8948e 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/CHANGELOG.md @@ -4,6 +4,12 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [1.0.5] - 2026-08-01 + +### Added + +- 확장 업데이트 모달의 「검색 인덱스 재생성」 선택 항목 문구 일본어 번역 추가 — 항목 이름과 설명(재생성 시 인덱스 잠금·재색인 안내)이 일본어 로케일에서 자연스럽게 표시됩니다. + ## [1.0.4] - 2026-07-17 ### Added diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/extensions.json b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/extensions.json index 5b2392d7..d4104c97 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/extensions.json +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/frontend/partial/extensions.json @@ -30,6 +30,9 @@ "compat_warning_title": "コアバージョン互換性警告", "compat_warning_message": "この{{type}}はコア {{required}} 以上が必要です。(現在: {{installed}})", "compat_guide_link": "コアアップグレードガイドを表示", - "force_label": "警告を無視して強制アップデート (推奨されません)" + "force_label": "警告を無視して強制アップデート (推奨されません)", + "rebuild_search_index_title": "検索インデックス", + "rebuild_search_index_label": "アップデート後、インデックスが未反映の検索インデックスを再生成", + "rebuild_search_index_desc": "再生成中は対象インデックスがロックされるか再インデックスされます。稼働中のサイトではチェックせず、メンテナンス時間帯に別途実行してください。" } } diff --git a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json index d759ed88..063f3b9c 100644 --- a/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json +++ b/lang-packs/_bundled/g7-template-sirsoft-admin_basic-ja/language-pack.json @@ -12,7 +12,7 @@ "en": "G7 template (sirsoft-admin_basic) Japanese language pack (bundled)", "ja": "G7 テンプレート (sirsoft-admin_basic) 日本語 言語パック(バンドル)" }, - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "scope": "template", "target_identifier": "sirsoft-admin_basic", diff --git a/lang/en/search.php b/lang/en/search.php index ff575069..1e7d5ea6 100644 --- a/lang/en/search.php +++ b/lang/en/search.php @@ -20,4 +20,54 @@ return [ 'per_page_min' => 'Items per page must be at least 1.', 'per_page_max' => 'Items per page must not exceed 100.', ], -]; \ No newline at end of file + + // Search index inspection / rebuild (search:index) — engine neutral + 'index' => [ + 'status' => [ + 'healthy' => 'Healthy', + 'degraded' => 'Partial', + 'stale' => 'Not indexed', + 'skipped' => 'Not determinable', + ], + + 'no_maintainer' => 'The current search engine (:driver) does not provide index inspection. It will appear here once its provider registers one.', + 'unavailable' => 'The index of the current search engine (:driver) cannot be inspected.', + 'no_targets' => 'The search engine (:driver) has no index to inspect.', + 'driver_label' => 'Search engine: :driver', + + 'col' => [ + 'index' => 'Index', + 'status' => 'Status', + 'measurement' => 'Measurement', + ], + + 'counts' => 'Healthy :healthy · Partial :degraded · Not indexed :stale · Not determinable :skipped (total :total)', + 'rebuild_targets' => ':count index(es) to rebuild', + 'rebuild_cost_warning' => 'Rebuilding locks or re-indexes the target. On a live site, run it during a maintenance window.', + 'rebuild_confirm' => 'Rebuild the indexes listed above?', + 'rebuild_after_bulk_confirm' => 'Rebuild search indexes after the bulk update? (default: no — on a live site, run it during a maintenance window)', + 'rebuild_skipped' => 'Skipping the rebuild.', + 'stale_hint' => 'Some indexes hold no indexed content. Rebuild them with `php artisan search:index --repair`.', + 'stale_after_update' => ':count search index(es) hold no indexed content, so those searches return nothing.', + 'rebuilt_item' => 'Rebuilt: :index', + 'rebuild_failed_item' => 'Rebuild failed: :index — :error', + 'still_stale_item' => 'Still not indexed after rebuild: :index', + 'degraded_hint' => '"Partial" may come from the engine tokenizer, so it is not rebuilt automatically. Inspect individually if needed.', + + 'report' => [ + 'nothing_to_do' => 'Inspected :count · nothing to rebuild', + 'rebuilt' => 'Inspected :inspected · rebuilt :repaired · failed :failed · still stale :remaining', + ], + + // mysql-fulltext driver specific + 'fulltext' => [ + 'unsupported_driver' => 'The current database driver does not support FULLTEXT (search falls back to LIKE; nothing to inspect).', + 'self_match' => ':found/:probed rows found themselves', + 'skip' => [ + 'no_single_pk' => 'No single-column primary key, so per-row inspection is not possible', + 'no_rows' => 'No row has content in the indexed columns', + 'no_tokens' => 'Could not build a search token from the sampled rows (content too short)', + ], + ], + ], +]; diff --git a/lang/ko/search.php b/lang/ko/search.php index 7ed44e77..1e802d4c 100644 --- a/lang/ko/search.php +++ b/lang/ko/search.php @@ -20,4 +20,54 @@ return [ 'per_page_min' => '페이지당 항목 수는 1 이상이어야 합니다.', 'per_page_max' => '페이지당 항목 수는 최대 100개까지 가능합니다.', ], -]; \ No newline at end of file + + // 검색 인덱스 점검·재생성 (search:index) — 엔진 중립 + 'index' => [ + 'status' => [ + 'healthy' => '정상', + 'degraded' => '부분', + 'stale' => '색인 누락', + 'skipped' => '판정 불가', + ], + + 'no_maintainer' => '현재 검색 엔진(:driver)은 인덱스 점검을 제공하지 않습니다. 엔진 제공자가 점검 기능을 등록하면 이 화면에서 함께 다룹니다.', + 'unavailable' => '현재 검색 엔진(:driver)의 인덱스를 점검할 수 없습니다.', + 'no_targets' => '검색 엔진(:driver)에 점검할 인덱스가 없습니다.', + 'driver_label' => '검색 엔진: :driver', + + 'col' => [ + 'index' => '인덱스', + 'status' => '판정', + 'measurement' => '측정', + ], + + 'counts' => '정상 :healthy · 부분 :degraded · 색인누락 :stale · 판정불가 :skipped (총 :total)', + 'rebuild_targets' => '재생성 대상 :count개', + 'rebuild_cost_warning' => '재생성 중에는 대상 인덱스가 잠기거나 재색인됩니다. 운영 중인 사이트에서는 유지보수 시간에 수행하세요.', + 'rebuild_confirm' => '위 인덱스를 재생성할까요?', + 'rebuild_after_bulk_confirm' => '일괄 업데이트 후 검색 인덱스를 재생성할까요? (기본: 아니오 — 운영 중이면 유지보수 시간에 별도로 수행하세요)', + 'rebuild_skipped' => '재생성을 건너뜁니다.', + 'stale_hint' => '색인이 누락된 인덱스가 있습니다. `php artisan search:index --repair` 로 재생성하세요.', + 'stale_after_update' => '색인이 누락된 검색 인덱스 :count개가 있어 해당 검색이 결과를 돌려주지 않습니다.', + 'rebuilt_item' => '재생성: :index', + 'rebuild_failed_item' => '재생성 실패: :index — :error', + 'still_stale_item' => '재생성 후에도 색인 누락: :index', + 'degraded_hint' => '「부분」 은 엔진의 토큰 처리 특성일 수 있어 자동 재생성 대상이 아닙니다. 필요하면 개별 확인하세요.', + + 'report' => [ + 'nothing_to_do' => '점검 :count개 · 재생성 대상 없음', + 'rebuilt' => '점검 :inspected개 · 재생성 :repaired개 · 실패 :failed개 · 잔존 :remaining개', + ], + + // mysql-fulltext 드라이버 전용 문구 + 'fulltext' => [ + 'unsupported_driver' => '현재 DB 드라이버는 FULLTEXT 를 지원하지 않습니다 (LIKE 검색으로 동작하며 점검 대상이 없습니다).', + 'self_match' => ':found/:probed 행이 자기 자신을 찾음', + 'skip' => [ + 'no_single_pk' => '단일 컬럼 기본키가 없어 행 단위 판정을 할 수 없습니다', + 'no_rows' => '색인 컬럼에 내용이 있는 행이 없습니다', + 'no_tokens' => '표본 행에서 검색 토큰을 만들 수 없습니다 (내용이 너무 짧음)', + ], + ], + ], +]; diff --git a/lang/partial/en/errors.json b/lang/partial/en/errors.json index ae53f92e..1a5ba6c1 100644 --- a/lang/partial/en/errors.json +++ b/lang/partial/en/errors.json @@ -6,5 +6,6 @@ "component_load_failed_with_name": "Failed to load component ({componentName})", "translation_load_failed": "Failed to load translation file", "translation_load_failed_with_locale": "Failed to load {locale} translation file", - "template_init_error": "An error occurred while initializing the template engine" + "template_init_error": "An error occurred while initializing the template engine", + "network_request_failed": "The request could not be sent. Check your network connection and try again." } diff --git a/lang/partial/ko/errors.json b/lang/partial/ko/errors.json index 7f04c16b..85d0db5a 100644 --- a/lang/partial/ko/errors.json +++ b/lang/partial/ko/errors.json @@ -6,5 +6,6 @@ "component_load_failed_with_name": "컴포넌트({componentName})를 불러오는데 실패했습니다", "translation_load_failed": "다국어 파일을 불러오는데 실패했습니다", "translation_load_failed_with_locale": "{locale} 다국어 파일을 불러오는데 실패했습니다", - "template_init_error": "템플릿 엔진을 초기화하는 중 오류가 발생했습니다" + "template_init_error": "템플릿 엔진을 초기화하는 중 오류가 발생했습니다", + "network_request_failed": "요청을 보내지 못했습니다. 네트워크 연결을 확인한 뒤 다시 시도해 주세요." } diff --git a/modules/_bundled/gnuboard7-hello_module/CHANGELOG.md b/modules/_bundled/gnuboard7-hello_module/CHANGELOG.md index fdf30e2e..224822c0 100644 --- a/modules/_bundled/gnuboard7-hello_module/CHANGELOG.md +++ b/modules/_bundled/gnuboard7-hello_module/CHANGELOG.md @@ -4,11 +4,19 @@ 형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)를 따르며, [Semantic Versioning](https://semver.org/lang/ko/)을 준수합니다. +## [0.1.1] - 2026-07-31 + +### Fixed + +- 관리자 메모 목록 화면에 메모가 하나도 보이지 않던 문제를 수정했습니다. 화면이 부르는 주소와 실제로 열려 있는 주소가 달라 목록을 받아오지 못하고 있었습니다. +- 메모 목록에서 한 페이지에 보여줄 개수를 0·음수·과도하게 큰 값으로 요청하면 오류가 나거나 메모 전체가 한 번에 조회되던 문제를 수정했습니다. 1~100 범위를 벗어나면 요청이 거부됩니다. + ## [0.1.0] - 2026-07-01 ### Fixed - 메모 목록에서 작성·수정 화면에 다녀오면 보고 있던 페이지가 사라지고 1페이지로 되돌아가던 문제를 수정했습니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) +- 메모 목록에서 같은 시각에 등록된 메모가 여러 건일 때 페이지를 넘기면 같은 메모가 두 번 보이던 문제를 수정했습니다. ### Changed diff --git a/modules/_bundled/gnuboard7-hello_module/composer.json b/modules/_bundled/gnuboard7-hello_module/composer.json index daa3f357..4ce20a7f 100644 --- a/modules/_bundled/gnuboard7-hello_module/composer.json +++ b/modules/_bundled/gnuboard7-hello_module/composer.json @@ -2,7 +2,7 @@ "name": "modules/gnuboard7-hello_module", "description": "Hello sample module for Gnuboard7 (learning purpose)", "type": "library", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "autoload": { "psr-4": { diff --git a/modules/_bundled/gnuboard7-hello_module/docs/api/README.md b/modules/_bundled/gnuboard7-hello_module/docs/api/README.md index 2bf8b841..1aa7f6ac 100644 --- a/modules/_bundled/gnuboard7-hello_module/docs/api/README.md +++ b/modules/_bundled/gnuboard7-hello_module/docs/api/README.md @@ -4,11 +4,11 @@ > 아래 표는 자동 생성됩니다. 각 문서를 열면 엔드포인트별 파라미터·응답·예시를 볼 수 있습니다. -- **문서 수**: 1 · **엔드포인트 수**: 2 +- **문서 수**: 1 · **엔드포인트 수**: 7 | 문서 | 도메인 | 엔드포인트 | | --- | --- | --- | -| [memos.md](memos.md) | `memos` | 2 | +| [memos.md](memos.md) | `memos` | 7 | diff --git a/modules/_bundled/gnuboard7-hello_module/docs/api/memos.md b/modules/_bundled/gnuboard7-hello_module/docs/api/memos.md index 286c241d..dd5e6022 100644 --- a/modules/_bundled/gnuboard7-hello_module/docs/api/memos.md +++ b/modules/_bundled/gnuboard7-hello_module/docs/api/memos.md @@ -8,8 +8,8 @@ ```text 1. 이 문서는 실제 API 호출로 실측한 Memos 엔드포인트 레퍼런스입니다 -2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표 -3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다 +2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(curl) + 실측 응답 필드 표 + 응답 예시(envelope) +3. 응답 필드의 예시값·응답 예시 JSON 은 실제 호출 응답에서 관측된 값입니다 4. 갱신: 코드 변경 후 php artisan api:docgen 재실행 5. 설명(TODO) 칸은 사람이 채웁니다 ``` @@ -17,6 +17,233 @@ --- +### GET /api/modules/gnuboard7-hello_module/admin/memos + +- **라우트명**: `api.modules.gnuboard7-hello_module.admin.memos.index` +- **컨트롤러**: `Modules\Gnuboard7\HelloModule\Http\Controllers\Admin\MemoController@index` +- **인증/권한**: `auth:sanctum` + `permission:gnuboard7-hello_module.memos.read` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | +| per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | + +**요청 예시** + +```http +GET /api/modules/gnuboard7-hello_module/admin/memos?page=1&per_page=1 HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`gnuboard7-hello_module.memos.read`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | + + + +**설명** + + +### POST /api/modules/gnuboard7-hello_module/admin/memos + +- **라우트명**: `api.modules.gnuboard7-hello_module.admin.memos.store` +- **컨트롤러**: `Modules\Gnuboard7\HelloModule\Http\Controllers\Admin\MemoController@store` +- **인증/권한**: `auth:sanctum` + `permission:gnuboard7-hello_module.memos.create` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| title | body | string | 예 | max 255 | 제목 | +| content | body | string | 예 | — | 본문 내용 | + +**요청 예시** + +```http +POST /api/modules/gnuboard7-hello_module/admin/memos HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +Content-Type: application/json + +{ + "title": "예시 제목", + "content": "예시 내용입니다." +} +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`gnuboard7-hello_module.memos.create`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | + + + +**설명** + + +### DELETE /api/modules/gnuboard7-hello_module/admin/memos/{id} + +- **라우트명**: `api.modules.gnuboard7-hello_module.admin.memos.destroy` +- **컨트롤러**: `Modules\Gnuboard7\HelloModule\Http\Controllers\Admin\MemoController@destroy` +- **인증/권한**: `auth:sanctum` + `permission:gnuboard7-hello_module.memos.delete` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| id | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +DELETE /api/modules/gnuboard7-hello_module/admin/memos/{id} HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`gnuboard7-hello_module.memos.delete`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + +### GET /api/modules/gnuboard7-hello_module/admin/memos/{id} + +- **라우트명**: `api.modules.gnuboard7-hello_module.admin.memos.show` +- **컨트롤러**: `Modules\Gnuboard7\HelloModule\Http\Controllers\Admin\MemoController@show` +- **인증/권한**: `auth:sanctum` + `permission:gnuboard7-hello_module.memos.read` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| id | path | string | 예 | — | 대상 리소스의 식별자 | + +**요청 예시** + +```http +GET /api/modules/gnuboard7-hello_module/admin/memos/{id} HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`gnuboard7-hello_module.memos.read`)이 없는 경우 | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + +### PUT /api/modules/gnuboard7-hello_module/admin/memos/{id} + +- **라우트명**: `api.modules.gnuboard7-hello_module.admin.memos.update` +- **컨트롤러**: `Modules\Gnuboard7\HelloModule\Http\Controllers\Admin\MemoController@update` +- **인증/권한**: `auth:sanctum` + `permission:gnuboard7-hello_module.memos.update` + +**요청 파라미터** + +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| id | path | string | 예 | — | 대상 리소스의 식별자 | +| title | body | string | 예 | max 255 | 제목 | +| content | body | string | 예 | — | 본문 내용 | + +**요청 예시** + +```http +PUT /api/modules/gnuboard7-hello_module/admin/memos/{id} HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer {YOUR_TOKEN} +Content-Type: application/json + +{ + "title": "예시 제목", + "content": "예시 내용입니다." +} +``` + +**응답 필드** (`data` 내부) + + + +**응답 예시** + + + +**에러 응답** + +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 403 | Forbidden | 요구 권한(`gnuboard7-hello_module.memos.update`)이 없는 경우 | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | +| 404 | Not Found | path 파라미터에 해당하는 리소스가 없는 경우 | + + + +**설명** + + ### GET /api/modules/gnuboard7-hello_module/memos - **라우트명**: `api.modules.gnuboard7-hello_module.memos.index` @@ -25,12 +252,15 @@ **요청 파라미터** -_요청 파라미터 없음._ +| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 | +| --- | --- | --- | --- | --- | --- | +| page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | +| per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | **요청 예시** ```http -GET /api/modules/gnuboard7-hello_module/memos HTTP/1.1 +GET /api/modules/gnuboard7-hello_module/memos?page=1&per_page=1 HTTP/1.1 Host: api.example.com Accept: application/json Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생략 가능) @@ -38,11 +268,78 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 **응답 필드** (`data` 내부) - +_목록 응답: `data.data[]` 배열 항목의 필드._ + +| 필드 | 타입 | 실측 예시값 | 용도/설명 | +| --- | --- | --- | --- | +| id | integer | `2` | 기본 키 (내부 식별자) | +| uuid | string | `4473e9ee-ecdf-4ad0-afb3-78ada47265af` | 외부 노출용 UUID (URL/API 식별자, 내부 id 비노출) | +| title | string | `두 번째 메모` | 제목 | +| content | string | `Memo 엔티티의 CRUD 동작을 확인할 수 있는 추가 샘플입니다.` | 본문 내용 | +| created_at | string | `2026-07-31 22:09:15` | 생성 일시 | +| updated_at | string | `2026-07-31 22:09:15` | 최종 수정 일시 | +| abilities | object | `{"can_create":false,"can_update":false,"can_delete":false}` | 현재 사용자가 이 리소스에 수행 가능한 작업 불리언 맵 (can_update, can_delete 등 — 권한 맵 기반) | + +**응답 예시** + +```http +HTTP/1.1 200 +``` + +```json +{ + "success": true, + "message": "메모를 조회했습니다.", + "data": { + "data": [ + { + "id": 2, + "uuid": "4473e9ee-ecdf-4ad0-afb3-78ada47265af", + "title": "두 번째 메모", + "content": "Memo 엔티티의 CRUD 동작을 확인할 수 있는 추가 샘플입니다.", + "created_at": "2026-07-31 22:09:15", + "updated_at": "2026-07-31 22:09:15", + "abilities": { + "can_create": false, + "can_update": false, + "can_delete": false + } + }, + { + "id": 1, + "uuid": "86a29502-24cd-4124-a283-b0454d499fc1", + "title": "환영합니다", + "content": "Hello 모듈의 첫 번째 샘플 메모입니다. 학습용으로 제공됩니다.", + "created_at": "2026-07-31 22:09:15", + "updated_at": "2026-07-31 22:09:15", + "abilities": { + "can_create": false, + "can_update": false, + "can_delete": false + } + }, + "... (총 25건 중 2건 표시)" + ], + "meta": { + "current_page": 1, + "last_page": 2, + "per_page": 25, + "total": 34 + }, + "abilities": { + "can_create": false, + "can_update": false, + "can_delete": false + } + } +} +``` **에러 응답** -_대표 에러 없음 (공개 조회). _ +| 상태코드 | 의미 | 발생 조건 | +| --- | --- | --- | +| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (`error.errors` 에 필드별 메시지) | @@ -96,6 +393,10 @@ Authorization: Bearer {YOUR_TOKEN} (optional.sanctum: 비회원은 헤더 생 +**응답 예시** + + + **에러 응답** | 상태코드 | 의미 | 발생 조건 | diff --git a/modules/_bundled/gnuboard7-hello_module/module.json b/modules/_bundled/gnuboard7-hello_module/module.json index 145cf8a0..70c982db 100644 --- a/modules/_bundled/gnuboard7-hello_module/module.json +++ b/modules/_bundled/gnuboard7-hello_module/module.json @@ -5,7 +5,7 @@ "ko": "Hello 모듈", "en": "Hello Module" }, - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "description": { "ko": "학습용 최소 샘플 모듈 (Memo CRUD)", diff --git a/modules/_bundled/gnuboard7-hello_module/resources/layouts/admin/admin_memo_list.json b/modules/_bundled/gnuboard7-hello_module/resources/layouts/admin/admin_memo_list.json index 1a33695d..30a1c4a4 100644 --- a/modules/_bundled/gnuboard7-hello_module/resources/layouts/admin/admin_memo_list.json +++ b/modules/_bundled/gnuboard7-hello_module/resources/layouts/admin/admin_memo_list.json @@ -92,20 +92,20 @@ ] }, { - "id": "memo_items", + "id": "memo_items_{{idx}}", "type": "basic", "name": "Div", "props": { "className": "row-stack" }, "iteration": { - "data": "{{memos?.data?.data ?? []}}", + "source": "{{memos?.data?.data ?? []}}", "item_var": "memo", "index_var": "idx" }, "children": [ { - "id": "memo_item", + "id": "memo_item_{{idx}}", "type": "basic", "name": "Div", "props": { diff --git a/modules/_bundled/gnuboard7-hello_module/resources/layouts/user/user_memo_list.json b/modules/_bundled/gnuboard7-hello_module/resources/layouts/user/user_memo_list.json index aed6cb63..6f15abcd 100644 --- a/modules/_bundled/gnuboard7-hello_module/resources/layouts/user/user_memo_list.json +++ b/modules/_bundled/gnuboard7-hello_module/resources/layouts/user/user_memo_list.json @@ -51,20 +51,20 @@ "text": "$t:gnuboard7-hello_module.user.memos.index.title" }, { - "id": "user_memo_items", + "id": "user_memo_items_{{idx}}", "type": "basic", "name": "Div", "props": { "className": "space-y-3" }, "iteration": { - "data": "{{memos?.data?.data ?? []}}", + "source": "{{memos?.data?.data ?? []}}", "item_var": "memo", "index_var": "idx" }, "children": [ { - "id": "user_memo_item", + "id": "user_memo_item_{{idx}}", "type": "basic", "name": "Div", "props": { diff --git a/modules/_bundled/gnuboard7-hello_module/src/Http/Controllers/Admin/MemoController.php b/modules/_bundled/gnuboard7-hello_module/src/Http/Controllers/Admin/MemoController.php index cff800ae..bc475359 100644 --- a/modules/_bundled/gnuboard7-hello_module/src/Http/Controllers/Admin/MemoController.php +++ b/modules/_bundled/gnuboard7-hello_module/src/Http/Controllers/Admin/MemoController.php @@ -3,10 +3,11 @@ namespace Modules\Gnuboard7\HelloModule\Http\Controllers\Admin; use App\Http\Controllers\Api\Base\AdminBaseController; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; use Modules\Gnuboard7\HelloModule\Http\Requests\Admin\StoreMemoRequest; use Modules\Gnuboard7\HelloModule\Http\Requests\Admin\UpdateMemoRequest; +use Modules\Gnuboard7\HelloModule\Http\Requests\MemoListRequest; use Modules\Gnuboard7\HelloModule\Http\Resources\MemoCollection; use Modules\Gnuboard7\HelloModule\Http\Resources\MemoResource; use Modules\Gnuboard7\HelloModule\Services\MemoService; @@ -32,13 +33,13 @@ class MemoController extends AdminBaseController /** * 메모 목록을 조회합니다. * - * @param Request $request HTTP 요청 + * @param MemoListRequest $request 목록 조회 요청 (페이지네이션 상·하한 검증) * @return JsonResponse 메모 목록 응답 */ - public function index(Request $request): JsonResponse + public function index(MemoListRequest $request): JsonResponse { try { - $perPage = (int) $request->query('per_page', 10); + $perPage = (int) ($request->validated()['per_page'] ?? 10); $memos = $this->memoService->getMemos($perPage); return $this->success( @@ -65,7 +66,7 @@ class MemoController extends AdminBaseController 'gnuboard7-hello_module::messages.memo.fetch_success', new MemoResource($memo) ); - } catch (\Illuminate\Database\Eloquent\ModelNotFoundException) { + } catch (ModelNotFoundException) { return $this->notFound('gnuboard7-hello_module::messages.memo.not_found'); } catch (\Exception $e) { return $this->error('gnuboard7-hello_module::messages.memo.fetch_failed', 500, $e->getMessage()); @@ -110,7 +111,7 @@ class MemoController extends AdminBaseController 'gnuboard7-hello_module::messages.memo.update_success', new MemoResource($memo) ); - } catch (\Illuminate\Database\Eloquent\ModelNotFoundException) { + } catch (ModelNotFoundException) { return $this->notFound('gnuboard7-hello_module::messages.memo.not_found'); } catch (\Exception $e) { return $this->error('gnuboard7-hello_module::messages.memo.update_failed', 500, $e->getMessage()); @@ -130,7 +131,7 @@ class MemoController extends AdminBaseController $this->memoService->deleteMemo($memo); return $this->success('gnuboard7-hello_module::messages.memo.delete_success'); - } catch (\Illuminate\Database\Eloquent\ModelNotFoundException) { + } catch (ModelNotFoundException) { return $this->notFound('gnuboard7-hello_module::messages.memo.not_found'); } catch (\Exception $e) { return $this->error('gnuboard7-hello_module::messages.memo.delete_failed', 500, $e->getMessage()); diff --git a/modules/_bundled/gnuboard7-hello_module/src/Http/Controllers/Api/MemoController.php b/modules/_bundled/gnuboard7-hello_module/src/Http/Controllers/Api/MemoController.php index 17984b52..e5be3173 100644 --- a/modules/_bundled/gnuboard7-hello_module/src/Http/Controllers/Api/MemoController.php +++ b/modules/_bundled/gnuboard7-hello_module/src/Http/Controllers/Api/MemoController.php @@ -3,8 +3,9 @@ namespace Modules\Gnuboard7\HelloModule\Http\Controllers\Api; use App\Http\Controllers\Api\Base\PublicBaseController; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; +use Modules\Gnuboard7\HelloModule\Http\Requests\MemoListRequest; use Modules\Gnuboard7\HelloModule\Http\Resources\MemoCollection; use Modules\Gnuboard7\HelloModule\Http\Resources\MemoResource; use Modules\Gnuboard7\HelloModule\Services\MemoService; @@ -30,13 +31,13 @@ class MemoController extends PublicBaseController /** * 메모 목록을 조회합니다. * - * @param Request $request HTTP 요청 + * @param MemoListRequest $request 목록 조회 요청 (페이지네이션 상·하한 검증) * @return JsonResponse 메모 목록 응답 */ - public function index(Request $request): JsonResponse + public function index(MemoListRequest $request): JsonResponse { try { - $perPage = (int) $request->query('per_page', 10); + $perPage = (int) ($request->validated()['per_page'] ?? 10); $memos = $this->memoService->getMemos($perPage); return $this->success( @@ -63,7 +64,7 @@ class MemoController extends PublicBaseController 'gnuboard7-hello_module::messages.memo.fetch_success', new MemoResource($memo) ); - } catch (\Illuminate\Database\Eloquent\ModelNotFoundException) { + } catch (ModelNotFoundException) { return $this->notFound('gnuboard7-hello_module::messages.memo.not_found'); } catch (\Exception $e) { return $this->error('gnuboard7-hello_module::messages.memo.fetch_failed', 500, $e->getMessage()); diff --git a/modules/_bundled/gnuboard7-hello_module/src/Http/Requests/MemoListRequest.php b/modules/_bundled/gnuboard7-hello_module/src/Http/Requests/MemoListRequest.php new file mode 100644 index 00000000..0c020bad --- /dev/null +++ b/modules/_bundled/gnuboard7-hello_module/src/Http/Requests/MemoListRequest.php @@ -0,0 +1,40 @@ + ['nullable', 'integer', 'min:1'], + 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], + ]; + } +} diff --git a/modules/_bundled/gnuboard7-hello_module/src/Repositories/MemoRepository.php b/modules/_bundled/gnuboard7-hello_module/src/Repositories/MemoRepository.php index deaeb6ea..76f97ca3 100644 --- a/modules/_bundled/gnuboard7-hello_module/src/Repositories/MemoRepository.php +++ b/modules/_bundled/gnuboard7-hello_module/src/Repositories/MemoRepository.php @@ -22,6 +22,8 @@ class MemoRepository implements MemoRepositoryInterface { return Memo::query() ->orderByDesc('created_at') + // 전순서 보장 — created_at 동률에서 페이지 경계가 흔들리지 않도록 기본키를 덧붙인다 + ->orderByDesc('id') // audit:allow repository-paginate-column-pruning reason: 학습용 샘플 확장의 메모 테이블 — // 넓은 컬럼(text/JSON)이 없고, 지연 조인을 넣으면 샘플 코드의 학습 목적이 흐려진다 ->paginate($perPage); diff --git a/modules/_bundled/gnuboard7-hello_module/src/routes/api.php b/modules/_bundled/gnuboard7-hello_module/src/routes/api.php index 828d6c06..0e21e234 100644 --- a/modules/_bundled/gnuboard7-hello_module/src/routes/api.php +++ b/modules/_bundled/gnuboard7-hello_module/src/routes/api.php @@ -1,17 +1,26 @@ middleware(['optional.sanctum', 'throttle:600,1']) ->name('memos.') @@ -21,3 +30,34 @@ Route::prefix('memos') ->whereNumber('id') ->name('show'); }); + +/* +| 관리자 CRUD API — 인증 + 권한 미들웨어로 보호 +*/ +Route::prefix('admin/memos') + ->middleware(['auth:sanctum', 'throttle:600,1']) + ->name('admin.memos.') + ->group(function () { + Route::get('/', [AdminMemoController::class, 'index']) + ->middleware('permission:admin,gnuboard7-hello_module.memos.read') + ->name('index'); + + Route::post('/', [AdminMemoController::class, 'store']) + ->middleware('permission:admin,gnuboard7-hello_module.memos.create') + ->name('store'); + + Route::get('/{id}', [AdminMemoController::class, 'show']) + ->whereNumber('id') + ->middleware('permission:admin,gnuboard7-hello_module.memos.read') + ->name('show'); + + Route::put('/{id}', [AdminMemoController::class, 'update']) + ->whereNumber('id') + ->middleware('permission:admin,gnuboard7-hello_module.memos.update') + ->name('update'); + + Route::delete('/{id}', [AdminMemoController::class, 'destroy']) + ->whereNumber('id') + ->middleware('permission:admin,gnuboard7-hello_module.memos.delete') + ->name('destroy'); + }); diff --git a/modules/_bundled/gnuboard7-hello_module/src/routes/web.php b/modules/_bundled/gnuboard7-hello_module/src/routes/web.php index ec8e8847..a151f011 100644 --- a/modules/_bundled/gnuboard7-hello_module/src/routes/web.php +++ b/modules/_bundled/gnuboard7-hello_module/src/routes/web.php @@ -1,43 +1,20 @@ middleware(['auth:sanctum', 'throttle:600,1']) - ->name('admin.memos.') - ->group(function () { - Route::get('/', [MemoController::class, 'index']) - ->middleware('permission:admin,gnuboard7-hello_module.memos.read') - ->name('index'); - - Route::post('/', [MemoController::class, 'store']) - ->middleware('permission:admin,gnuboard7-hello_module.memos.create') - ->name('store'); - - Route::get('/{id}', [MemoController::class, 'show']) - ->whereNumber('id') - ->middleware('permission:admin,gnuboard7-hello_module.memos.read') - ->name('show'); - - Route::put('/{id}', [MemoController::class, 'update']) - ->whereNumber('id') - ->middleware('permission:admin,gnuboard7-hello_module.memos.update') - ->name('update'); - - Route::delete('/{id}', [MemoController::class, 'destroy']) - ->whereNumber('id') - ->middleware('permission:admin,gnuboard7-hello_module.memos.delete') - ->name('destroy'); - }); diff --git a/modules/_bundled/sirsoft-board/CHANGELOG.md b/modules/_bundled/sirsoft-board/CHANGELOG.md index f7932f3e..291890e7 100644 --- a/modules/_bundled/sirsoft-board/CHANGELOG.md +++ b/modules/_bundled/sirsoft-board/CHANGELOG.md @@ -23,6 +23,7 @@ - 게시글 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 게시글의 본문 앞부분까지 함께 읽어 느려졌지만, 이제 현재 페이지의 게시글만 본문 미리보기를 읽습니다. 공지 노출·답글 표시·다음 페이지 버튼은 이전과 동일하게 동작합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 신고 관리 목록도 같은 방식으로 개선했습니다. 신고 건수·대상 글 상태처럼 목록에 함께 표시되는 정보를 예전에는 건너뛰는 신고까지 모두 계산했지만, 이제 현재 페이지의 신고에 대해서만 계산합니다. 신고가 많이 쌓인 사이트에서 목록 페이지 이동이 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 마이페이지의 「내가 쓴 댓글」·「내가 댓글 단 글」 목록도 같은 방식으로 개선했습니다. 각 글의 최근 댓글을 예전에는 건너뛰는 글까지 모두 찾아봤지만, 이제 현재 페이지의 글에 대해서만 찾습니다. 댓글 활동이 많은 회원의 목록 페이지 이동이 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 게시글이 많은 게시판에서 목록 정렬이 더 빨라지도록 색인을 정비했습니다. 신고 관리 목록에도 같은 정비를 적용했습니다. 같은 시각에 등록된 게시글이 많은 구간에서 순서를 정하느라 생기던 추가 작업이 사라집니다. 기존 사이트도 업데이트 시 자동으로 반영되며, 게시글이 아주 많은 경우 이 과정에 수 분이 걸리고 그동안 글쓰기가 잠시 대기할 수 있습니다. - 개선된 사이트맵 생성 기능을 사용하기 위해 이 모듈은 이제 코어 7.0.6 이상이 필요합니다. ### Fixed @@ -35,13 +36,19 @@ - 데이터베이스에서 일부 컬럼의 설명이 비어 있던 문제를 바로잡았습니다. 회원·작성자 등 다른 표를 가리키는 컬럼이 대상이며, 업그레이드하면 이미 운영 중인 데이터베이스에도 설명이 채워집니다(직접 적어 넣은 설명은 그대로 둡니다). - 관리자 게시글 목록에서 글을 열어 이전글·다음글로 넘어가거나 수정 후 돌아올 때 보고 있던 페이지·검색어·분류가 사라지던 문제를 수정했습니다. 목록 ↔ 상세 ↔ 이전/다음 글 ↔ 작성·수정 폼 어디로 오가든 목록 상태가 유지되며, 신고 관리와 게시판 목록도 동일하게 개선됐습니다. 새로고침 버튼도 걸어 둔 검색·필터를 지운 채 새로 부르지 않고 보고 있던 목록을 그대로 다시 부릅니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) - 관리자 게시글 목록에서 뒤쪽 페이지를 보던 중 검색하거나 게시 상태를 바꾸면 그 페이지 번호가 그대로 유지돼 결과가 없는 빈 화면이 열리던 문제를 수정했습니다. 검색과 상태 변경은 1페이지부터 보여주며, 사용자가 고른 표시 개수와 정렬은 그대로 유지됩니다. 검색창에서 엔터를 치는 경우와 검색 버튼을 누르는 경우가 서로 다르게 동작하던 것도 함께 맞췄습니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) +- 신고 상세의 신고자 목록에서 한 페이지에 보여줄 개수를 0 이나 음수로 요청하면 오류가 나던 문제를 수정했습니다. +- 게시판 관리 목록과 신고 처리 로그에서 같은 시각에 등록된 항목이 여러 건일 때, 페이지를 넘기면 같은 항목이 두 번 보이고 다른 항목은 아예 보이지 않던 문제를 수정했습니다. - 첨부파일을 사용하지 않는 게시판을 아무것도 바꾸지 않고 저장해도 "허용 파일 확장자를 최소 1개 이상 입력하세요" 오류가 나며 저장이 막히던 문제를 수정했습니다. 첨부를 쓰지 않는 게시판에서는 허용 확장자를 비워 두어도 정상적으로 저장됩니다. 게시판 환경설정의 기본값도 동일하게 동작합니다. (#78 @glitter-gim 님께서 제보해주셨습니다.) - 마이페이지 「내가 댓글 단 글」에서 검색어를 입력하면 목록이 열리지 않고 오류가 나던 문제를 수정했습니다. 이제 댓글 내용으로 정상 검색됩니다. - 새 글 표시 기간을 "0시간"(새 글 표시 안 함)으로 저장할 수 없던 문제를 수정했습니다. 0은 원래 새 글 배지를 끄는 값으로 동작하도록 되어 있었으나 저장 단계에서 막혀 있었습니다. - 게시판 환경설정에서 제목·댓글 최소 길이를 0(제한 없음)으로 저장할 수 없던 문제를 수정했습니다. 내용 최소 길이는 0이 허용되어 같은 화면에서 항목마다 동작이 달랐습니다. - 댓글 계층 정리 시 서로가 서로의 부모로 얽혀 최상위 댓글에서 닿을 수 없는 댓글이 아무 기록 없이 넘어가던 문제를 수정했습니다. 이런 댓글은 내용과 위치를 그대로 보존하되, 정리되지 않았다는 사실이 업데이트 기록에 남아 확인할 수 있습니다. +- 마이페이지 활동 목록에서 한 페이지에 표시할 개수를 0이나 음수로 요청하면 서버 오류가 나던 문제를 수정했습니다. 이제 허용 범위(1~100)로 자동 조정되며, 지나치게 큰 값도 상한으로 잘립니다. +- 게시판 신고현황 목록에서 뒤쪽 페이지를 보던 중 정렬을 바꾸면 이전 페이지 번호에 그대로 머물러 엉뚱한 구간이 보이던 문제를 수정했습니다. 이제 정렬을 바꾸면 첫 페이지부터 보여줍니다. - 허용 확장자를 지정하지 않은 게시판에서 어떤 파일도 첨부할 수 없던 문제를 수정했습니다. 파일 선택 단계에서 막히던 것과 서버에서 거부되던 것을 함께 고쳐, 확장자를 지정하지 않은 게시판은 기본 확장자 목록을 사용합니다. 첨부 안내에 확장자가 비어 표시되던 것도 함께 해소됩니다. 확장자를 지정한 게시판은 지금처럼 목록에 없는 파일을 계속 거부합니다. - 게시판 알림 설정에서 저장에 실패해도 아무 안내 없이 넘어가던 화면을 수정했습니다. 실패하면 그 이유가 화면에 표시됩니다. +- 신고 상세의 신고자 목록에서 한 페이지에 표시할 개수에 상한이 없어 신고자 전체를 한 번에 불러올 수 있던 문제를 수정했습니다. 이제 다른 목록과 같은 상한(100)이 적용됩니다. +- 게시판 유형 상세를 조회한 기록이 활동 로그에 내부 식별자로 표시되던 문제를 수정했습니다. - 일부 안내 문구가 실제 문장 대신 내부 식별자(`sirsoft-board::messages....` 같은 문자열)로 표시되던 문제를 수정했습니다. 첨부파일이 있는 게시판을 찾지 못했을 때, 댓글 목록을 불러오지 못했을 때, 비회원 댓글의 비밀번호를 확인할 때, 삭제·블라인드된 글에 댓글을 달려 할 때, 권한이 없어 거절될 때가 해당됩니다. - 비밀글을 쓸 수 없는 게시판에서 비밀글로 저장하려 할 때, 답글 대상 게시판을 찾지 못했을 때, 신고 상태를 바꿀 수 없는 상태로 변경하려 할 때 나오던 안내가 정상 문장으로 표시됩니다. - 댓글 수정, 신고 접수·처리, 블라인드·복원, 게시판 설정 일괄 적용 화면에서 오류 안내에 항목 이름이 영문 필드명으로 나오던 것을 한글 항목명으로 바로잡았습니다. diff --git a/modules/_bundled/sirsoft-board/database/migrations/2026_08_01_000001_add_tiebreak_to_board_posts_list_index.php b/modules/_bundled/sirsoft-board/database/migrations/2026_08_01_000001_add_tiebreak_to_board_posts_list_index.php new file mode 100644 index 00000000..1ff8b1ad --- /dev/null +++ b/modules/_bundled/sirsoft-board/database/migrations/2026_08_01_000001_add_tiebreak_to_board_posts_list_index.php @@ -0,0 +1,87 @@ + */ + private const OLD_COLUMNS = ['board_id', 'is_notice', 'parent_id', 'deleted_at', 'created_at']; + + /** @var array */ + private const NEW_COLUMNS = ['board_id', 'is_notice', 'parent_id', 'deleted_at', 'created_at', 'id']; + + /** + * Run the migrations. + */ + public function up(): void + { + if (! Schema::hasTable(self::TABLE)) { + return; + } + + $existing = array_column(Schema::getIndexes(self::TABLE), 'name'); + + Schema::table(self::TABLE, function (Blueprint $table) use ($existing) { + if (! in_array(self::NEW_INDEX, $existing, true)) { + $table->index(self::NEW_COLUMNS, self::NEW_INDEX); + } + + // 새 인덱스가 기존 인덱스의 상위집합이므로 남겨 두면 쓰기 비용만 늘어난다 + if (in_array(self::OLD_INDEX, $existing, true)) { + $table->dropIndex(self::OLD_INDEX); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (! Schema::hasTable(self::TABLE)) { + return; + } + + $existing = array_column(Schema::getIndexes(self::TABLE), 'name'); + + Schema::table(self::TABLE, function (Blueprint $table) use ($existing) { + if (! in_array(self::OLD_INDEX, $existing, true)) { + $table->index(self::OLD_COLUMNS, self::OLD_INDEX); + } + + if (in_array(self::NEW_INDEX, $existing, true)) { + $table->dropIndex(self::NEW_INDEX); + } + }); + } +}; diff --git a/modules/_bundled/sirsoft-board/database/migrations/2026_08_01_000002_add_list_sort_index_to_boards_reports_table.php b/modules/_bundled/sirsoft-board/database/migrations/2026_08_01_000002_add_list_sort_index_to_boards_reports_table.php new file mode 100644 index 00000000..f2fd91f7 --- /dev/null +++ b/modules/_bundled/sirsoft-board/database/migrations/2026_08_01_000002_add_list_sort_index_to_boards_reports_table.php @@ -0,0 +1,61 @@ + */ + private const INDEX_COLUMNS = ['deleted_at', 'created_at', 'id']; + + /** + * Run the migrations. + */ + public function up(): void + { + if (! Schema::hasTable(self::TABLE)) { + return; + } + + $existing = array_column(Schema::getIndexes(self::TABLE), 'name'); + + Schema::table(self::TABLE, function (Blueprint $table) use ($existing) { + if (! in_array(self::INDEX_NAME, $existing, true)) { + $table->index(self::INDEX_COLUMNS, self::INDEX_NAME); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (! Schema::hasTable(self::TABLE)) { + return; + } + + $existing = array_column(Schema::getIndexes(self::TABLE), 'name'); + + Schema::table(self::TABLE, function (Blueprint $table) use ($existing) { + if (in_array(self::INDEX_NAME, $existing, true)) { + $table->dropIndex(self::INDEX_NAME); + } + }); + } +}; diff --git a/modules/_bundled/sirsoft-board/docs/api/board-activities.md b/modules/_bundled/sirsoft-board/docs/api/board-activities.md index 0cd00961..026e6c34 100644 --- a/modules/_bundled/sirsoft-board/docs/api/board-activities.md +++ b/modules/_bundled/sirsoft-board/docs/api/board-activities.md @@ -133,6 +133,6 @@ HTTP/1.1 200 -**설명** 로그인한 회원 본인의 게시글 활동을 마이페이지에 표시하기 위해 반환합니다. `auth:sanctum` 인증이 필요한 회원 전용 엔드포인트로, 대상 사용자는 항상 인증된 본인(`Auth::id()`)입니다. `board_slug`·`search`·`activity_type`·`sort`(latest/oldest/views) 필터와 `per_page`(기본 20) 페이지네이션을 지원하며, 응답에는 적용된 필터가 `query` 로 함께 담깁니다. +**설명** 로그인한 회원 본인의 게시글 활동을 마이페이지에 표시하기 위해 반환합니다. `auth:sanctum` 인증이 필요한 회원 전용 엔드포인트로, 대상 사용자는 항상 인증된 본인(`Auth::id()`)입니다. `board_slug`·`search`·`activity_type`·`sort`(latest/oldest/views) 필터와 `per_page`(기본 20, 허용 범위 1~100) 페이지네이션을 지원하며, 응답에는 적용된 필터가 `query` 로 함께 담깁니다. 범위를 벗어난 `per_page` 는 오류 대신 경계값으로 잘립니다(0·음수 → 1, 100 초과 → 100). diff --git a/modules/_bundled/sirsoft-board/module.php b/modules/_bundled/sirsoft-board/module.php index 794dc1b1..22fd34a3 100644 --- a/modules/_bundled/sirsoft-board/module.php +++ b/modules/_bundled/sirsoft-board/module.php @@ -873,8 +873,11 @@ class Module extends AbstractModule 'trigger_type', 'ip_address', 'created_at', 'updated_at', 'deleted_at', ], 'order' => [['created_at', 'desc'], ['id', 'desc']], - 'filters' => ['board_id' => 1], - 'seed_overrides' => ['board_id' => 1, 'is_notice' => 0], + // 실제 목록 쿼리(PostRepository::buildSortedPostList)는 공지와 답글을 빼고 + // 원글만 페이지네이션한다. 필터를 board_id 만 걸면 등치 사슬이 is_notice 에서 + // 끊겨 옵티마이저가 다른 인덱스를 골라, 제품이 실행하지 않는 실행 계획을 재게 된다. + 'filters' => ['board_id' => 1, 'is_notice' => 0, 'parent_id' => null], + 'seed_overrides' => ['board_id' => 1, 'is_notice' => 0, 'parent_id' => null], 'soft_delete' => true, ], // 댓글은 계측 프로파일을 두지 않는다. 페이지네이션되는 댓글 목록은 회원 본인 댓글 diff --git a/modules/_bundled/sirsoft-board/resources/lang/en/activity_log.php b/modules/_bundled/sirsoft-board/resources/lang/en/activity_log.php deleted file mode 100644 index 99ab05e7..00000000 --- a/modules/_bundled/sirsoft-board/resources/lang/en/activity_log.php +++ /dev/null @@ -1,55 +0,0 @@ - [ - // Board management (Admin) - 'board_index' => 'Board list viewed', - 'board_show' => 'Board details viewed (:board_name)', - 'board_create' => 'Board created (:board_name)', - 'board_update' => 'Board updated (:board_name)', - 'board_delete' => 'Board deleted (:board_name)', - 'board_copy' => 'Board copied (:board_name)', - 'board_add_to_menu' => 'Board added to menu (:board_name)', - - // Board type (Admin) - 'board_type_index' => 'Board type list viewed', - 'board_type_show' => 'Board type details viewed (:type_name)', - 'board_type_create' => 'Board type created (:type_name)', - 'board_type_update' => 'Board type updated (:type_name)', - 'board_type_delete' => 'Board type deleted (:type_name)', - - // Posts - 'post_create' => 'Post created (Board: :board_name, Title: :title)', - 'post_update' => 'Post updated (Board: :board_name, Title: :title)', - 'post_delete' => 'Post deleted (Board: :board_name, ID: :post_id)', - 'post_blind' => 'Post blinded (Board: :board_name, ID: :post_id)', - 'post_restore' => 'Post restored (Board: :board_name, ID: :post_id)', - - // Comments - 'comment_create' => 'Comment created (Board: :board_name, Post: :post_id)', - 'comment_update' => 'Comment updated (ID: :comment_id)', - 'comment_delete' => 'Comment deleted (ID: :comment_id)', - 'comment_blind' => 'Comment blinded (ID: :comment_id)', - 'comment_restore' => 'Comment restored (ID: :comment_id)', - - // Attachments - 'board_attachment_upload' => 'Attachment uploaded (Post: :post_id)', - 'board_attachment_delete' => 'Attachment deleted (Post: :post_id)', - 'board_attachment_download' => 'Attachment downloaded (Post: :post_id)', - 'board_attachment_reorder' => 'Attachment order changed (Post: :post_id)', - - // Report management (Admin) - 'report_create' => 'Report created (ID: :report_id)', - 'report_update_status' => 'Report status changed (ID: :report_id)', - 'report_bulk_update_status' => 'Report statuses bulk changed (:count items)', - 'report_delete' => 'Report deleted (ID: :report_id)', - 'report_restore_content' => 'Report content restored (ID: :report_id)', - 'report_blind_content' => 'Report content blinded (ID: :report_id)', - 'report_delete_content' => 'Report content deleted (ID: :report_id)', - - // Board settings - 'board_settings_index' => 'Board settings viewed', - 'board_settings_bulk_apply' => 'Board settings bulk applied', - 'board_settings_bulk_apply_aborted' => 'Board settings bulk apply aborted (full rollback, failed board: :failed_board_name, :failed_at/:total)', - ], -]; diff --git a/modules/_bundled/sirsoft-board/resources/lang/ko/activity_log.php b/modules/_bundled/sirsoft-board/resources/lang/ko/activity_log.php deleted file mode 100644 index 3b792f58..00000000 --- a/modules/_bundled/sirsoft-board/resources/lang/ko/activity_log.php +++ /dev/null @@ -1,55 +0,0 @@ - [ - // 게시판 관리 (Admin) - 'board_index' => '게시판 목록 조회', - 'board_show' => '게시판 상세 조회 (:board_name)', - 'board_create' => '게시판 생성 (:board_name)', - 'board_update' => '게시판 수정 (:board_name)', - 'board_delete' => '게시판 삭제 (:board_name)', - 'board_copy' => '게시판 복사 (:board_name)', - 'board_add_to_menu' => '게시판 메뉴 추가 (:board_name)', - - // 게시판 유형 (Admin) - 'board_type_index' => '게시판 유형 목록 조회', - 'board_type_show' => '게시판 유형 상세 조회 (:type_name)', - 'board_type_create' => '게시판 유형 생성 (:type_name)', - 'board_type_update' => '게시판 유형 수정 (:type_name)', - 'board_type_delete' => '게시판 유형 삭제 (:type_name)', - - // 게시물 - 'post_create' => '게시물 작성 (게시판: :board_name, 제목: :title)', - 'post_update' => '게시물 수정 (게시판: :board_name, 제목: :title)', - 'post_delete' => '게시물 삭제 (게시판: :board_name, ID: :post_id)', - 'post_blind' => '게시물 블라인드 (게시판: :board_name, ID: :post_id)', - 'post_restore' => '게시물 복원 (게시판: :board_name, ID: :post_id)', - - // 댓글 - 'comment_create' => '댓글 작성 (게시판: :board_name, 게시물: :post_id)', - 'comment_update' => '댓글 수정 (ID: :comment_id)', - 'comment_delete' => '댓글 삭제 (ID: :comment_id)', - 'comment_blind' => '댓글 블라인드 (ID: :comment_id)', - 'comment_restore' => '댓글 복원 (ID: :comment_id)', - - // 첨부파일 - 'board_attachment_upload' => '첨부파일 업로드 (게시물: :post_id)', - 'board_attachment_delete' => '첨부파일 삭제 (게시물: :post_id)', - 'board_attachment_download' => '첨부파일 다운로드 (게시물: :post_id)', - 'board_attachment_reorder' => '첨부파일 순서 변경 (게시물: :post_id)', - - // 신고 관리 (Admin) - 'report_create' => '신고 접수 (ID: :report_id)', - 'report_update_status' => '신고 상태 변경 (ID: :report_id)', - 'report_bulk_update_status' => '신고 일괄 상태 변경 (:count건)', - 'report_delete' => '신고 삭제 (ID: :report_id)', - 'report_restore_content' => '신고 콘텐츠 복원 (ID: :report_id)', - 'report_blind_content' => '신고 콘텐츠 블라인드 (ID: :report_id)', - 'report_delete_content' => '신고 콘텐츠 삭제 (ID: :report_id)', - - // 게시판 설정 - 'board_settings_index' => '게시판 설정 조회', - 'board_settings_bulk_apply' => '게시판 설정 일괄 적용', - 'board_settings_bulk_apply_aborted' => '게시판 설정 일괄 적용 중단 (전체 롤백, 실패 게시판: :failed_board_name, :failed_at/:total)', - ], -]; diff --git a/modules/_bundled/sirsoft-board/resources/layouts/admin/admin_board_reports_index.json b/modules/_bundled/sirsoft-board/resources/layouts/admin/admin_board_reports_index.json index b5636299..43d337e9 100644 --- a/modules/_bundled/sirsoft-board/resources/layouts/admin/admin_board_reports_index.json +++ b/modules/_bundled/sirsoft-board/resources/layouts/admin/admin_board_reports_index.json @@ -2164,10 +2164,13 @@ "params": { "path": "/admin/boards/reports", "mergeQuery": true, + "replace": true, "query": { "sort_by": "{{$args[0]}}", - "sort_order": "{{$args[1]}}" - } + "sort_order": "{{$args[1]}}", + "page": 1 + }, + "transition_overlay_target": "reports_data_grid__body" } } ] diff --git a/modules/_bundled/sirsoft-board/src/Http/Controllers/Admin/ReportController.php b/modules/_bundled/sirsoft-board/src/Http/Controllers/Admin/ReportController.php index dff2f3fb..8d0c4bbb 100644 --- a/modules/_bundled/sirsoft-board/src/Http/Controllers/Admin/ReportController.php +++ b/modules/_bundled/sirsoft-board/src/Http/Controllers/Admin/ReportController.php @@ -5,12 +5,12 @@ namespace Modules\Sirsoft\Board\Http\Controllers\Admin; use App\Http\Controllers\Api\Base\AdminBaseController; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; +use Modules\Sirsoft\Board\Enums\ReportStatus; use Modules\Sirsoft\Board\Exceptions\DeletedReportStatusChangeException; use Modules\Sirsoft\Board\Http\Requests\Admin\IndexReportRequest; use Modules\Sirsoft\Board\Http\Requests\Admin\ReportersRequest; use Modules\Sirsoft\Board\Http\Requests\Admin\StatusCountsRequest; use Modules\Sirsoft\Board\Http\Requests\BulkUpdateStatusRequest; -use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Modules\Sirsoft\Board\Http\Requests\UpdateStatusRequest; use Modules\Sirsoft\Board\Http\Resources\ReportCollection; use Modules\Sirsoft\Board\Http\Resources\ReportDetailResource; @@ -18,6 +18,7 @@ use Modules\Sirsoft\Board\Http\Resources\ReportLogResource; use Modules\Sirsoft\Board\Http\Resources\ReportResource; use Modules\Sirsoft\Board\Services\ReportService; use Modules\Sirsoft\Board\Traits\ChecksBoardPermission; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** * 관리자용 신고 관리 컨트롤러 @@ -27,6 +28,7 @@ use Modules\Sirsoft\Board\Traits\ChecksBoardPermission; class ReportController extends AdminBaseController { use ChecksBoardPermission; + /** * ReportController 생성자 * @@ -127,7 +129,7 @@ class ReportController extends AdminBaseController /** * 신고 케이스의 신고자 목록을 페이지네이션으로 반환합니다. * - * @param int $id 신고 케이스 ID + * @param int $id 신고 케이스 ID * @param ReportersRequest $request 신고자 목록 조회 요청 * @return JsonResponse 신고자 목록 응답 */ @@ -138,7 +140,9 @@ class ReportController extends AdminBaseController return $this->forbidden('sirsoft-board::messages.reports.permission_denied'); } - $perPage = min((int) $request->query('per_page', 10), 50); + // 상한만으로는 0/음수를 막지 못한다 — 페이지네이터에서 나눗셈 오류(500)가 되거나 + // 음수 per_page 가 그대로 응답 메타에 실린다. 하한을 함께 닫는다. + $perPage = min(max((int) $request->query('per_page', 10), 1), 50); $page = max((int) $request->query('page', 1), 1); $paginator = $this->reportService->paginateReporters($id, $perPage, $page); @@ -148,12 +152,12 @@ class ReportController extends AdminBaseController [ 'data' => ReportLogResource::collection($paginator->items())->resolve(), 'pagination' => [ - 'total' => $paginator->total(), - 'from' => $paginator->firstItem() ?? 0, - 'to' => $paginator->lastItem() ?? 0, - 'per_page' => $paginator->perPage(), + 'total' => $paginator->total(), + 'from' => $paginator->firstItem() ?? 0, + 'to' => $paginator->lastItem() ?? 0, + 'per_page' => $paginator->perPage(), 'current_page' => $paginator->currentPage(), - 'last_page' => $paginator->lastPage(), + 'last_page' => $paginator->lastPage(), ], ] ); @@ -258,7 +262,7 @@ class ReportController extends AdminBaseController $affectedCount = $result['affected_count']; // 상태명 다국어 키 가져오기 - $statusEnum = \Modules\Sirsoft\Board\Enums\ReportStatus::from($targetStatus); + $statusEnum = ReportStatus::from($targetStatus); $statusLabel = $statusEnum->label(); // 성공 메시지 생성 diff --git a/modules/_bundled/sirsoft-board/src/Http/Controllers/User/BoardController.php b/modules/_bundled/sirsoft-board/src/Http/Controllers/User/BoardController.php index 0a52d9da..d2eb02c1 100644 --- a/modules/_bundled/sirsoft-board/src/Http/Controllers/User/BoardController.php +++ b/modules/_bundled/sirsoft-board/src/Http/Controllers/User/BoardController.php @@ -4,7 +4,10 @@ namespace Modules\Sirsoft\Board\Http\Controllers\User; use App\Http\Controllers\Api\Base\PublicBaseController; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; +use Modules\Sirsoft\Board\Http\Requests\User\IndexBoardRequest; +use Modules\Sirsoft\Board\Http\Requests\User\PopularBoardsRequest; +use Modules\Sirsoft\Board\Http\Requests\User\PopularPostsRequest; +use Modules\Sirsoft\Board\Http\Requests\User\RecentPostsRequest; use Modules\Sirsoft\Board\Http\Resources\BoardMenuResource; use Modules\Sirsoft\Board\Http\Resources\BoardResource; use Modules\Sirsoft\Board\Services\BoardService; @@ -37,16 +40,16 @@ class BoardController extends PublicBaseController * - 각 게시판별 최신글 N개 포함 (답변글, 삭제, 블라인드 제외) * - limit 파라미터로 게시글 개수 조절 가능 (기본: 0, 최대: 10) * - * @param Request $request + * @param IndexBoardRequest $request * @return JsonResponse */ - public function index(Request $request): JsonResponse + public function index(IndexBoardRequest $request): JsonResponse { // 활성화된 게시판만 조회 $boards = $this->boardService->getActiveBoards(); // limit 파라미터 (0이면 최신글 미포함, 최대 10개) - $recentPostsLimit = min(max((int) $request->input('limit', 0), 0), 10); + $recentPostsLimit = $request->recentPostsLimit(); // toListArray() 메서드를 사용하여 경량 데이터 반환 $data = $boards->map(function ($board) use ($recentPostsLimit) { @@ -128,14 +131,12 @@ class BoardController extends PublicBaseController * 모든 게시판의 최근 게시글을 통합하여 반환합니다. * - 기본 5개, 최대 20개까지 조회 가능 * - * @param Request $request + * @param RecentPostsRequest $request * @return JsonResponse */ - public function recentPosts(Request $request): JsonResponse + public function recentPosts(RecentPostsRequest $request): JsonResponse { - $limit = min((int) $request->input('limit', 5), 20); - - return $this->success('common.success', $this->boardService->getCachedRecentPosts($limit)); + return $this->success('common.success', $this->boardService->getCachedRecentPosts($request->limit())); } /** @@ -146,19 +147,16 @@ class BoardController extends PublicBaseController * - 기간별 필터링: today, week, month, year (기본: week) * - period=all은 하위 호환을 위해 year로 매핑 * - * @param Request $request + * @param PopularPostsRequest $request * @return JsonResponse */ - public function popular(Request $request): JsonResponse + public function popular(PopularPostsRequest $request): JsonResponse { - $period = $request->input('period', 'week'); - // all → year 매핑 (하위 호환) - if ($period === 'all') { - $period = 'year'; - } - $limit = min((int) $request->input('limit', 20), 50); - - return $this->success('common.success', $this->boardService->getCachedPopularPosts($period, $limit)); + // period 의 all → year 매핑(하위 호환)은 FormRequest 접근자가 담당한다. + return $this->success( + 'common.success', + $this->boardService->getCachedPopularPosts($request->period(), $request->limit()) + ); } /** @@ -167,13 +165,11 @@ class BoardController extends PublicBaseController * 게시글 수가 많은 순으로 게시판 목록을 반환합니다. * - 기본 4개, 최대 20개까지 조회 가능 * - * @param Request $request + * @param PopularBoardsRequest $request * @return JsonResponse */ - public function popularBoards(Request $request): JsonResponse + public function popularBoards(PopularBoardsRequest $request): JsonResponse { - $limit = min((int) $request->input('limit', 4), 20); - - return $this->success('common.success', $this->boardService->getCachedPopularBoards($limit)); + return $this->success('common.success', $this->boardService->getCachedPopularBoards($request->limit())); } } diff --git a/modules/_bundled/sirsoft-board/src/Http/Controllers/User/ReportController.php b/modules/_bundled/sirsoft-board/src/Http/Controllers/User/ReportController.php index 9e085027..acacfb44 100644 --- a/modules/_bundled/sirsoft-board/src/Http/Controllers/User/ReportController.php +++ b/modules/_bundled/sirsoft-board/src/Http/Controllers/User/ReportController.php @@ -11,7 +11,7 @@ use Modules\Sirsoft\Board\Enums\ReportType; use Modules\Sirsoft\Board\Exceptions\DuplicateReportException; use Modules\Sirsoft\Board\Http\Requests\StoreReportRequest; use Modules\Sirsoft\Board\Http\Resources\ReportResource; -use Modules\Sirsoft\Board\Models\Board; +use Modules\Sirsoft\Board\Services\BoardService; use Modules\Sirsoft\Board\Services\BoardSettingsService; use Modules\Sirsoft\Board\Services\ReportService; @@ -26,9 +26,11 @@ class ReportController extends AuthBaseController * ReportController 생성자 * * @param ReportService $reportService 신고 서비스 + * @param BoardService $boardService 게시판 서비스 (slug → 게시판 해석) */ public function __construct( - private ReportService $reportService + private ReportService $reportService, + private BoardService $boardService ) { parent::__construct(); } @@ -75,7 +77,12 @@ class ReportController extends AuthBaseController ReportType $targetType ): JsonResponse { try { - $board = Board::where('slug', $slug)->firstOrFail(); + // 신고는 게시판 읽기 스코프와 무관하게 slug 로만 해석한다 (기존 동작 유지). + $board = $this->boardService->getBoardBySlug($slug, checkScope: false); + + if (! $board) { + return $this->notFound('sirsoft-board::messages.boards.error_404'); + } if (! $board->use_report) { return $this->error('sirsoft-board::messages.reports.report_disabled', 403); @@ -134,5 +141,4 @@ class ReportController extends AuthBaseController return $this->error('sirsoft-board::messages.reports.create_failed', 500, $e->getMessage()); } } - } diff --git a/modules/_bundled/sirsoft-board/src/Http/Controllers/User/UserActivityController.php b/modules/_bundled/sirsoft-board/src/Http/Controllers/User/UserActivityController.php index 38d2529f..47cb6f21 100644 --- a/modules/_bundled/sirsoft-board/src/Http/Controllers/User/UserActivityController.php +++ b/modules/_bundled/sirsoft-board/src/Http/Controllers/User/UserActivityController.php @@ -33,6 +33,7 @@ class UserActivityController extends AuthBaseController * @param Request $request HTTP 요청 * @return JsonResponse 게시글 활동 목록 응답 */ + // audit:allow controller-base-request-injection reason: GET 본인 활동 목록. 필터·페이징 파라미터만 input() 으로 읽고, per_page 는 아래에서 1~100 으로 잘라낸다 (같은 모듈 PostController::userPosts 와 동일 패턴) public function index(Request $request): JsonResponse { try { @@ -45,7 +46,11 @@ class UserActivityController extends AuthBaseController 'sort' => $request->input('sort', 'latest'), // latest, oldest, views ]; + // 요청 per_page 는 그대로 페이지네이터에 넘기지 않는다. 0/음수는 페이지네이터에서 + // 나눗셈 오류(500)가 되고, 큰 값은 목록 전량을 한 번에 읽는다. + // 같은 컨트롤러의 myComments 와 동일한 상·하한을 적용한다. $perPage = (int) $request->input('per_page', 20); + $perPage = min(max($perPage, 1), 100); $activities = $this->postService->getUserActivities($userId, $filters, $perPage); @@ -75,6 +80,7 @@ class UserActivityController extends AuthBaseController * @param Request $request HTTP 요청 * @return JsonResponse 댓글 목록 응답 */ + // audit:allow controller-base-request-injection reason: GET 본인 댓글 목록. 필터·페이징 파라미터만 input() 으로 읽고, per_page 는 아래에서 1~100 으로 잘라낸다 public function myComments(Request $request): JsonResponse { try { @@ -127,4 +133,4 @@ class UserActivityController extends AuthBaseController return $this->error('sirsoft-board::messages.user_activities.stats_failed', 500, $e->getMessage()); } } -} \ No newline at end of file +} diff --git a/modules/_bundled/sirsoft-board/src/Http/Requests/Admin/ReportersRequest.php b/modules/_bundled/sirsoft-board/src/Http/Requests/Admin/ReportersRequest.php index 1266d4d0..fd7e9531 100644 --- a/modules/_bundled/sirsoft-board/src/Http/Requests/Admin/ReportersRequest.php +++ b/modules/_bundled/sirsoft-board/src/Http/Requests/Admin/ReportersRequest.php @@ -31,7 +31,8 @@ class ReportersRequest extends FormRequest public function rules(): array { return [ - 'per_page' => ['nullable', 'integer', 'min:1'], + // 상한이 없으면 신고자 수만큼을 한 번에 요청할 수 있어 목록 조회가 전량 조회가 된다 + 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], 'page' => ['nullable', 'integer', 'min:1'], ]; } diff --git a/modules/_bundled/sirsoft-board/src/Http/Requests/User/IndexBoardRequest.php b/modules/_bundled/sirsoft-board/src/Http/Requests/User/IndexBoardRequest.php new file mode 100644 index 00000000..a638f977 --- /dev/null +++ b/modules/_bundled/sirsoft-board/src/Http/Requests/User/IndexBoardRequest.php @@ -0,0 +1,50 @@ +> 검증 규칙 + */ + public function rules(): array + { + // 상한은 규칙이 아니라 접근자에서 클램프한다 — 이 엔드포인트는 공개 API 이고, 상한 + // 초과 요청을 거부하지 않고 상한까지만 돌려주는 것이 기존 계약이다. + return [ + 'limit' => ['nullable', 'integer', 'min:0'], + ]; + } + + /** + * 게시판별로 함께 조회할 최신글 개수를 반환합니다. + * + * @return int 0 이면 최신글 미포함 (상한 초과 요청은 상한으로 클램프) + */ + public function recentPostsLimit(): int + { + return min((int) $this->validated('limit', 0), self::MAX_RECENT_POSTS); + } +} diff --git a/modules/_bundled/sirsoft-board/src/Http/Requests/User/PopularBoardsRequest.php b/modules/_bundled/sirsoft-board/src/Http/Requests/User/PopularBoardsRequest.php new file mode 100644 index 00000000..cb722d2d --- /dev/null +++ b/modules/_bundled/sirsoft-board/src/Http/Requests/User/PopularBoardsRequest.php @@ -0,0 +1,50 @@ +> 검증 규칙 + */ + public function rules(): array + { + // 상한은 규칙이 아니라 접근자에서 클램프한다 (기존 공개 API 계약). + return [ + 'limit' => ['nullable', 'integer', 'min:0'], + ]; + } + + /** + * 조회할 게시판 개수를 반환합니다. + * + * @return int 조회 개수 (상한 초과 요청은 상한으로 클램프) + */ + public function limit(): int + { + return min((int) $this->validated('limit', self::DEFAULT_LIMIT), self::MAX_LIMIT); + } +} diff --git a/modules/_bundled/sirsoft-board/src/Http/Requests/User/PopularPostsRequest.php b/modules/_bundled/sirsoft-board/src/Http/Requests/User/PopularPostsRequest.php new file mode 100644 index 00000000..e98ffd4a --- /dev/null +++ b/modules/_bundled/sirsoft-board/src/Http/Requests/User/PopularPostsRequest.php @@ -0,0 +1,87 @@ + + */ + public const RESOLVED_PERIODS = ['today', 'week', 'month', 'year']; + + /** + * 요청 권한 — 공개 엔드포인트이므로 true 고정. + * + * @return bool 항상 true + */ + public function authorize(): bool + { + return true; + } + + /** + * 검증 규칙을 반환합니다. + * + * @return array> 검증 규칙 + */ + public function rules(): array + { + // 상한/어휘를 규칙으로 거부하지 않는 이유: 이 엔드포인트는 북마크 가능한 공개 URL 이고, + // 기존 계약이 "상한 초과는 상한까지, 미지원 period 는 year 로 해석" 이다. 정규화는 + // 접근자가 담당한다 (period() 가 닫힌 집합만 반환하므로 캐시 키 공간도 함께 닫힌다). + return [ + 'period' => ['nullable', 'string'], + 'limit' => ['nullable', 'integer', 'min:0'], + ]; + } + + /** + * 조회 기간을 닫힌 집합 안의 값으로 반환합니다. + * + * 미지정은 기본 기간, `all` 은 하위 호환 별칭이라 `year`, 그 밖의 미지원 값도 `year` 로 + * 해석합니다 — 저장소가 미지원 period 를 1년 범위로 처리해 온 기존 동작과 같습니다. + * 반환값을 닫아 두면 이 값을 그대로 쓰는 캐시 키가 임의 문자열로 늘어나지 않습니다. + * + * @return string 기간 키 (today|week|month|year) + */ + public function period(): string + { + $period = $this->validated('period'); + + if ($period === null || $period === '') { + return self::DEFAULT_PERIOD; + } + + return in_array((string) $period, self::RESOLVED_PERIODS, true) + ? (string) $period + : 'year'; + } + + /** + * 조회할 게시글 개수를 반환합니다. + * + * @return int 조회 개수 (상한 초과 요청은 상한으로 클램프) + */ + public function limit(): int + { + return min((int) $this->validated('limit', self::DEFAULT_LIMIT), self::MAX_LIMIT); + } +} diff --git a/modules/_bundled/sirsoft-board/src/Http/Requests/User/RecentPostsRequest.php b/modules/_bundled/sirsoft-board/src/Http/Requests/User/RecentPostsRequest.php new file mode 100644 index 00000000..56457e2c --- /dev/null +++ b/modules/_bundled/sirsoft-board/src/Http/Requests/User/RecentPostsRequest.php @@ -0,0 +1,51 @@ +> 검증 규칙 + */ + public function rules(): array + { + // 상한은 규칙이 아니라 접근자에서 클램프한다 — 이 엔드포인트는 공개 API 이고, 상한 + // 초과 요청을 거부하지 않고 상한까지만 돌려주는 것이 기존 계약이다. + return [ + 'limit' => ['nullable', 'integer', 'min:0'], + ]; + } + + /** + * 조회할 게시글 개수를 반환합니다. + * + * @return int 조회 개수 (상한 초과 요청은 상한으로 클램프) + */ + public function limit(): int + { + return min((int) $this->validated('limit', self::DEFAULT_LIMIT), self::MAX_LIMIT); + } +} diff --git a/modules/_bundled/sirsoft-board/src/Repositories/BoardRepository.php b/modules/_bundled/sirsoft-board/src/Repositories/BoardRepository.php index a0eeea71..83915bbb 100644 --- a/modules/_bundled/sirsoft-board/src/Repositories/BoardRepository.php +++ b/modules/_bundled/sirsoft-board/src/Repositories/BoardRepository.php @@ -144,7 +144,8 @@ class BoardRepository implements BoardRepositoryInterface */ public function paginate(int $perPage = 15): LengthAwarePaginator { - $query = Board::orderBy('created_at', 'desc'); + // 정렬 마지막의 기본키는 전순서 보장용이다 (created_at 동률에서 페이지 경계 흔들림 방지) + $query = Board::orderBy('created_at', 'desc')->orderBy('id', 'desc'); // 권한 스코프 필터링 PermissionHelper::applyPermissionScope($query, 'sirsoft-board.boards.read'); diff --git a/modules/_bundled/sirsoft-board/src/Repositories/ReportRepository.php b/modules/_bundled/sirsoft-board/src/Repositories/ReportRepository.php index ad3e05fb..38730611 100644 --- a/modules/_bundled/sirsoft-board/src/Repositories/ReportRepository.php +++ b/modules/_bundled/sirsoft-board/src/Repositories/ReportRepository.php @@ -586,6 +586,8 @@ class ReportRepository implements ReportRepositoryInterface return ReportLog::where('report_id', $reportId) ->with('reporter') ->latest() + // 전순서 보장 — 같은 초에 기록된 처리 로그가 페이지 경계에서 뒤섞이지 않도록 + ->orderByDesc('id') // audit:allow repository-paginate-column-pruning reason: 신고 케이스 1건에 종속된 신고자 로그 — // where(report_id) 로 이미 좁혀져 OFFSET 이 깊어질 수 없다 ->paginate($perPage, ['*'], 'page', $page); diff --git a/modules/_bundled/sirsoft-board/src/lang/en/activity_log.php b/modules/_bundled/sirsoft-board/src/lang/en/activity_log.php index 390ad944..d77bb3b6 100644 --- a/modules/_bundled/sirsoft-board/src/lang/en/activity_log.php +++ b/modules/_bundled/sirsoft-board/src/lang/en/activity_log.php @@ -35,6 +35,7 @@ return [ // Board type (Admin) 'board_type_index' => 'Board type list viewed', + 'board_type_show' => 'Board type viewed (:type_name)', 'board_type_create' => 'Board type created (:type_name)', 'board_type_update' => 'Board type updated (:type_name)', 'board_type_delete' => 'Board type deleted (:type_name)', diff --git a/modules/_bundled/sirsoft-board/src/lang/ko/activity_log.php b/modules/_bundled/sirsoft-board/src/lang/ko/activity_log.php index d75714fc..c32f08b0 100644 --- a/modules/_bundled/sirsoft-board/src/lang/ko/activity_log.php +++ b/modules/_bundled/sirsoft-board/src/lang/ko/activity_log.php @@ -35,6 +35,7 @@ return [ // 게시판 유형 (Admin) 'board_type_index' => '게시판 유형 목록 조회', + 'board_type_show' => '게시판 유형 상세 조회 (:type_name)', 'board_type_create' => '게시판 유형 생성 (:type_name)', 'board_type_update' => '게시판 유형 수정 (:type_name)', 'board_type_delete' => '게시판 유형 삭제 (:type_name)', diff --git a/modules/_bundled/sirsoft-board/tests/Feature/Admin/ReportRequestValidationTest.php b/modules/_bundled/sirsoft-board/tests/Feature/Admin/ReportRequestValidationTest.php index 874fbefc..01067d07 100644 --- a/modules/_bundled/sirsoft-board/tests/Feature/Admin/ReportRequestValidationTest.php +++ b/modules/_bundled/sirsoft-board/tests/Feature/Admin/ReportRequestValidationTest.php @@ -177,4 +177,32 @@ class ReportRequestValidationTest extends ModuleTestCase $response->assertStatus(200) ->assertJson(['success' => true]); } + + /** + * 신고자 목록의 per_page 가 상·하한 밖 값을 거부하는지 검증합니다. + * + * 상한이 없으면 한 요청으로 신고자 전량을 끌어올 수 있어 페이지네이션이 무의미해진다. + */ + public function test_reporters_per_page_is_bounded(): void + { + $report = Report::factory()->create([ + 'board_id' => $this->board->id, + 'status' => ReportStatus::Pending, + ]); + + $url = "/api/modules/sirsoft-board/admin/reports/{$report->id}/reporters"; + + foreach ([0, -3, 101, 100000] as $perPage) { + $this->actingAs($this->admin) + ->getJson("{$url}?per_page={$perPage}") + ->assertStatus(422); + } + + // 경계 안쪽 값은 그대로 통과해야 한다 + foreach ([1, 100] as $perPage) { + $this->actingAs($this->admin) + ->getJson("{$url}?per_page={$perPage}") + ->assertStatus(200); + } + } } diff --git a/modules/_bundled/sirsoft-board/tests/Feature/User/BoardPopularApiTest.php b/modules/_bundled/sirsoft-board/tests/Feature/User/BoardPopularApiTest.php index 9df1c4ce..8a86a861 100644 --- a/modules/_bundled/sirsoft-board/tests/Feature/User/BoardPopularApiTest.php +++ b/modules/_bundled/sirsoft-board/tests/Feature/User/BoardPopularApiTest.php @@ -231,6 +231,36 @@ class BoardPopularApiTest extends ModuleTestCase $this->assertNotContains('Over One Year', $titles); } + /** + * 미지원 period 가 거부되지 않고 year 로 해석되며, 캐시 키도 정규화되는지 테스트 + * + * 북마크 가능한 공개 URL 이라 미지원 값에 422 를 돌려주면 기존 링크가 깨진다. + * 동시에 period 는 캐시 키에 들어가므로, 원문을 그대로 쓰면 임의 문자열마다 + * 캐시 엔트리가 생겨 키 공간이 무한히 늘어난다 (요청 값이 아니라 해석된 값으로 키를 만든다). + */ + public function test_popular_unsupported_period_falls_back_to_year_and_normalizes_cache_key(): void + { + // Given: 1년 이내/이전 게시글 생성 + $board = Board::factory()->create(['is_active' => true]); + DB::table('board_posts')->insert([ + ['board_id' => $board->id, 'title' => 'Recent', 'content' => 'Content', 'view_count' => 100, 'status' => PostStatus::Published->value, 'ip_address' => '127.0.0.1', 'created_at' => now(), 'updated_at' => now()], + ['board_id' => $board->id, 'title' => 'Over One Year', 'content' => 'Content', 'view_count' => 300, 'status' => PostStatus::Published->value, 'ip_address' => '127.0.0.1', 'created_at' => now()->subMonths(13), 'updated_at' => now()], + ]); + + // When: 지원하지 않는 period 로 호출 + $response = $this->getJson('/api/modules/sirsoft-board/boards/popular?period=nonsense&limit=5'); + + // Then: 422 가 아니라 200 이고, year 와 동일한 범위(1년 이내)로 해석된다 + $response->assertStatus(200); + $titles = array_column($response->json('data'), 'title'); + $this->assertContains('Recent', $titles); + $this->assertNotContains('Over One Year', $titles); + + // Then: 캐시 키에는 요청 원문이 아니라 해석된 값이 들어간다 + $this->assertTrue(Cache::has('g7:module.sirsoft-board:popular_posts_year_5')); + $this->assertFalse(Cache::has('g7:module.sirsoft-board:popular_posts_nonsense_5')); + } + /** * 게시글이 없을 때 빈 배열을 반환하는지 테스트 */ @@ -352,7 +382,7 @@ class BoardPopularApiTest extends ModuleTestCase $response1->assertStatus(200); // 캐시 키 확인 (형식: g7:module.sirsoft-board:popular_posts_{period}_{limit}) - $this->assertTrue(Cache::has("g7:module.sirsoft-board:popular_posts_week_5")); + $this->assertTrue(Cache::has('g7:module.sirsoft-board:popular_posts_week_5')); // When: 두 번째 API 호출 $response2 = $this->getJson('/api/modules/sirsoft-board/boards/popular?limit=5&period=week'); @@ -413,8 +443,8 @@ class BoardPopularApiTest extends ModuleTestCase /** * 게시판을 생성하고 게시글을 삽입하는 헬퍼 * - * @param int $postCount 생성할 게시글 수 - * @param Board|null $board 기존 게시판 (null이면 새로 생성) + * @param int $postCount 생성할 게시글 수 + * @param Board|null $board 기존 게시판 (null이면 새로 생성) * @return Board 생성된 게시판 */ private function createBoardWithPosts(int $postCount, ?Board $board = null): Board diff --git a/modules/_bundled/sirsoft-board/tests/Feature/User/UserActivityApiTest.php b/modules/_bundled/sirsoft-board/tests/Feature/User/UserActivityApiTest.php index e66435ca..c30c8cea 100644 --- a/modules/_bundled/sirsoft-board/tests/Feature/User/UserActivityApiTest.php +++ b/modules/_bundled/sirsoft-board/tests/Feature/User/UserActivityApiTest.php @@ -237,6 +237,40 @@ class UserActivityApiTest extends BoardTestCase /** * 댓글을 단 게시글이 활동에 포함된다. */ + /** + * per_page 경계값 회귀 가드 (#492 브라우저 실측). + * + * 같은 컨트롤러의 my-comments 는 min(max(per_page,1),100) 로 잘라내는데 + * board-activities 만 원시 입력을 그대로 페이지네이터에 넘겨, + * per_page=0 / 음수에서 500 이 나고 큰 값은 전량 조회가 됐다. + */ + public function test_board_activities_per_page_는_상한_100_으로_잘린다(): void + { + $response = $this->actingAs($this->user) + ->getJson('/api/modules/sirsoft-board/me/board-activities?per_page=100000'); + + $response->assertOk(); + $this->assertSame(100, $response->json('data.per_page')); + } + + public function test_board_activities_per_page_0_은_500_이_아니라_최소값으로_처리된다(): void + { + $response = $this->actingAs($this->user) + ->getJson('/api/modules/sirsoft-board/me/board-activities?per_page=0'); + + $response->assertOk(); + $this->assertSame(1, $response->json('data.per_page')); + } + + public function test_board_activities_per_page_음수는_500_이_아니라_최소값으로_처리된다(): void + { + $response = $this->actingAs($this->user) + ->getJson('/api/modules/sirsoft-board/me/board-activities?per_page=-5'); + + $response->assertOk(); + $this->assertSame(1, $response->json('data.per_page')); + } + public function test_activity_includes_commented_posts(): void { // Given: 다른 사용자의 게시글에 댓글 작성 @@ -766,10 +800,10 @@ class UserActivityApiTest extends BoardTestCase // 비즈니스 로직: 통계 쿼리는 board_posts.comments_count 컬럼 (SUM) 기반이므로 // CommentService 를 거치지 않는 직접 Comment::create 시에도 카운터 수동 동기화 필요. // (Listener 는 Service 레벨 훅에만 반응하므로 테스트에서 Model::create 직접 사용 시 미반영) - \Illuminate\Support\Facades\DB::table('board_posts') + DB::table('board_posts') ->where('id', $postId) ->update([ - 'comments_count' => \Illuminate\Support\Facades\DB::table('board_comments') + 'comments_count' => DB::table('board_comments') ->where('post_id', $postId) ->whereNull('deleted_at') ->count(), @@ -777,4 +811,4 @@ class UserActivityApiTest extends BoardTestCase return $comment; } -} \ No newline at end of file +} diff --git a/modules/_bundled/sirsoft-board/tests/Unit/Benchmark/ListIndexCoverageTest.php b/modules/_bundled/sirsoft-board/tests/Unit/Benchmark/ListIndexCoverageTest.php new file mode 100644 index 00000000..8f25ce8f --- /dev/null +++ b/modules/_bundled/sirsoft-board/tests/Unit/Benchmark/ListIndexCoverageTest.php @@ -0,0 +1,30 @@ +assertListIndexCoverageForExtension(new Module); + } +} diff --git a/modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php index 6800e262..346c0ef7 100644 --- a/modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php +++ b/modules/_bundled/sirsoft-board/tests/Unit/Repositories/PostListDeferredJoinTest.php @@ -5,6 +5,10 @@ namespace Modules\Sirsoft\Board\Tests\Unit\Repositories; // ModuleTestCase 수동 로드 (autoload 전에 로드 필요) require_once __DIR__.'/../../ModuleTestCase.php'; +use App\Helpers\PermissionHelper; +use App\Models\Permission; +use App\Models\Role; +use App\Models\User; use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Board\Repositories\PostRepository; use Modules\Sirsoft\Board\Tests\BoardTestCase; @@ -15,6 +19,10 @@ use Modules\Sirsoft\Board\Tests\BoardTestCase; * 목록은 이제 "이번 페이지의 원글 ID 를 먼저 구하고, 그 ID 에 대해서만 목록 컬럼과 관계를 * 읽는" 2단계 조회다. 이 화면은 공지 병합·답글 트리·다음 페이지 판정이 얽혀 있어, 조회 방식 * 변경이 조용히 깨지면 화면에서 빈 값이나 누락으로만 드러난다. 그 성질들을 고정한다. + * + * @scenario case=post_list_deferred_join + * + * @effects self_scoped_list_returns_only_own_rows, soft_deleted_rows_stay_excluded, with_trashed_scope_is_preserved */ class PostListDeferredJoinTest extends BoardTestCase { @@ -193,4 +201,92 @@ class PostListDeferredJoinTest extends BoardTestCase $this->assertSame($sorted, $ids, '동률 정렬에서도 id 내림차순이 유지되어야 한다'); } + + /** + * self 스코프 사용자의 목록에 타인 글이 섞이지 않는지 검증합니다. + * + * 권한 스코프는 `PermissionHelper::applyPermissionScope()` 가 쿼리에 붙이는 where 다. + * 지연 조인은 이 쿼리를 clone 해 inner/outer 두 번 실행하므로, 스코프가 한쪽에만 + * 걸리면 목록에 타인 글이 노출되거나(권한 누출) 페이지가 비게 된다. 이 실패는 + * 예외 없이 조용히 일어나므로 결과 집합으로 직접 단언한다. + */ + public function test_self_scoped_list_returns_only_own_rows(): void + { + $owner = User::factory()->create(); + $other = User::factory()->create(); + + for ($i = 1; $i <= 6; $i++) { + $this->createTestPost(['title' => "내 글 {$i}", 'user_id' => $owner->id]); + $this->createTestPost(['title' => "남의 글 {$i}", 'user_id' => $other->id]); + } + + $permission = "sirsoft-board.{$this->board->slug}.admin.posts.read"; + + // owner_key 는 config/board.php 의 권한 정의가 SSoT 다. 여기에 값을 적어 두면 + // 정의가 바뀌어 스코프가 꺼져도 테스트는 계속 통과한다. + $scopeMeta = config('sirsoft-board.board_permission_definitions')['admin.posts.read']['scope'] ?? null; + + $this->assertNotNull($scopeMeta, 'admin.posts.read 에 scope 정의가 없다 — 스코프 필터가 적용되지 않는다'); + + $this->grantScopedPermission($owner, $permission, 'self', $scopeMeta); + + // 권한 메타가 실제로 스코프를 켜는지 확인 — 이게 null 이면 아래 단언이 무의미해진다 + $this->assertSame( + $scopeMeta['owner_key'], + PermissionHelper::getOwnerKey($permission), + "권한 {$permission} 의 owner_key 가 config 정의와 다르다" + ); + $this->actingAs($owner); + + $result = $this->repository->paginate( + $this->board->slug, + ['scope_permission' => $permission], + 5 + ); + + $titles = collect($result->items())->pluck('title')->all(); + $userIds = collect($result->items())->pluck('user_id')->unique()->all(); + + $this->assertNotEmpty($titles, 'self 스코프에서도 본인 글은 조회되어야 한다'); + $this->assertSame([$owner->id], $userIds, 'self 스코프 목록에 타인 글이 섞이면 안 된다'); + + foreach ($titles as $title) { + $this->assertStringNotContainsString('남의 글', $title); + } + } + + /** + * 사용자에게 지정한 스코프로 권한을 부여합니다. + * + * @param User $user 대상 사용자 + * @param string $identifier 권한 식별자 + * @param string $scopeType 스코프 유형 (self/role) + * @param array $scopeMeta config 의 scope 정의 (resource_route_key / owner_key) + */ + private function grantScopedPermission(User $user, string $identifier, string $scopeType, array $scopeMeta): void + { + $permission = Permission::updateOrCreate( + ['identifier' => $identifier], + [ + 'name' => json_encode(['ko' => $identifier, 'en' => $identifier]), + 'description' => json_encode(['ko' => $identifier, 'en' => $identifier]), + 'extension_type' => 'module', + 'extension_identifier' => 'sirsoft-board', + 'type' => 'admin', + 'resource_route_key' => $scopeMeta['resource_route_key'], + 'owner_key' => $scopeMeta['owner_key'], + ] + ); + + $role = Role::create([ + 'identifier' => 'scoped_test_'.uniqid(), + 'name' => json_encode(['ko' => '스코프 테스트', 'en' => 'Scoped Test']), + 'is_active' => true, + ]); + + $role->permissions()->attach($permission->id, ['scope_type' => $scopeType]); + $user->roles()->attach($role->id, ['assigned_at' => now()]); + + $user->refresh(); + } } diff --git a/modules/_bundled/sirsoft-board/upgrades/data/1.0.3/migrations/04_AddTiebreakToPostListIndex.php b/modules/_bundled/sirsoft-board/upgrades/data/1.0.3/migrations/04_AddTiebreakToPostListIndex.php new file mode 100644 index 00000000..f688efce --- /dev/null +++ b/modules/_bundled/sirsoft-board/upgrades/data/1.0.3/migrations/04_AddTiebreakToPostListIndex.php @@ -0,0 +1,90 @@ + */ + private const NEW_COLUMNS = ['board_id', 'is_notice', 'parent_id', 'deleted_at', 'created_at', 'id']; + + /** + * 마이그레이션 식별자를 반환합니다. + * + * @return string 마이그레이션 이름 + */ + public function name(): string + { + return 'AddTiebreakToPostListIndex'; + } + + /** + * 목록 인덱스를 기본키 포함 형태로 교체합니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + */ + public function run(UpgradeContext $context): void + { + if (! Schema::hasTable(self::TABLE)) { + $context->logger->info('[board:1.0.3] 게시글 테이블 부재 — 목록 인덱스 교체 스킵'); + + return; + } + + $existing = array_column(Schema::getIndexes(self::TABLE), 'name'); + + if (! in_array(self::NEW_INDEX, $existing, true)) { + $context->logger->info('[board:1.0.3] 목록 인덱스 교체 시작 (게시글이 많으면 수 분 이상 걸릴 수 있고 그동안 게시글 쓰기가 대기합니다)'); + + Schema::table(self::TABLE, function ($table) { + $table->index(self::NEW_COLUMNS, self::NEW_INDEX); + }); + + $context->logger->info('[board:1.0.3] 새 목록 인덱스 생성 완료: '.self::NEW_INDEX); + } else { + $context->logger->info('[board:1.0.3] 새 목록 인덱스가 이미 존재 — 생성 스킵'); + } + + // 새 인덱스가 기존 인덱스의 상위집합이므로 남겨 두면 쓰기 비용만 늘어난다. + // 반드시 새 인덱스 생성 이후에 지운다 — 순서를 바꾸면 중간 중단 시 목록 조회가 + // 인덱스 없이 남는 구간이 생긴다. + if (in_array(self::OLD_INDEX, $existing, true)) { + Schema::table(self::TABLE, function ($table) { + $table->dropIndex(self::OLD_INDEX); + }); + + $context->logger->info('[board:1.0.3] 상위집합에 포함된 기존 인덱스 제거: '.self::OLD_INDEX); + } + } +} diff --git a/modules/_bundled/sirsoft-board/upgrades/data/1.0.3/migrations/05_AddReportListSortIndex.php b/modules/_bundled/sirsoft-board/upgrades/data/1.0.3/migrations/05_AddReportListSortIndex.php new file mode 100644 index 00000000..37e98f5d --- /dev/null +++ b/modules/_bundled/sirsoft-board/upgrades/data/1.0.3/migrations/05_AddReportListSortIndex.php @@ -0,0 +1,70 @@ + */ + private const INDEX_COLUMNS = ['deleted_at', 'created_at', 'id']; + + /** + * 마이그레이션 식별자를 반환합니다. + * + * @return string 마이그레이션 이름 + */ + public function name(): string + { + return 'AddReportListSortIndex'; + } + + /** + * 신고 목록 정렬 색인을 추가합니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + */ + public function run(UpgradeContext $context): void + { + if (! Schema::hasTable(self::TABLE)) { + $context->logger->info('[board:1.0.3] 신고 테이블 부재 — 정렬 색인 추가 스킵'); + + return; + } + + $existing = array_column(Schema::getIndexes(self::TABLE), 'name'); + + if (in_array(self::INDEX_NAME, $existing, true)) { + $context->logger->info('[board:1.0.3] 이미 존재하는 색인 — 스킵: '.self::INDEX_NAME); + + return; + } + + $context->logger->info('[board:1.0.3] 신고 목록 정렬 색인 추가 시작 (신고가 많으면 수 분 걸릴 수 있고 그동안 신고 접수가 대기합니다)'); + + Schema::table(self::TABLE, function ($table) { + $table->index(self::INDEX_COLUMNS, self::INDEX_NAME); + }); + + $context->logger->info('[board:1.0.3] 신고 목록 정렬 색인 추가 완료: '.self::INDEX_NAME); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md index 00a88499..2599086c 100644 --- a/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md +++ b/modules/_bundled/sirsoft-ecommerce/CHANGELOG.md @@ -27,6 +27,7 @@ - 실제로 사용되지 않던 설정 항목(상품·주문 목록의 페이지당 개수, 결제 자동취소 기한 중복 항목)을 정리했습니다. 미입금 자동취소 기한은 관리자 환경설정의 값 하나만 사용합니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) - 사이트맵을 만들 때 상품과 카테고리를 한 번에 모두 메모리에 올리지 않고 나눠서 읽도록 바꿨습니다. 상품이 많은 쇼핑몰에서 사이트맵 생성 중 메모리가 부족해 실패하던 문제가 줄어듭니다. - 상품 리뷰·마일리지 내역·쿠폰 발급 내역 목록도 같은 방식으로 뒤쪽 페이지 조회를 개선했습니다. 상품 리뷰는 작성일 정렬용 인덱스를 추가하고 기간 검색 조건을 인덱스가 활용되는 형태로 바꿔, 리뷰가 많은 쇼핑몰에서 목록과 기간 검색이 함께 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) +- 주문·상품·상품문의·상품후기·쿠폰 발급이력 목록의 정렬 색인을 정비했습니다. 정렬 순서를 색인이 그대로 담고 있지 않아 목록을 열 때마다 따로 줄을 세우던 작업이 사라져, 데이터가 많은 쇼핑몰일수록 목록이 빨라집니다. 기존 쇼핑몰도 업데이트 시 자동으로 반영되며, 데이터가 아주 많은 경우 이 과정에 수 분이 걸리고 그동안 해당 데이터의 저장이 잠시 대기할 수 있습니다. - 상품 목록(관리자·쇼핑몰 화면)·상품 문의 관리 목록·추가배송비 목록도 같은 방식으로 개선했습니다. 상품 문의는 기간 검색 조건도 인덱스가 활용되는 형태로 바꿔 기간을 좁혀 찾을 때 더 빨라집니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 주문 목록을 뒤쪽 페이지에서도 빠르게 열 수 있도록 조회 방식을 바꿨습니다. 예전에는 페이지가 뒤로 갈수록 건너뛰는 주문의 스냅샷·관리자 메모 같은 큰 항목까지 함께 읽어 느려졌지만, 이제 현재 페이지의 주문만 상세 정보를 읽습니다. 주문 10만 건 기준 5,000번째 페이지에서 조회 시간이 약 6분의 1로 줄어듭니다. 화면에 보이는 내용은 이전과 동일합니다. (#74 @jiwonpapa 님께서 제보해주셨습니다.) - 개선된 사이트맵 생성 기능을 사용하기 위해 이 모듈은 이제 코어 7.0.6 이상이 필요합니다. @@ -51,13 +52,25 @@ - 관리자 상품 등록/수정 화면의 배송 정보에서 '배송정책 등록' 버튼을 눌러도 아무 반응이 없던 문제를 수정했습니다. 이제 배송정책 등록 화면으로 이동합니다. - 관리자 브랜드·상품 카테고리·상품 공통정보·상품 고시정보 목록에서 왼쪽 목록의 항목을 고르면 걸어 둔 검색어(브랜드는 정렬 포함)가 사라지던 문제를 수정했습니다. 항목 선택은 선택 상태만 주소에 기록하고 목록 조건은 그대로 둡니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) - 관리자 쿠폰 목록에서 '등록' 으로 들어가거나 상품을 새로 저장한 직후, 보고 있던 목록의 조건이 사라져 취소·복귀 시 1페이지로 돌아가던 문제를 수정했습니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) +- 상품 문의 등록이 실패했을 때 "문의 등록에 실패했습니다." 만 뜨고 이유를 알 수 없던 문제를 수정했습니다. 이제 "문의 게시판이 설정되지 않았습니다" 처럼 구체적인 사유가 표시되어, 문의 수정·답변 화면과 안내 수준이 같아집니다. +- 찜 목록에서 판매자가 삭제한 상품이 빈 칸으로 남아 "데이터를 표시할 수 없습니다" 로 보이던 문제를 수정했습니다. 삭제된 상품은 목록에서 빠집니다. +- 회원 마일리지 내역에서 사용·차감 내역만 「내용」 칸이 비어 있던 문제를 수정했습니다. 적립과 동일하게 사유가 표시됩니다. +- 관리자 마일리지 내역 목록의 NO 칸이 항상 비어 있던 문제를 수정했습니다. +- 내 쿠폰 목록에서 유효기간이 지난 쿠폰이 "사용가능" 으로 표시되던 문제를 수정했습니다. 만료 상태로 표시됩니다. +- 상품 문의 목록과 찜 목록에서 한 페이지에 보여줄 개수를 0·음수·과도하게 큰 값으로 요청하면 오류가 나거나 목록 전체가 한 번에 조회되던 문제를 수정했습니다. 1~100 범위를 벗어나면 요청이 거부됩니다. +- 상품 문의 목록·찜 목록·다운로드 가능 쿠폰 목록·통합검색 상품 목록에서 같은 시각에 등록된 항목이 여러 건일 때, 페이지를 넘기면 같은 항목이 두 번 보이고 다른 항목은 아예 보이지 않던 문제를 수정했습니다. 이제 페이지를 모두 넘겨도 목록에 있는 항목이 빠짐없이 한 번씩만 나옵니다. +- 주문 관리 목록에서 "최근 발송순" 또는 "오래된 발송순"을 고르면 목록이 갱신되지 않고 정렬 이름만 바뀌던 문제를 수정했습니다. 이제 주문별 발송일 기준으로 정렬되며, 발송 이력이 없는 주문도 목록에서 사라지지 않습니다. 주문이 많아도 빠르게 정렬되도록 색인을 함께 추가했습니다. +- 쿠폰 관리 목록에서 "유효기간 임박순"·"유효기간 여유순"을 고르면 목록이 그대로이던 문제를 수정했습니다. 화면이 제공하는 정렬인데 서버가 거부하고 있었으며, 이제 두 정렬 모두 정상 동작합니다. - 상품을 장바구니에 담거나 바로 구매할 때, 표시 언어(로케일)나 관리자의 옵션 이름 수정과 무관하게 선택한 옵션이 정확히 담기도록 개선했습니다. 특정 상황에서 "해당 옵션 조합을 찾을 수 없습니다" 오류로 담기가 실패하던 문제가 해소됩니다. - 오류 안내가 뜨기는 하지만 내용이 비어 있던 문제를 수정했습니다. 서버가 알려 준 실패 사유가 그대로 표시됩니다. - 헤더에서 통화나 배송 국가를 바꿀 때 저장에 실패해도 아무 안내 없이 넘어가던 문제를 수정했습니다. 실패하면 그 이유가 화면에 표시됩니다. - 일부 안내 문구가 실제 문장 대신 내부 식별자로 표시되던 문제를 수정했습니다. 리뷰 작성·답글·일괄처리, 장바구니 선택 항목 처리, 주문서 쿠폰 적용, 클레임 사유 관리, 주문 목록의 배송비 검색 조건, 택배사·상품 공통정보 관리, 결제 대기 주문 취소가 해당됩니다. - 관리자 주문 메모 안내가 실제 입력 한도(2,000자)와 다르게 "최대 1000자"로 표시되던 문제를 수정했습니다. +- 금액·가격 범위 검색에서 상한을 0으로 지정하면 조건이 무시되고 전체 목록이 나오던 문제를 수정했습니다. 주문 목록의 "배송비 0원(무료배송)만 보기", 결제금액 0원 조회, 상품 목록의 가격 범위, 쿠폰 목록의 혜택금액·최소주문금액 범위가 해당됩니다. 이제 0도 정상적인 경계값으로 처리됩니다. - 쇼핑몰 환경설정 저장·조회, 카테고리 이미지 등록·삭제·순서 변경, 브랜드·상품 카테고리 전시 상태 변경, 검색 프리셋 조회, 결제 설정 조회 화면에서 결과 안내가 실제 문장 대신 내부 식별자로 표시되던 문제를 수정했습니다. - 화면과 문서에 "클래임"으로 잘못 적혀 있던 용어를 "클레임"으로 바로잡았습니다. +- 쿠폰 목록의 "발급수"가 실제 발급 건수보다 적게 표시되던 문제를 수정했습니다. 목록의 발급수와 발급 내역 창의 "총 N건"이 서로 달랐으며, 이제 두 값이 일치합니다. +- 활동 로그 내용에 실제 값 대신 `:option_id` 같은 표시가 그대로 나오던 문제를 수정했습니다. 주문 상세의 활동 로그 탭, 상품 수정 화면의 로그 탭, 관리자 활동 로그 목록이 해당됩니다. - 클레임 사유 목록 조회 시 유형·귀책 구분·사용여부·검색어에 잘못된 값을 넣어도 그대로 조회되던 것을 검증하도록 바꿨습니다. 잘못된 값은 이제 안내와 함께 거절됩니다. - 검색 프리셋의 대상 화면 이름(상품 목록·주문 목록·고객 목록)이 표시되지 않던 문제를 수정했습니다. - 상품 문의 답변 상태와 배송정책이 없는 배송 그룹의 표시명이 비어 보이던 문제를 수정했습니다. diff --git a/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_07_31_000001_add_shipped_at_index_to_ecommerce_order_shippings_table.php b/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_07_31_000001_add_shipped_at_index_to_ecommerce_order_shippings_table.php new file mode 100644 index 00000000..e341cd18 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_07_31_000001_add_shipped_at_index_to_ecommerce_order_shippings_table.php @@ -0,0 +1,54 @@ +index(['order_id', 'shipped_at'], 'ecommerce_order_shippings_order_id_shipped_at_index'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (! Schema::hasTable('ecommerce_order_shippings')) { + return; + } + + $existingIndexes = array_column(Schema::getIndexes('ecommerce_order_shippings'), 'name'); + + Schema::table('ecommerce_order_shippings', function (Blueprint $table) use ($existingIndexes) { + if (in_array('ecommerce_order_shippings_order_id_shipped_at_index', $existingIndexes)) { + $table->dropIndex('ecommerce_order_shippings_order_id_shipped_at_index'); + } + }); + } +}; diff --git a/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_08_01_000001_add_list_sort_indexes_to_ecommerce_tables.php b/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_08_01_000001_add_list_sort_indexes_to_ecommerce_tables.php new file mode 100644 index 00000000..dbab1a7d --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/database/migrations/2026_08_01_000001_add_list_sort_indexes_to_ecommerce_tables.php @@ -0,0 +1,103 @@ + [신규 색인명, 컬럼, 교체 대상 기존 색인명(없으면 null), 되돌릴 컬럼]] + * + * @var array, 2: string|null, 3: array}> + */ + private const TARGETS = [ + 'ecommerce_orders' => [ + 'idx_orders_deleted_ordered_id', ['deleted_at', 'ordered_at', 'id'], null, [], + ], + 'ecommerce_products' => [ + 'idx_products_deleted_created_id', ['deleted_at', 'created_at', 'id'], null, [], + ], + 'ecommerce_product_inquiries' => [ + 'idx_inquiries_created_id', ['created_at', 'id'], null, [], + ], + 'ecommerce_product_reviews' => [ + 'idx_reviews_deleted_created_id', ['deleted_at', 'created_at', 'id'], + 'idx_reviews_deleted_created', ['deleted_at', 'created_at'], + ], + 'ecommerce_promotion_coupon_issues' => [ + 'idx_coupon_issues_issued_id', ['issued_at', 'id'], 'idx_issued_at', ['issued_at'], + ], + ]; + + /** + * Run the migrations. + */ + public function up(): void + { + foreach (self::TARGETS as $table => [$newIndex, $columns, $oldIndex, $oldColumns]) { + if (! Schema::hasTable($table)) { + continue; + } + + $existing = array_column(Schema::getIndexes($table), 'name'); + + Schema::table($table, function (Blueprint $blueprint) use ($existing, $newIndex, $columns, $oldIndex) { + if (! in_array($newIndex, $existing, true)) { + $blueprint->index($columns, $newIndex); + } + + if ($oldIndex !== null && in_array($oldIndex, $existing, true)) { + $blueprint->dropIndex($oldIndex); + } + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + foreach (self::TARGETS as $table => [$newIndex, $columns, $oldIndex, $oldColumns]) { + if (! Schema::hasTable($table)) { + continue; + } + + $existing = array_column(Schema::getIndexes($table), 'name'); + + Schema::table($table, function (Blueprint $blueprint) use ($existing, $newIndex, $oldIndex, $oldColumns) { + if ($oldIndex !== null && $oldColumns !== [] && ! in_array($oldIndex, $existing, true)) { + $blueprint->index($oldColumns, $oldIndex); + } + + if (in_array($newIndex, $existing, true)) { + $blueprint->dropIndex($newIndex); + } + }); + } + } +}; diff --git a/modules/_bundled/sirsoft-ecommerce/database/seeders/ActivityLogSampleSeeder.php b/modules/_bundled/sirsoft-ecommerce/database/seeders/ActivityLogSampleSeeder.php index 0900c0f2..e16d827b 100644 --- a/modules/_bundled/sirsoft-ecommerce/database/seeders/ActivityLogSampleSeeder.php +++ b/modules/_bundled/sirsoft-ecommerce/database/seeders/ActivityLogSampleSeeder.php @@ -204,17 +204,17 @@ class ActivityLogSampleSeeder extends Seeder $actions = [ ['action' => 'product_option.bulk_price_update', 'key' => 'product_option_bulk_price_update', 'loggable' => true, - 'params' => fn ($opt) => ['count' => rand(2, 20)], + 'params' => fn ($opt) => ['option_id' => $opt->id], 'changes' => fn ($opt) => [ ['field' => 'price_adjustment', 'label_key' => 'sirsoft-ecommerce::activity_log.fields.price_adjustment', 'old' => rand(0, 5000), 'new' => $opt->price_adjustment ?? 0, 'type' => 'currency'], ]], ['action' => 'product_option.bulk_stock_update', 'key' => 'product_option_bulk_stock_update', 'loggable' => true, - 'params' => fn ($opt) => ['count' => rand(2, 20)], + 'params' => fn ($opt) => ['option_id' => $opt->id], 'changes' => fn ($opt) => [ ['field' => 'stock_quantity', 'label_key' => 'sirsoft-ecommerce::activity_log.fields.stock_quantity', 'old' => rand(0, 30), 'new' => $opt->stock_quantity ?? rand(10, 100), 'type' => 'number'], ]], ['action' => 'product_option.bulk_update', 'key' => 'product_option_bulk_update', 'loggable' => true, - 'params' => fn ($opt) => ['count' => rand(2, 20)], + 'params' => fn ($opt) => ['option_id' => $opt->id], 'changes' => fn ($opt) => [ ['field' => 'sku', 'label_key' => 'sirsoft-ecommerce::activity_log.fields.sku', 'old' => 'OLD-SKU-'.rand(100, 999), 'new' => $opt->sku ?? 'SKU-'.rand(100, 999), 'type' => 'text'], ]], @@ -271,7 +271,7 @@ class ActivityLogSampleSeeder extends Seeder 'params' => fn ($o) => ['order_number' => $o->order_number, 'count' => 1], 'changes' => fn ($o) => [ ['field' => 'order_status', 'label_key' => 'sirsoft-ecommerce::activity_log.fields.order_status', 'old' => OrderStatusEnum::PREPARING->value, 'new' => OrderStatusEnum::SHIPPING->value, 'type' => 'enum', - 'old_label_key' => OrderStatusEnum::PREPARING->labelKey(), 'new_label_key' => OrderStatusEnum::SHIPPING->labelKey()], + 'old_label_key' => OrderStatusEnum::PREPARING->labelKey(), 'new_label_key' => OrderStatusEnum::SHIPPING->labelKey()], ]], ['action' => 'order.payment_complete', 'key' => 'order_payment_complete', 'loggable' => true, 'params' => fn ($o) => ['order_number' => $o->order_number]], @@ -310,11 +310,11 @@ class ActivityLogSampleSeeder extends Seeder ['action' => 'order_option.bulk_status_change', 'key' => 'order_option_bulk_status_change', 'loggable' => true, 'params' => fn ($opt) => ['count' => 1]], ['action' => 'order_option.confirm', 'key' => 'order_option_confirm', 'loggable' => true, - 'params' => fn ($opt) => ['order_number' => $opt->order?->order_number ?? 'N/A'], - 'properties' => fn ($opt) => ['order_id' => $opt->order_id]], + 'params' => fn ($opt) => ['option_id' => $opt->id], + 'properties' => fn ($opt) => ['order_id' => $opt->order_id, 'option_id' => $opt->id]], ['action' => 'order_option.partial_cancel', 'key' => 'order_option_partial_cancel', 'loggable' => true, - 'params' => fn ($opt) => ['order_number' => $opt->order?->order_number ?? 'N/A'], - 'properties' => fn ($opt) => ['order_id' => $opt->order_id, 'product_name' => $opt->product_name, 'quantity' => $opt->quantity]], + 'params' => fn ($opt) => ['option_id' => $opt->id], + 'properties' => fn ($opt) => ['order_id' => $opt->order_id, 'option_id' => $opt->id, 'product_name' => $opt->product_name, 'quantity' => $opt->quantity]], ]; $count += $this->generateResourceLogs($allOptions, $admins, ActivityLogType::Admin, (new OrderOption)->getMorphClass(), $optionActions); @@ -524,9 +524,9 @@ class ActivityLogSampleSeeder extends Seeder ['action' => 'product_review.bulk_delete', 'key' => 'review_bulk_delete', 'loggable' => true, 'params' => fn ($r) => ['review_id' => $r->id, 'count' => 1]], ['action' => 'product_review.create', 'key' => 'product_review_create', 'loggable' => true, - 'params' => fn ($r) => ['product_name' => $r->product ? $this->getLocalizedName($r->product->name) : '삭제된 상품']], + 'params' => fn ($r) => ['review_id' => $r->id]], ['action' => 'product_review.delete', 'key' => 'product_review_delete', 'loggable' => false, - 'params' => fn ($r) => ['product_name' => $r->product ? $this->getLocalizedName($r->product->name) : '삭제된 상품'], + 'params' => fn ($r) => ['review_id' => $r->id], 'properties' => fn ($r) => ['deleted_id' => $r->id]], ]; @@ -855,7 +855,8 @@ class ActivityLogSampleSeeder extends Seeder // 주문 레벨 $orderActions = [ ['action' => 'order.create', 'key' => 'user_order_create', 'loggable' => true, - 'params' => fn ($o) => ['order_number' => $o->order_number]], + 'params' => fn ($o) => ['order_id' => $o->id], + 'properties' => fn ($o) => ['order_id' => $o->id, 'order_number' => $o->order_number]], ]; $count = $this->generateResourceLogs($orders, $users, ActivityLogType::User, (new Order)->getMorphClass(), $orderActions); @@ -865,8 +866,8 @@ class ActivityLogSampleSeeder extends Seeder if ($allOptions->isNotEmpty()) { $optionActions = [ ['action' => 'order_option.confirm', 'key' => 'user_order_option_confirm', 'loggable' => true, - 'params' => fn ($opt) => ['order_number' => $opt->order?->order_number ?? 'N/A'], - 'properties' => fn ($opt) => ['order_id' => $opt->order_id]], + 'params' => fn ($opt) => ['option_id' => $opt->id], + 'properties' => fn ($opt) => ['order_id' => $opt->order_id, 'option_id' => $opt->id]], ]; $count += $this->generateResourceLogs($allOptions, $users, ActivityLogType::User, (new OrderOption)->getMorphClass(), $optionActions); diff --git a/modules/_bundled/sirsoft-ecommerce/database/seeders/Sample/OrderSeeder.php b/modules/_bundled/sirsoft-ecommerce/database/seeders/Sample/OrderSeeder.php index 8af77f43..ed40a31f 100644 --- a/modules/_bundled/sirsoft-ecommerce/database/seeders/Sample/OrderSeeder.php +++ b/modules/_bundled/sirsoft-ecommerce/database/seeders/Sample/OrderSeeder.php @@ -1553,6 +1553,7 @@ class OrderSeeder extends Seeder $now = now(); $replenished = 0; + $perCoupon = []; foreach ($unique as $record) { CouponIssue::create([ @@ -1567,9 +1568,16 @@ class OrderSeeder extends Seeder 'discount_amount' => null, ]); + $perCoupon[$record['coupon_id']] = ($perCoupon[$record['coupon_id']] ?? 0) + 1; $replenished++; } + // 발급수 카운터 동기화 — 보충 발급도 발급이므로 issued_count 에 반영한다. + // 누락하면 쿠폰 목록의 "발급수" 와 발급 이력의 "총 N건" 이 영구히 어긋난다. + foreach ($perCoupon as $couponId => $count) { + Coupon::where('id', $couponId)->increment('issued_count', $count); + } + $this->command->line(" - 주문서 테스트용 쿠폰 {$replenished}건 보충 발급 완료"); } } diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/inquiries.md b/modules/_bundled/sirsoft-ecommerce/docs/api/inquiries.md index 0e5d4a7f..871e32d5 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/inquiries.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/inquiries.md @@ -298,7 +298,16 @@ HTTP/1.1 200 -**설명** 로그인 회원이 마이페이지에서 자신이 작성한 상품 문의 목록을 조회합니다. `auth:sanctum` 인증만 요구하며, `ProductInquiryService::getUserInquiries()` 가 로그인 사용자(`Auth::id()`)의 문의를 `search`(검색어)·`is_answered`(답변 여부) 필터와 `per_page`(기본 10)로 페이지네이션해 `items`(문의 배열)와 `meta`(페이지 정보)로 반환합니다. 마이페이지 문의 내역 화면을 채우는 데 사용합니다. +**요청 파라미터** + +| 이름 | 타입 | 필수 | 기본값 | 설명 | +| --- | --- | :---: | --- | --- | +| `page` | integer | - | 1 | 페이지 번호 (최소 1) | +| `per_page` | integer | - | 10 | 페이지당 건수 (1~100). 범위를 벗어나면 422 | +| `search` | string | - | - | 검색어 — **상품명**(`product_name_snapshot` 의 로케일별 값) 대상. 문의 제목·본문은 검색하지 않음 | +| `is_answered` | boolean | - | - | 답변 여부 (`0` 답변대기 / `1` 답변완료) | + +**설명** 로그인 회원이 마이페이지에서 자신이 작성한 상품 문의 목록을 조회합니다. `auth:sanctum` 인증만 요구하며, `ProductInquiryService::getUserInquiries()` 가 로그인 사용자(`Auth::id()`)의 문의를 `search`(검색어)·`is_answered`(답변 여부) 필터와 `per_page`(기본 10, 허용 1~100)로 페이지네이션해 `items`(문의 배열)와 `meta`(페이지 정보)로 반환합니다. 마이페이지 문의 내역 화면을 채우는 데 사용합니다. ### DELETE /api/modules/sirsoft-ecommerce/user/inquiries/{inquiryId} diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md b/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md index 4b9acd42..2d97bdd7 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/orders.md @@ -47,7 +47,7 @@ | user_id | query | integer | 아니오 | — | user 식별자 | | orderer_uuid | query | uuid | 아니오 | — | 특정 회원의 주문만 조회하는 주문자 UUID 필터 (회원 검색 연동용) | | member_type | query | string | 아니오 | `member`, `guest` | 회원 구분 필터 (member 회원 주문 / guest 비회원 주문) | -| sort_by | query | string | 아니오 | `ordered_at`, `paid_at`, `total_amount` | 정렬 기준 필드명 (그 외 값은 422) | +| sort_by | query | string | 아니오 | `ordered_at`, `paid_at`, `total_amount`, `shipped_at` | 정렬 기준 필드명 (그 외 값은 422). `shipped_at` 은 배송 테이블 기준 — `desc` 는 주문별 **가장 늦은** 발송일, `asc` 는 **가장 이른** 발송일로 정렬하며 발송 이력이 없는 주문도 목록에 남는다 | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | | per_page | query | integer | 아니오 | min 10, max 100 | 페이지당 항목 수 | | page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md b/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md index cb996aea..15f07a96 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/promotion-coupons.md @@ -29,7 +29,7 @@ | --- | --- | --- | --- | --- | --- | | page | query | integer | 아니오 | min 1 | 조회할 페이지 번호 (1부터 시작) | | per_page | query | integer | 아니오 | min 1, max 100 | 페이지당 항목 수 | -| sort_by | query | string | 아니오 | `created_at`, `name`, `discount_value`, `issued_count` | 정렬 기준 필드명 | +| sort_by | query | string | 아니오 | `created_at`, `name`, `discount_value`, `issued_count`, `valid_to` | 정렬 기준 필드명 (그 외 값은 422). `valid_to` 는 유효기간 종료일 | | sort_order | query | string | 아니오 | `asc`, `desc` | 정렬 방향 (asc 오름차순 / desc 내림차순) | | search_field | query | string | 아니오 | `all`, `name`, `description`, `created_by` | 검색 대상 필드명 (검색어를 적용할 컬럼) | | search_keyword | query | string | 아니오 | max 255 | 검색 키워드 (부분 일치) | diff --git a/modules/_bundled/sirsoft-ecommerce/docs/api/wishlist.md b/modules/_bundled/sirsoft-ecommerce/docs/api/wishlist.md index b5392d1f..8313fba1 100644 --- a/modules/_bundled/sirsoft-ecommerce/docs/api/wishlist.md +++ b/modules/_bundled/sirsoft-ecommerce/docs/api/wishlist.md @@ -72,10 +72,18 @@ HTTP/1.1 200 | 상태코드 | 의미 | 발생 조건 | | --- | --- | --- | | 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 | +| 422 | Validation Error | `page` < 1, `per_page` 가 1~100 범위를 벗어나거나 정수가 아닌 경우 | -**설명** 로그인한 회원의 찜(위시리스트) 목록을 페이지네이션으로 조회합니다. `auth:sanctum` 인증이 필요하며, `WishlistController@index`가 `ProductWishlistService::getByUser()`로 본인 찜 목록만 가져와 `WishlistCollection`으로 반환합니다. `per_page`는 기본 20건이며 최대 100건으로 제한됩니다. 마이페이지의 찜 목록 화면에서 사용합니다. +**요청 파라미터** + +| 이름 | 타입 | 필수 | 기본값 | 설명 | +| --- | --- | :---: | --- | --- | +| `page` | integer | - | 1 | 페이지 번호 (최소 1) | +| `per_page` | integer | - | 20 | 페이지당 건수 (1~100). 범위를 벗어나면 422 | + +**설명** 로그인한 회원의 찜(위시리스트) 목록을 페이지네이션으로 조회합니다. `auth:sanctum` 인증이 필요하며, `WishlistController@index`가 `ProductWishlistService::getByUser()`로 본인 찜 목록만 가져와 `WishlistCollection`으로 반환합니다. 페이지네이션 파라미터는 `WishlistListRequest` 가 검증하며 `per_page` 는 기본 20건, 허용 범위 1~100건입니다(범위를 벗어나면 422). 소프트 삭제된 상품의 찜 행은 목록에서 제외되므로 `total` 은 찜 테이블의 행 수보다 작을 수 있습니다. 마이페이지의 찜 목록 화면에서 사용합니다. ### POST /api/modules/sirsoft-ecommerce/wishlist/toggle diff --git a/modules/_bundled/sirsoft-ecommerce/resources/lang/en/activity_log.php b/modules/_bundled/sirsoft-ecommerce/resources/lang/en/activity_log.php deleted file mode 100644 index 23fa5402..00000000 --- a/modules/_bundled/sirsoft-ecommerce/resources/lang/en/activity_log.php +++ /dev/null @@ -1,159 +0,0 @@ - [ - // Product management (Admin) - 'product_index' => 'Product list viewed', - 'product_show' => 'Product details viewed (ID: :product_id)', - 'product_create' => 'Product created (:product_name)', - 'product_update' => 'Product updated (:product_name)', - 'product_delete' => 'Product deleted (:product_name)', - 'product_bulk_update' => 'Products bulk updated (:count items)', - 'product_bulk_price_update' => 'Product prices bulk updated (:count items)', - 'product_bulk_stock_update' => 'Product stocks bulk updated (:count items)', - 'product_stock_sync' => 'Product stock synced (:product_name)', - - // Order management (Admin) - 'order_index' => 'Order list viewed', - 'order_show' => 'Order details viewed (:order_number)', - 'order_create' => 'Order created (:order_number)', - 'order_update' => 'Order updated (:order_number)', - 'order_delete' => 'Order deleted (:order_number)', - 'order_payment_complete' => 'Payment completed (:order_number)', - 'order_payment_failed' => 'Payment failed (:order_number)', - 'order_cancel' => 'Order fully cancelled (:order_number)', - 'order_partial_cancel' => 'Order partially cancelled (:order_number)', - 'order_coupon_restore' => 'Order cancellation coupon restored (:order_number)', - 'order_mileage_restore' => 'Order cancellation mileage restored (:order_number, :amount)', - 'order_bulk_update' => 'Orders bulk updated (:count items)', - 'order_bulk_status_update' => 'Order statuses bulk updated (:count items)', - 'order_bulk_shipping_update' => 'Shipping info bulk entered (:count items)', - 'order_update_shipping_address' => 'Shipping address changed (:order_number)', - 'order_send_email' => 'Order email sent (:order_number)', - 'order_option_status_change' => 'Order option status changed (Order: :order_number)', - 'order_option_bulk_status_change' => 'Order option statuses bulk changed (:count items)', - 'order_option_confirm' => 'Order option purchase confirmed (Order: :order_number)', - 'order_option_partial_cancel' => 'Order option partially cancelled (Order: :order_number)', - - // Coupon management (Admin) - 'coupon_index' => 'Coupon list viewed', - 'coupon_show' => 'Coupon details viewed (:coupon_name)', - 'coupon_create' => 'Coupon created (:coupon_name)', - 'coupon_update' => 'Coupon updated (:coupon_name)', - 'coupon_delete' => 'Coupon deleted (:coupon_name)', - 'coupon_bulk_status' => 'Coupon statuses bulk changed (:count items)', - - // Shipping policy management (Admin) - 'shipping_policy_index' => 'Shipping policy list viewed', - 'shipping_policy_create' => 'Shipping policy created (:policy_name)', - 'shipping_policy_update' => 'Shipping policy updated (:policy_name)', - 'shipping_policy_delete' => 'Shipping policy deleted (:policy_name)', - 'shipping_policy_toggle_active' => 'Shipping policy active status changed (:policy_name)', - 'shipping_policy_set_default' => 'Default shipping policy set (:policy_name)', - 'shipping_policy_bulk_delete' => 'Shipping policies bulk deleted (:count items)', - 'shipping_policy_bulk_toggle_active' => 'Shipping policy active statuses bulk changed (:count items)', - - // Shipping carrier management (Admin) - 'shipping_carrier_index' => 'Shipping carrier list viewed', - 'shipping_carrier_show' => 'Shipping carrier details viewed (:carrier_name)', - 'shipping_carrier_create' => 'Shipping carrier created (:carrier_name)', - 'shipping_carrier_update' => 'Shipping carrier updated (:carrier_name)', - 'shipping_carrier_delete' => 'Shipping carrier deleted (:carrier_name)', - 'shipping_carrier_toggle_status' => 'Shipping carrier status toggled (:carrier_name)', - - // Category management (Admin) - 'category_index' => 'Category list viewed', - 'category_show' => 'Category details viewed (:category_name)', - 'category_create' => 'Category created (:category_name)', - 'category_update' => 'Category updated (:category_name)', - 'category_delete' => 'Category deleted (:category_name)', - 'category_reorder' => 'Category order changed', - 'category_toggle_status' => 'Category status toggled (:category_name)', - - // Brand management (Admin) - 'brand_index' => 'Brand list viewed', - 'brand_show' => 'Brand details viewed (:brand_name)', - 'brand_create' => 'Brand created (:brand_name)', - 'brand_update' => 'Brand updated (:brand_name)', - 'brand_delete' => 'Brand deleted (:brand_name)', - 'brand_toggle_status' => 'Brand status toggled (:brand_name)', - - // Label management (Admin) - 'label_index' => 'Product label list viewed', - 'label_create' => 'Product label created (:label_name)', - 'label_update' => 'Product label updated (:label_name)', - 'label_delete' => 'Product label deleted (:label_name)', - 'label_toggle_status' => 'Product label status toggled (:label_name)', - - // Product common info (Admin) - 'product_common_info_index' => 'Product common info list viewed', - 'product_common_info_create' => 'Product common info created (:info_name)', - 'product_common_info_update' => 'Product common info updated (:info_name)', - 'product_common_info_delete' => 'Product common info deleted (:info_name)', - - // Product notice template (Admin) - 'product_notice_template_index' => 'Product notice template list viewed', - 'product_notice_template_create' => 'Product notice template created (:template_name)', - 'product_notice_template_update' => 'Product notice template updated (:template_name)', - 'product_notice_template_delete' => 'Product notice template deleted (:template_name)', - 'product_notice_template_copy' => 'Product notice template copied (:template_name)', - - // Extra fee template (Admin) - 'extra_fee_template_index' => 'Extra fee template list viewed', - 'extra_fee_template_create' => 'Extra fee template created (:template_name)', - 'extra_fee_template_update' => 'Extra fee template updated (:template_name)', - 'extra_fee_template_delete' => 'Extra fee template deleted (:template_name)', - 'extra_fee_template_toggle_active' => 'Extra fee template active status changed (:template_name)', - 'extra_fee_template_bulk_delete' => 'Extra fee templates bulk deleted (:count items)', - 'extra_fee_template_bulk_toggle_active' => 'Extra fee template active statuses bulk changed (:count items)', - 'extra_fee_template_bulk_create' => 'Extra fee templates bulk created (:count items)', - - // Product option (Admin) - 'product_option_bulk_price_update' => 'Product option prices bulk updated (:count items)', - 'product_option_bulk_stock_update' => 'Product option stocks bulk updated (:count items)', - 'product_option_bulk_update' => 'Product options bulk updated (:count items)', - - // Product image (Admin) - 'product_image_upload' => 'Product image uploaded (Product: :product_name)', - 'product_image_delete' => 'Product image deleted (Product: :product_name)', - 'product_image_reorder' => 'Product image order changed (Product: :product_name)', - - // Review management (Admin) - 'review_index' => 'Review list viewed', - 'review_show' => 'Review details viewed (ID: :review_id)', - 'review_create' => 'Review created (Product: :product_name)', - 'review_delete' => 'Review deleted (ID: :review_id)', - 'review_bulk_delete' => 'Reviews bulk deleted (:count items)', - 'review_reply' => 'Review reply created (ID: :review_id)', - 'product_review_create' => 'Product review created (Product: :product_name)', - 'product_review_delete' => 'Product review deleted (Product: :product_name)', - - // Ecommerce settings - 'ecommerce_settings_index' => 'Ecommerce settings viewed', - - // Payment (Admin) - 'payment_refund' => 'Payment refunded (Order: :order_number)', - - // User actions (ActivityLogType::User) - 'cart_add' => 'Added to cart (:product_name)', - 'cart_update_quantity' => 'Cart quantity changed (:product_name)', - 'cart_change_option' => 'Cart option changed (:product_name)', - 'cart_delete' => 'Cart item deleted', - 'cart_delete_all' => 'Cart cleared', - 'wishlist_add' => 'Added to wishlist (:product_name)', - 'wishlist_remove' => 'Removed from wishlist (:product_name)', - 'coupon_use' => 'Coupon used (:coupon_name)', - 'coupon_restore' => 'Coupon restored (:coupon_name)', - 'user_coupon_download' => 'Coupon downloaded (:coupon_name)', - 'mileage_earn' => 'Mileage earned (:amount)', - 'mileage_use' => 'Mileage used (:amount)', - 'mileage_restore' => 'Mileage restored (:amount)', - 'mileage_expire' => 'Mileage expired (:amount)', - 'mileage_earn_cancel' => 'Mileage earning canceled (:amount)', - 'mileage_admin_earn' => 'Mileage granted manually (:amount)', - 'mileage_admin_deduct' => 'Mileage deducted manually (:amount)', - 'mileage_extend_expiry' => 'Mileage expiry extended (:days days)', - 'user_order_create' => 'Order created (:order_number)', - 'user_order_option_confirm' => 'Purchase confirmed (Order: :order_number)', - ], -]; diff --git a/modules/_bundled/sirsoft-ecommerce/resources/lang/en/enums.php b/modules/_bundled/sirsoft-ecommerce/resources/lang/en/enums.php deleted file mode 100644 index 896e383a..00000000 --- a/modules/_bundled/sirsoft-ecommerce/resources/lang/en/enums.php +++ /dev/null @@ -1,19 +0,0 @@ - [ - 'visible' => 'Visible', - 'hidden' => 'Hidden', - ], - - 'has_reply' => [ - 'replied' => 'Replied', - 'not_replied' => 'Not Replied', - ], -]; diff --git a/modules/_bundled/sirsoft-ecommerce/resources/lang/en/messages.php b/modules/_bundled/sirsoft-ecommerce/resources/lang/en/messages.php deleted file mode 100644 index e8a2d63b..00000000 --- a/modules/_bundled/sirsoft-ecommerce/resources/lang/en/messages.php +++ /dev/null @@ -1,87 +0,0 @@ - [ - // Order creation - 'created' => 'Your order has been placed.', - 'create_failed' => 'Failed to create order.', - - // Order status - 'status_changed' => 'Order status has been changed.', - 'cancelled' => 'Order has been cancelled.', - 'cancel_failed' => 'Failed to cancel order.', - - // Auto cancel - 'auto_cancel_expired_reason' => 'Automatically cancelled due to payment deadline expiration', - - // Payment - 'payment_cancelled' => 'Payment cancellation has been recorded.', - 'payment_pending' => 'Awaiting payment.', - 'payment_completed' => 'Payment completed.', - 'payment_failed' => 'Payment failed.', - - // Stock - 'stock_insufficient' => 'Insufficient stock.', - 'stock_restored' => 'Stock has been restored.', - - // Validation - 'not_found' => 'Order not found.', - 'invalid_status' => 'Invalid order status.', - 'amount_mismatch' => 'Payment amount does not match.', - ], - - 'address' => [ - 'created' => 'Address has been added.', - 'create_failed' => 'Failed to add address.', - 'updated' => 'Address has been updated.', - 'update_failed' => 'Failed to update address.', - 'deleted' => 'Address has been deleted.', - 'delete_failed' => 'Failed to delete address.', - 'not_found' => 'Address not found.', - 'fetched' => 'Address retrieved.', - 'fetch_failed' => 'Failed to retrieve address.', - 'list_fetched' => 'Address list retrieved.', - 'list_fetch_failed' => 'Failed to retrieve address list.', - 'set_default' => 'Set as default address.', - 'default_set' => 'Set as default address.', - 'set_default_failed' => 'Failed to set default address.', - 'name_duplicate' => 'An address with the same name already exists.', - 'auto_saved_label' => 'Auto-saved address', - ], - - 'cart' => [ - 'added' => 'Added to cart.', - 'updated' => 'Cart has been updated.', - 'removed' => 'Removed from cart.', - 'empty' => 'Your cart is empty.', - ], - - 'product' => [ - 'not_found' => 'Product not found.', - 'not_available' => 'This product is currently not available.', - 'option_not_found' => 'Product option not found.', - ], - - 'coupon' => [ - 'applied' => 'Coupon applied.', - 'removed' => 'Coupon removed.', - 'invalid' => 'Invalid coupon.', - 'expired' => 'This coupon has expired.', - 'already_used' => 'This coupon has already been used.', - 'min_amount_not_met' => 'Minimum order amount not met.', - ], - - 'payment' => [ - 'method_not_supported' => 'Payment method not supported.', - 'dbank_account_required' => 'Please select a bank account for bank transfer.', - 'depositor_name_required' => 'Please enter the depositor name.', - 'provider_not_found' => 'Payment provider not found.', - 'client_config_success' => 'Payment client configuration retrieved.', - ], -]; diff --git a/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/activity_log.php b/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/activity_log.php deleted file mode 100644 index d8248dfb..00000000 --- a/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/activity_log.php +++ /dev/null @@ -1,159 +0,0 @@ - [ - // 상품 관리 (Admin) - 'product_index' => '상품 목록 조회', - 'product_show' => '상품 상세 조회 (ID: :product_id)', - 'product_create' => '상품 생성 (:product_name)', - 'product_update' => '상품 수정 (:product_name)', - 'product_delete' => '상품 삭제 (:product_name)', - 'product_bulk_update' => '상품 일괄 수정 (:count건)', - 'product_bulk_price_update' => '상품 일괄 가격 수정 (:count건)', - 'product_bulk_stock_update' => '상품 일괄 재고 수정 (:count건)', - 'product_stock_sync' => '상품 재고 동기화 (:product_name)', - - // 주문 관리 (Admin) - 'order_index' => '주문 목록 조회', - 'order_show' => '주문 상세 조회 (:order_number)', - 'order_create' => '주문 생성 (:order_number)', - 'order_update' => '주문 수정 (:order_number)', - 'order_delete' => '주문 삭제 (:order_number)', - 'order_payment_complete' => '결제 완료 (:order_number)', - 'order_payment_failed' => '결제 실패 (:order_number)', - 'order_cancel' => '주문 전체 취소 (:order_number)', - 'order_partial_cancel' => '주문 부분 취소 (:order_number)', - 'order_coupon_restore' => '주문 취소 쿠폰 복원 (:order_number)', - 'order_mileage_restore' => '주문 취소 마일리지 복원 (:order_number, :amount원)', - 'order_bulk_update' => '주문 일괄 변경 (:count건)', - 'order_bulk_status_update' => '주문 일괄 상태 변경 (:count건)', - 'order_bulk_shipping_update' => '운송장 일괄 입력 (:count건)', - 'order_update_shipping_address' => '배송지 변경 (:order_number)', - 'order_send_email' => '주문 이메일 발송 (:order_number)', - 'order_option_status_change' => '주문 옵션 상태 변경 (주문: :order_number)', - 'order_option_bulk_status_change' => '주문 옵션 일괄 상태 변경 (:count건)', - 'order_option_confirm' => '주문 옵션 구매 확정 (주문: :order_number)', - 'order_option_partial_cancel' => '주문 옵션 부분 취소 (주문: :order_number)', - - // 쿠폰 관리 (Admin) - 'coupon_index' => '쿠폰 목록 조회', - 'coupon_show' => '쿠폰 상세 조회 (:coupon_name)', - 'coupon_create' => '쿠폰 생성 (:coupon_name)', - 'coupon_update' => '쿠폰 수정 (:coupon_name)', - 'coupon_delete' => '쿠폰 삭제 (:coupon_name)', - 'coupon_bulk_status' => '쿠폰 일괄 상태 변경 (:count건)', - - // 배송정책 관리 (Admin) - 'shipping_policy_index' => '배송정책 목록 조회', - 'shipping_policy_create' => '배송정책 생성 (:policy_name)', - 'shipping_policy_update' => '배송정책 수정 (:policy_name)', - 'shipping_policy_delete' => '배송정책 삭제 (:policy_name)', - 'shipping_policy_toggle_active' => '배송정책 활성 상태 변경 (:policy_name)', - 'shipping_policy_set_default' => '기본 배송정책 설정 (:policy_name)', - 'shipping_policy_bulk_delete' => '배송정책 일괄 삭제 (:count건)', - 'shipping_policy_bulk_toggle_active' => '배송정책 일괄 활성 상태 변경 (:count건)', - - // 배송업체 관리 (Admin) - 'shipping_carrier_index' => '배송업체 목록 조회', - 'shipping_carrier_show' => '배송업체 상세 조회 (:carrier_name)', - 'shipping_carrier_create' => '배송업체 생성 (:carrier_name)', - 'shipping_carrier_update' => '배송업체 수정 (:carrier_name)', - 'shipping_carrier_delete' => '배송업체 삭제 (:carrier_name)', - 'shipping_carrier_toggle_status' => '배송업체 상태 전환 (:carrier_name)', - - // 카테고리 관리 (Admin) - 'category_index' => '카테고리 목록 조회', - 'category_show' => '카테고리 상세 조회 (:category_name)', - 'category_create' => '카테고리 생성 (:category_name)', - 'category_update' => '카테고리 수정 (:category_name)', - 'category_delete' => '카테고리 삭제 (:category_name)', - 'category_reorder' => '카테고리 순서 변경', - 'category_toggle_status' => '카테고리 상태 전환 (:category_name)', - - // 브랜드 관리 (Admin) - 'brand_index' => '브랜드 목록 조회', - 'brand_show' => '브랜드 상세 조회 (:brand_name)', - 'brand_create' => '브랜드 생성 (:brand_name)', - 'brand_update' => '브랜드 수정 (:brand_name)', - 'brand_delete' => '브랜드 삭제 (:brand_name)', - 'brand_toggle_status' => '브랜드 상태 전환 (:brand_name)', - - // 라벨 관리 (Admin) - 'label_index' => '상품 라벨 목록 조회', - 'label_create' => '상품 라벨 생성 (:label_name)', - 'label_update' => '상품 라벨 수정 (:label_name)', - 'label_delete' => '상품 라벨 삭제 (:label_name)', - 'label_toggle_status' => '상품 라벨 상태 전환 (:label_name)', - - // 상품 공통정보 (Admin) - 'product_common_info_index' => '상품 공통정보 목록 조회', - 'product_common_info_create' => '상품 공통정보 생성 (:info_name)', - 'product_common_info_update' => '상품 공통정보 수정 (:info_name)', - 'product_common_info_delete' => '상품 공통정보 삭제 (:info_name)', - - // 상품 고시정보 템플릿 (Admin) - 'product_notice_template_index' => '상품 고시정보 템플릿 목록 조회', - 'product_notice_template_create' => '상품 고시정보 템플릿 생성 (:template_name)', - 'product_notice_template_update' => '상품 고시정보 템플릿 수정 (:template_name)', - 'product_notice_template_delete' => '상품 고시정보 템플릿 삭제 (:template_name)', - 'product_notice_template_copy' => '상품 고시정보 템플릿 복제 (:template_name)', - - // 추가비용 템플릿 (Admin) - 'extra_fee_template_index' => '추가비용 템플릿 목록 조회', - 'extra_fee_template_create' => '추가비용 템플릿 생성 (:template_name)', - 'extra_fee_template_update' => '추가비용 템플릿 수정 (:template_name)', - 'extra_fee_template_delete' => '추가비용 템플릿 삭제 (:template_name)', - 'extra_fee_template_toggle_active' => '추가비용 템플릿 활성 상태 변경 (:template_name)', - 'extra_fee_template_bulk_delete' => '추가비용 템플릿 일괄 삭제 (:count건)', - 'extra_fee_template_bulk_toggle_active' => '추가비용 템플릿 일괄 활성 상태 변경 (:count건)', - 'extra_fee_template_bulk_create' => '추가비용 템플릿 일괄 생성 (:count건)', - - // 상품옵션 (Admin) - 'product_option_bulk_price_update' => '상품옵션 일괄 가격 수정 (:count건)', - 'product_option_bulk_stock_update' => '상품옵션 일괄 재고 수정 (:count건)', - 'product_option_bulk_update' => '상품옵션 일괄 수정 (:count건)', - - // 상품 이미지 (Admin) - 'product_image_upload' => '상품 이미지 업로드 (상품: :product_name)', - 'product_image_delete' => '상품 이미지 삭제 (상품: :product_name)', - 'product_image_reorder' => '상품 이미지 순서 변경 (상품: :product_name)', - - // 리뷰 관리 (Admin) - 'review_index' => '리뷰 목록 조회', - 'review_show' => '리뷰 상세 조회 (ID: :review_id)', - 'review_create' => '리뷰 작성 (상품: :product_name)', - 'review_delete' => '리뷰 삭제 (ID: :review_id)', - 'review_bulk_delete' => '리뷰 일괄 삭제 (:count건)', - 'review_reply' => '리뷰 답변 작성 (ID: :review_id)', - 'product_review_create' => '상품 리뷰 작성 (상품: :product_name)', - 'product_review_delete' => '상품 리뷰 삭제 (상품: :product_name)', - - // 이커머스 설정 - 'ecommerce_settings_index' => '이커머스 설정 조회', - - // 결제 (Admin) - 'payment_refund' => '결제 환불 (주문: :order_number)', - - // 사용자 행위 (ActivityLogType::User) - 'cart_add' => '장바구니 추가 (:product_name)', - 'cart_update_quantity' => '장바구니 수량 변경 (:product_name)', - 'cart_change_option' => '장바구니 옵션 변경 (:product_name)', - 'cart_delete' => '장바구니 삭제', - 'cart_delete_all' => '장바구니 전체 삭제', - 'wishlist_add' => '위시리스트 추가 (:product_name)', - 'wishlist_remove' => '위시리스트 제거 (:product_name)', - 'coupon_use' => '쿠폰 사용 (:coupon_name)', - 'coupon_restore' => '쿠폰 복원 (:coupon_name)', - 'user_coupon_download' => '쿠폰 다운로드 (:coupon_name)', - 'mileage_earn' => '마일리지 적립 (:amount원)', - 'mileage_use' => '마일리지 사용 (:amount원)', - 'mileage_restore' => '마일리지 복원 (:amount원)', - 'mileage_expire' => '마일리지 소멸 (:amount원)', - 'mileage_earn_cancel' => '마일리지 적립 회수 (:amount원)', - 'mileage_admin_earn' => '마일리지 수동 지급 (:amount원)', - 'mileage_admin_deduct' => '마일리지 수동 차감 (:amount원)', - 'mileage_extend_expiry' => '마일리지 유효기간 연장 (:days일)', - 'user_order_create' => '주문 생성 (:order_number)', - 'user_order_option_confirm' => '구매 확정 (주문: :order_number)', - ], -]; diff --git a/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/enums.php b/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/enums.php deleted file mode 100644 index 7d67095b..00000000 --- a/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/enums.php +++ /dev/null @@ -1,19 +0,0 @@ - [ - 'visible' => '전시중', - 'hidden' => '숨김', - ], - - 'has_reply' => [ - 'replied' => '답변완료', - 'not_replied' => '미답변', - ], -]; diff --git a/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/messages.php b/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/messages.php deleted file mode 100644 index 502cb13a..00000000 --- a/modules/_bundled/sirsoft-ecommerce/resources/lang/ko/messages.php +++ /dev/null @@ -1,87 +0,0 @@ - [ - // 주문 생성 - 'created' => '주문이 완료되었습니다.', - 'create_failed' => '주문 생성에 실패했습니다.', - - // 주문 상태 - 'status_changed' => '주문 상태가 변경되었습니다.', - 'cancelled' => '주문이 취소되었습니다.', - 'cancel_failed' => '주문 취소에 실패했습니다.', - - // 자동 취소 - 'auto_cancel_expired_reason' => '입금 기한 만료로 인한 자동 취소', - - // 결제 - 'payment_cancelled' => '결제 취소가 기록되었습니다.', - 'payment_pending' => '입금 대기 중입니다.', - 'payment_completed' => '결제가 완료되었습니다.', - 'payment_failed' => '결제에 실패했습니다.', - - // 재고 - 'stock_insufficient' => '재고가 부족합니다.', - 'stock_restored' => '재고가 복원되었습니다.', - - // 검증 - 'not_found' => '주문을 찾을 수 없습니다.', - 'invalid_status' => '유효하지 않은 주문 상태입니다.', - 'amount_mismatch' => '결제 금액이 일치하지 않습니다.', - ], - - 'address' => [ - 'created' => '배송지가 추가되었습니다.', - 'create_failed' => '배송지 추가에 실패했습니다.', - 'updated' => '배송지가 수정되었습니다.', - 'update_failed' => '배송지 수정에 실패했습니다.', - 'deleted' => '배송지가 삭제되었습니다.', - 'delete_failed' => '배송지 삭제에 실패했습니다.', - 'not_found' => '배송지를 찾을 수 없습니다.', - 'fetched' => '배송지 정보를 조회했습니다.', - 'fetch_failed' => '배송지 조회에 실패했습니다.', - 'list_fetched' => '배송지 목록을 조회했습니다.', - 'list_fetch_failed' => '배송지 목록 조회에 실패했습니다.', - 'set_default' => '기본 배송지로 설정되었습니다.', - 'default_set' => '기본 배송지로 설정되었습니다.', - 'set_default_failed' => '기본 배송지 설정에 실패했습니다.', - 'name_duplicate' => '동일한 이름의 배송지가 이미 존재합니다.', - 'auto_saved_label' => '자동 저장된 배송지', - ], - - 'cart' => [ - 'added' => '장바구니에 추가되었습니다.', - 'updated' => '장바구니가 수정되었습니다.', - 'removed' => '장바구니에서 삭제되었습니다.', - 'empty' => '장바구니가 비어있습니다.', - ], - - 'product' => [ - 'not_found' => '상품을 찾을 수 없습니다.', - 'not_available' => '현재 구매할 수 없는 상품입니다.', - 'option_not_found' => '상품 옵션을 찾을 수 없습니다.', - ], - - 'coupon' => [ - 'applied' => '쿠폰이 적용되었습니다.', - 'removed' => '쿠폰이 해제되었습니다.', - 'invalid' => '유효하지 않은 쿠폰입니다.', - 'expired' => '만료된 쿠폰입니다.', - 'already_used' => '이미 사용된 쿠폰입니다.', - 'min_amount_not_met' => '최소 주문 금액 조건을 충족하지 않습니다.', - ], - - 'payment' => [ - 'method_not_supported' => '지원하지 않는 결제 수단입니다.', - 'dbank_account_required' => '무통장입금 계좌를 선택해주세요.', - 'depositor_name_required' => '입금자명을 입력해주세요.', - 'provider_not_found' => '결제 제공자를 찾을 수 없습니다.', - 'client_config_success' => '결제 클라이언트 설정을 조회했습니다.', - ], -]; diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_mileage_transaction_index/_transactions_table.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_mileage_transaction_index/_transactions_table.json index 7e97e4d9..0f85fa98 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_mileage_transaction_index/_transactions_table.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_mileage_transaction_index/_transactions_table.json @@ -36,7 +36,7 @@ "alwaysShowPagination": true, "columns": [ { - "field": "no", + "field": "number", "header": "NO", "width": "56px", "sortable": false, @@ -47,7 +47,7 @@ "props": { "className": "text-gray-500 dark:text-gray-400" }, - "text": "{{row.no}}" + "text": "{{row.number}}" } ] }, diff --git a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_promotion_coupon_list/_modal_issue_history.json b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_promotion_coupon_list/_modal_issue_history.json index 14a8fa95..f2415111 100644 --- a/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_promotion_coupon_list/_modal_issue_history.json +++ b/modules/_bundled/sirsoft-ecommerce/resources/layouts/admin/partials/admin_ecommerce_promotion_coupon_list/_modal_issue_history.json @@ -82,7 +82,7 @@ "name": "Icon", "props": { "name": "x", - "className": "w-3 h-3" + "className": "text-xs" } } ] @@ -182,7 +182,7 @@ "name": "Icon", "props": { "name": "user", - "className": "w-4 h-4 text-gray-400 dark:text-gray-500" + "className": "text-base text-gray-400 dark:text-gray-500" } }, { diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductCommonInfoController.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductCommonInfoController.php index f023a91d..821b81fe 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductCommonInfoController.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ProductCommonInfoController.php @@ -6,7 +6,7 @@ use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\AdminBaseController; use Exception; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; +use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\IndexProductCommonInfoRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\StoreProductCommonInfoRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\UpdateProductCommonInfoRequest; use Modules\Sirsoft\Ecommerce\Http\Resources\ProductCommonInfoCollection; @@ -27,25 +27,15 @@ class ProductCommonInfoController extends AdminBaseController /** * 공통정보 목록을 조회합니다. * - * @param Request $request 요청 데이터 + * @param IndexProductCommonInfoRequest $request 요청 데이터 * @return JsonResponse 공통정보 목록 JSON 응답 */ - public function index(Request $request): JsonResponse + public function index(IndexProductCommonInfoRequest $request): JsonResponse { - $filters = [ - 'search' => $request->get('search'), - 'is_active' => $request->boolean('active_only', false) ? true : null, - 'is_default' => $request->has('default_only') && $request->boolean('default_only') ? true : null, - ]; - - // null 값 제거 - $filters = array_filter($filters, fn ($v) => $v !== null); - - // 페이지네이션 파라미터 - $perPage = (int) $request->get('per_page', 20); + $filters = $request->filters(); // per_page가 0 이하이거나 all이면 전체 조회 - if ($perPage <= 0 || $request->get('per_page') === 'all') { + if ($request->wantsAll()) { $commonInfos = $this->commonInfoService->getAllCommonInfos($filters); return ResponseHelper::moduleSuccess( @@ -56,7 +46,7 @@ class ProductCommonInfoController extends AdminBaseController } // 페이지네이션 조회 - $commonInfos = $this->commonInfoService->getPaginatedCommonInfos($filters, $perPage); + $commonInfos = $this->commonInfoService->getPaginatedCommonInfos($filters, $request->perPage()); return ResponseHelper::moduleSuccess( 'sirsoft-ecommerce', @@ -68,7 +58,7 @@ class ProductCommonInfoController extends AdminBaseController /** * 공통정보 상세를 조회합니다. * - * @param int $id 공통정보 ID + * @param int $id 공통정보 ID * @return JsonResponse 공통정보 상세 JSON 응답 */ public function show(int $id): JsonResponse @@ -93,7 +83,7 @@ class ProductCommonInfoController extends AdminBaseController /** * 공통정보를 생성합니다. * - * @param StoreProductCommonInfoRequest $request 생성 요청 + * @param StoreProductCommonInfoRequest $request 생성 요청 * @return JsonResponse 생성된 공통정보 JSON 응답 */ public function store(StoreProductCommonInfoRequest $request): JsonResponse @@ -119,8 +109,8 @@ class ProductCommonInfoController extends AdminBaseController /** * 공통정보를 수정합니다. * - * @param UpdateProductCommonInfoRequest $request 수정 요청 - * @param int $id 공통정보 ID + * @param UpdateProductCommonInfoRequest $request 수정 요청 + * @param int $id 공통정보 ID * @return JsonResponse 수정된 공통정보 JSON 응답 */ public function update(UpdateProductCommonInfoRequest $request, int $id): JsonResponse @@ -145,7 +135,7 @@ class ProductCommonInfoController extends AdminBaseController /** * 공통정보를 삭제합니다. * - * @param int $id 공통정보 ID + * @param int $id 공통정보 ID * @return JsonResponse 삭제 결과 JSON 응답 */ public function destroy(int $id): JsonResponse @@ -170,7 +160,7 @@ class ProductCommonInfoController extends AdminBaseController /** * 공통정보 사용 여부를 토글합니다. * - * @param int $id 공통정보 ID + * @param int $id 공통정보 ID * @return JsonResponse 토글된 공통정보 JSON 응답 */ public function toggleActive(int $id): JsonResponse diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ShippingCarrierController.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ShippingCarrierController.php index fa88fa48..0eae9b7e 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ShippingCarrierController.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Admin/ShippingCarrierController.php @@ -5,7 +5,8 @@ namespace Modules\Sirsoft\Ecommerce\Http\Controllers\Admin; use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\AdminBaseController; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; +use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\ActiveShippingCarrierRequest; +use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\IndexShippingCarrierRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\StoreShippingCarrierRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Admin\UpdateShippingCarrierRequest; use Modules\Sirsoft\Ecommerce\Http\Resources\ShippingCarrierCollection; @@ -24,12 +25,12 @@ class ShippingCarrierController extends AdminBaseController /** * 배송사 목록 조회 * - * @param Request $request + * @param IndexShippingCarrierRequest $request * @return JsonResponse */ - public function index(Request $request): JsonResponse + public function index(IndexShippingCarrierRequest $request): JsonResponse { - $carriers = $this->carrierService->getAllCarriers($request->all()); + $carriers = $this->carrierService->getAllCarriers($request->filters()); return ResponseHelper::moduleSuccess( 'sirsoft-ecommerce', @@ -41,7 +42,7 @@ class ShippingCarrierController extends AdminBaseController /** * 배송사 생성 * - * @param StoreShippingCarrierRequest $request + * @param StoreShippingCarrierRequest $request * @return JsonResponse */ public function store(StoreShippingCarrierRequest $request): JsonResponse @@ -59,13 +60,12 @@ class ShippingCarrierController extends AdminBaseController /** * 활성 배송사 목록 조회 (Select 옵션용) * - * @param Request $request + * @param ActiveShippingCarrierRequest $request * @return JsonResponse */ - public function active(Request $request): JsonResponse + public function active(ActiveShippingCarrierRequest $request): JsonResponse { - $type = $request->query('type'); - $carriers = $this->carrierService->getActiveCarriers($type); + $carriers = $this->carrierService->getActiveCarriers($request->type()); $data = $carriers->map(function ($carrier) { return [ @@ -87,7 +87,7 @@ class ShippingCarrierController extends AdminBaseController /** * 배송사 상세 조회 * - * @param int $id + * @param int $id * @return JsonResponse */ public function show(int $id): JsonResponse @@ -112,8 +112,8 @@ class ShippingCarrierController extends AdminBaseController /** * 배송사 수정 * - * @param UpdateShippingCarrierRequest $request - * @param int $carrier + * @param UpdateShippingCarrierRequest $request + * @param int $carrier * @return JsonResponse */ public function update(UpdateShippingCarrierRequest $request, int $carrier): JsonResponse @@ -138,7 +138,7 @@ class ShippingCarrierController extends AdminBaseController /** * 배송사 삭제 * - * @param int $carrier + * @param int $carrier * @return JsonResponse */ public function destroy(int $carrier): JsonResponse @@ -173,7 +173,7 @@ class ShippingCarrierController extends AdminBaseController /** * 배송사 상태 토글 * - * @param int $carrier + * @param int $carrier * @return JsonResponse */ public function toggleStatus(int $carrier): JsonResponse diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/ProductInquiryController.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/ProductInquiryController.php index fec6c118..48395305 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/ProductInquiryController.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/ProductInquiryController.php @@ -6,6 +6,7 @@ use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\PublicBaseController; use Exception; use Illuminate\Http\JsonResponse; +use Modules\Sirsoft\Ecommerce\Http\Requests\Public\ProductInquiryListRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\Public\StoreInquiryRequest; use Modules\Sirsoft\Ecommerce\Models\Product; use Modules\Sirsoft\Ecommerce\Services\ProductInquiryService; @@ -25,16 +26,18 @@ class ProductInquiryController extends PublicBaseController /** * 상품 문의 목록 조회 * + * @param ProductInquiryListRequest $request 목록 조회 요청 (페이지네이션 상·하한 검증) * @param Product $product 라우트 바인딩된 상품 (product_code 또는 id) * @return JsonResponse 문의 목록 및 board_settings 메타 JSON 응답 */ - public function index(Product $product): JsonResponse + public function index(ProductInquiryListRequest $request, Product $product): JsonResponse { try { $this->logApiUsage('inquiry.index'); - $perPage = (int) (request()->query('per_page', 10)); - $page = (int) (request()->query('page', 1)); - $excludeSecret = filter_var(request()->query('exclude_secret', false), FILTER_VALIDATE_BOOLEAN); + $validated = $request->validated(); + $perPage = (int) ($validated['per_page'] ?? 10); + $page = (int) ($validated['page'] ?? 1); + $excludeSecret = filter_var($validated['exclude_secret'] ?? false, FILTER_VALIDATE_BOOLEAN); $result = $this->inquiryService->getProductInquiries($product->id, $perPage, $page, $excludeSecret); @@ -72,10 +75,15 @@ class ProductInquiryController extends PublicBaseController 201 ); } catch (\RuntimeException $e) { + // 실패 사유(문의 게시판 미설정·게시판 모듈 불가 등)를 그대로 보여준다. + // 일반 문구만 남기면 서버 기록을 봐야만 원인을 알 수 있다 — 같은 기능의 + // 수정·답변 경로는 이미 사유를 노출하고 있어 안내 수준을 맞춘다. return ResponseHelper::moduleError( 'sirsoft-ecommerce', - 'messages.inquiries.create_failed', - 422 + 'messages.inquiries.operation_failed_reason', + 422, + null, + ['reason' => $e->getMessage()] ); } catch (Exception $e) { return ResponseHelper::moduleError( diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/PublicCouponController.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/PublicCouponController.php index b7b0eef8..ce22d367 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/PublicCouponController.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/PublicCouponController.php @@ -6,7 +6,7 @@ use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\PublicBaseController; use Exception; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; +use Modules\Sirsoft\Ecommerce\Http\Requests\Public\DownloadableCouponsRequest; use Modules\Sirsoft\Ecommerce\Models\Product; use Modules\Sirsoft\Ecommerce\Services\UserCouponService; @@ -27,11 +27,11 @@ class PublicCouponController extends PublicBaseController * 선택적 인증: 라우트에 optional.sanctum 미들웨어 적용 * 로그인 시 is_downloaded 정보가 추가됩니다. * - * @param Request $request 요청 데이터 - * @param Product $product 라우트 바인딩된 상품 (product_code 또는 id) + * @param DownloadableCouponsRequest $request 요청 데이터 + * @param Product $product 라우트 바인딩된 상품 (product_code 또는 id) * @return JsonResponse 쿠폰 목록 */ - public function downloadableCoupons(Request $request, Product $product): JsonResponse + public function downloadableCoupons(DownloadableCouponsRequest $request, Product $product): JsonResponse { try { $this->logApiUsage('products.downloadable_coupons'); diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/WishlistController.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/WishlistController.php index 66f61cb2..94d103a5 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/WishlistController.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/WishlistController.php @@ -6,9 +6,9 @@ use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\PublicBaseController; use Exception; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Modules\Sirsoft\Ecommerce\Http\Requests\Public\ToggleWishlistRequest; +use Modules\Sirsoft\Ecommerce\Http\Requests\User\WishlistListRequest; use Modules\Sirsoft\Ecommerce\Http\Resources\WishlistCollection; use Modules\Sirsoft\Ecommerce\Services\ProductWishlistService; @@ -26,7 +26,7 @@ class WishlistController extends PublicBaseController /** * 찜 토글 (추가/제거) * - * @param ToggleWishlistRequest $request 검증된 요청 + * @param ToggleWishlistRequest $request 검증된 요청 * @return JsonResponse 토글 결과 */ public function toggle(ToggleWishlistRequest $request): JsonResponse @@ -54,16 +54,16 @@ class WishlistController extends PublicBaseController /** * 사용자 찜 목록 조회 * - * @param Request $request 요청 + * @param WishlistListRequest $request 목록 조회 요청 (페이지네이션 상·하한 검증) * @return JsonResponse 찜 목록 */ - public function index(Request $request): JsonResponse + public function index(WishlistListRequest $request): JsonResponse { try { $this->logApiUsage('wishlist.index'); $userId = Auth::id(); - $perPage = min((int) $request->input('per_page', 20), 100); + $perPage = (int) ($request->validated()['per_page'] ?? 20); $wishlists = $this->wishlistService->getByUser($userId, $perPage); @@ -80,7 +80,7 @@ class WishlistController extends PublicBaseController /** * 찜 삭제 * - * @param int $id 찜 ID + * @param int $id 찜 ID * @return JsonResponse 삭제 결과 */ public function destroy(int $id): JsonResponse diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/User/ProductInquiryController.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/User/ProductInquiryController.php index 0b5caa97..72ac9ab5 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/User/ProductInquiryController.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/User/ProductInquiryController.php @@ -8,8 +8,9 @@ use App\Http\Controllers\Api\Base\AuthBaseController; use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth; -use Modules\Sirsoft\Ecommerce\Http\Requests\User\UpdateInquiryRequest; use Modules\Sirsoft\Ecommerce\Http\Requests\User\UpdateInquiryReplyRequest; +use Modules\Sirsoft\Ecommerce\Http\Requests\User\UpdateInquiryRequest; +use Modules\Sirsoft\Ecommerce\Http\Requests\User\UserInquiryListRequest; use Modules\Sirsoft\Ecommerce\Services\ProductInquiryService; /** @@ -26,16 +27,18 @@ class ProductInquiryController extends AuthBaseController /** * 마이페이지 문의 목록 조회 * + * @param UserInquiryListRequest $request 목록 조회 요청 (페이지네이션 상·하한 검증) * @return JsonResponse 내 문의 목록 JSON 응답 */ - public function index(): JsonResponse + public function index(UserInquiryListRequest $request): JsonResponse { try { $this->logApiUsage('inquiry.user_list'); - $perPage = (int) (request()->query('per_page', 10)); + $validated = $request->validated(); + $perPage = (int) ($validated['per_page'] ?? 10); $filters = array_filter([ - 'search' => request()->query('search'), - 'is_answered' => request()->query('is_answered'), + 'search' => $validated['search'] ?? null, + 'is_answered' => $validated['is_answered'] ?? null, ], fn ($v) => ! is_null($v)); $result = $this->inquiryService->getUserInquiries(Auth::id(), $filters, $perPage); @@ -74,7 +77,15 @@ class ProductInquiryController extends AuthBaseController return ResponseHelper::moduleSuccess('sirsoft-ecommerce', 'messages.inquiries.updated', ['id' => $inquiryId]); } catch (\RuntimeException $e) { - return ResponseHelper::error($e->getMessage(), 422); + // 서비스가 던지는 RuntimeException 은 생성 시점에 __() 로 번역된 사용자 문구다. + // 사유를 파라미터로 넘겨 실패 원인이 화면에 그대로 남게 한다. + return ResponseHelper::moduleError( + 'sirsoft-ecommerce', + 'messages.inquiries.operation_failed_reason', + 422, + null, + ['reason' => $e->getMessage()] + ); } catch (Exception $e) { return ResponseHelper::moduleError('sirsoft-ecommerce', 'messages.inquiries.update_failed', 500); } @@ -134,7 +145,15 @@ class ProductInquiryController extends AuthBaseController 201 ); } catch (\RuntimeException $e) { - return ResponseHelper::error($e->getMessage(), 422); + // 서비스가 던지는 RuntimeException 은 생성 시점에 __() 로 번역된 사용자 문구다. + // 사유를 파라미터로 넘겨 실패 원인이 화면에 그대로 남게 한다. + return ResponseHelper::moduleError( + 'sirsoft-ecommerce', + 'messages.inquiries.operation_failed_reason', + 422, + null, + ['reason' => $e->getMessage()] + ); } catch (Exception $e) { return ResponseHelper::moduleError('sirsoft-ecommerce', 'messages.inquiries.reply_failed', 500); } @@ -161,7 +180,15 @@ class ProductInquiryController extends AuthBaseController return ResponseHelper::moduleSuccess('sirsoft-ecommerce', 'messages.inquiries.reply_updated', ['id' => $inquiryId]); } catch (\RuntimeException $e) { - return ResponseHelper::error($e->getMessage(), 422); + // 서비스가 던지는 RuntimeException 은 생성 시점에 __() 로 번역된 사용자 문구다. + // 사유를 파라미터로 넘겨 실패 원인이 화면에 그대로 남게 한다. + return ResponseHelper::moduleError( + 'sirsoft-ecommerce', + 'messages.inquiries.operation_failed_reason', + 422, + null, + ['reason' => $e->getMessage()] + ); } catch (Exception $e) { return ResponseHelper::moduleError('sirsoft-ecommerce', 'messages.inquiries.reply_update_failed', 500); } @@ -187,7 +214,15 @@ class ProductInquiryController extends AuthBaseController return ResponseHelper::moduleSuccess('sirsoft-ecommerce', 'messages.inquiries.reply_deleted', ['deleted' => true]); } catch (\RuntimeException $e) { - return ResponseHelper::error($e->getMessage(), 422); + // 서비스가 던지는 RuntimeException 은 생성 시점에 __() 로 번역된 사용자 문구다. + // 사유를 파라미터로 넘겨 실패 원인이 화면에 그대로 남게 한다. + return ResponseHelper::moduleError( + 'sirsoft-ecommerce', + 'messages.inquiries.operation_failed_reason', + 422, + null, + ['reason' => $e->getMessage()] + ); } catch (Exception $e) { return ResponseHelper::moduleError('sirsoft-ecommerce', 'messages.inquiries.reply_delete_failed', 500); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ActiveShippingCarrierRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ActiveShippingCarrierRequest.php new file mode 100644 index 00000000..c2228bec --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/ActiveShippingCarrierRequest.php @@ -0,0 +1,45 @@ +> 검증 규칙 + */ + public function rules(): array + { + return [ + 'type' => ['nullable', 'string', 'max:50'], + ]; + } + + /** + * 배송사 유형 필터를 반환합니다. + * + * @return string|null 유형 (미지정 시 null) + */ + public function type(): ?string + { + $type = $this->validated('type'); + + return $type === null ? null : (string) $type; + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/CouponListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/CouponListRequest.php index 9931b16b..24ab5724 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/CouponListRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/CouponListRequest.php @@ -18,6 +18,10 @@ class CouponListRequest extends FormRequest { /** * 사용자가 이 요청을 수행할 권한이 있는지 확인 + * + * 인증/권한은 permission 미들웨어 체인이 담당하므로 항상 true 를 반환한다. + * + * @return bool 항상 true */ public function authorize(): bool { @@ -34,7 +38,7 @@ class CouponListRequest extends FormRequest return [ 'page' => 'nullable|integer|min:1', 'per_page' => 'nullable|integer|min:1|max:100', - 'sort_by' => 'nullable|string|in:created_at,name,discount_value,issued_count', + 'sort_by' => 'nullable|string|in:created_at,name,discount_value,issued_count,valid_to', 'sort_order' => 'nullable|string|in:asc,desc', 'search_field' => 'nullable|string|in:all,name,description,created_by', 'search_keyword' => 'nullable|string|max:255', diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/IndexProductCommonInfoRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/IndexProductCommonInfoRequest.php new file mode 100644 index 00000000..aabea968 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/IndexProductCommonInfoRequest.php @@ -0,0 +1,107 @@ +has($key)) { + continue; + } + + $value = filter_var($this->input($key), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + + if ($value !== null) { + $normalized[$key] = $value; + } + } + + if ($normalized !== []) { + $this->merge($normalized); + } + } + + /** + * 검증 규칙을 반환합니다. + * + * @return array> 검증 규칙 + */ + public function rules(): array + { + return [ + 'search' => ['nullable', 'string', 'max:255'], + 'active_only' => ['nullable', 'boolean'], + 'default_only' => ['nullable', 'boolean'], + // 정수 또는 `all`. 0 이하는 전체 조회로 해석된다. + 'per_page' => ['nullable', 'regex:/^(all|-?\d+)$/'], + ]; + } + + /** + * 목록 필터를 반환합니다 (미지정 필터는 키 자체가 빠집니다). + * + * @return array 필터 배열 + */ + public function filters(): array + { + $filters = [ + 'search' => $this->validated('search'), + 'is_active' => $this->boolean('active_only') ? true : null, + 'is_default' => $this->has('default_only') && $this->boolean('default_only') ? true : null, + ]; + + return array_filter($filters, fn ($value) => $value !== null); + } + + /** + * 전체 조회 여부를 반환합니다. + * + * @return bool `per_page=all` 이거나 0 이하이면 true + */ + public function wantsAll(): bool + { + $perPage = $this->validated('per_page'); + + return $perPage === 'all' || (int) ($perPage ?? self::DEFAULT_PER_PAGE) <= 0; + } + + /** + * 페이지 크기를 반환합니다. + * + * @return int 페이지 크기 + */ + public function perPage(): int + { + return (int) ($this->validated('per_page') ?? self::DEFAULT_PER_PAGE); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/IndexShippingCarrierRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/IndexShippingCarrierRequest.php new file mode 100644 index 00000000..61d0a2ad --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/IndexShippingCarrierRequest.php @@ -0,0 +1,69 @@ +has('is_active')) { + return; + } + + $value = filter_var($this->input('is_active'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + + if ($value !== null) { + $this->merge(['is_active' => $value]); + } + } + + /** + * 검증 규칙을 반환합니다. + * + * @return array> 검증 규칙 + */ + public function rules(): array + { + return [ + 'is_active' => ['nullable', 'boolean'], + 'type' => ['nullable', 'string', 'max:50'], + 'search' => ['nullable', 'string', 'max:255'], + ]; + } + + /** + * 목록 필터를 반환합니다. + * + * @return array 필터 배열 + */ + public function filters(): array + { + return array_filter( + $this->validated(), + fn ($value) => $value !== null + ); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/OrderListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/OrderListRequest.php index 43719ff2..164eb966 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/OrderListRequest.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Admin/OrderListRequest.php @@ -8,7 +8,7 @@ use Illuminate\Validation\Rule; use Modules\Sirsoft\Ecommerce\Enums\DeviceTypeEnum; use Modules\Sirsoft\Ecommerce\Enums\OrderDateTypeEnum; use Modules\Sirsoft\Ecommerce\Enums\OrderStatusEnum; -use Modules\Sirsoft\Ecommerce\Models\ShippingType; +use Modules\Sirsoft\Ecommerce\Repositories\Contracts\ShippingTypeRepositoryInterface; use Modules\Sirsoft\Ecommerce\Services\PaymentMethodResolver; /** @@ -53,7 +53,11 @@ class OrderListRequest extends FormRequest // 배송유형 (다중선택) 'shipping_type' => ['nullable', 'array'], - 'shipping_type.*' => ['string', Rule::in(ShippingType::pluck('code')->toArray())], + // 비활성 배송유형으로도 과거 주문을 필터링할 수 있어야 하므로 전체 코드를 허용한다 + // (getActiveCodes() 를 쓰면 비활성 유형의 기존 주문이 조회 불가가 된다). + 'shipping_type.*' => ['string', Rule::in( + app(ShippingTypeRepositoryInterface::class)->getAll()->pluck('code')->toArray() + )], // 결제수단 (다중선택) 'payment_method' => ['nullable', 'array'], @@ -92,7 +96,9 @@ class OrderListRequest extends FormRequest 'member_type' => ['nullable', 'in:member,guest'], // 정렬 및 페이지네이션 - 'sort_by' => ['nullable', 'in:ordered_at,paid_at,total_amount'], + // shipped_at 은 주문이 아니라 배송 테이블 컬럼이다 — OrderRepository 가 + // RELATED_SORTABLE_COLUMNS 로 상관 서브쿼리 정렬을 수행한다. + 'sort_by' => ['nullable', 'in:ordered_at,paid_at,total_amount,shipped_at'], 'sort_order' => ['nullable', 'in:asc,desc'], 'per_page' => ['nullable', 'integer', 'min:10', 'max:100'], 'page' => ['nullable', 'integer', 'min:1'], diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Public/DownloadableCouponsRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Public/DownloadableCouponsRequest.php new file mode 100644 index 00000000..e90ad272 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Public/DownloadableCouponsRequest.php @@ -0,0 +1,34 @@ +> 검증 규칙 (사용 파라미터 없음) + */ + public function rules(): array + { + return []; + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Public/ProductInquiryListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Public/ProductInquiryListRequest.php new file mode 100644 index 00000000..289b63d5 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/Public/ProductInquiryListRequest.php @@ -0,0 +1,64 @@ +has('exclude_secret')) { + $normalized = filter_var( + $this->query('exclude_secret'), + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE + ); + + // 해석 불가한 값은 건드리지 않는다 — null 로 바꿔 버리면 오타 파라미터가 + // 조용히 "필터 없음" 으로 통과해 게이트가 무력해진다. + if ($normalized !== null) { + $this->merge(['exclude_secret' => $normalized]); + } + } + } + + /** + * 유효성 검사 규칙 + * + * @return array 검증 규칙 + */ + public function rules(): array + { + return [ + 'page' => ['nullable', 'integer', 'min:1'], + 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], + 'exclude_secret' => ['nullable', 'boolean'], + ]; + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UserInquiryListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UserInquiryListRequest.php new file mode 100644 index 00000000..a101be48 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/UserInquiryListRequest.php @@ -0,0 +1,65 @@ +has('is_answered')) { + $normalized = filter_var( + $this->query('is_answered'), + FILTER_VALIDATE_BOOLEAN, + FILTER_NULL_ON_FAILURE + ); + + // 해석 불가한 값은 건드리지 않는다 — null 로 바꿔 버리면 오타 파라미터가 + // 조용히 "필터 없음" 으로 통과해 게이트가 무력해진다. + if ($normalized !== null) { + $this->merge(['is_answered' => $normalized]); + } + } + } + + /** + * 유효성 검사 규칙 + * + * @return array 검증 규칙 + */ + public function rules(): array + { + return [ + 'page' => ['nullable', 'integer', 'min:1'], + 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], + 'search' => ['nullable', 'string', 'max:100'], + 'is_answered' => ['nullable', 'boolean'], + ]; + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/WishlistListRequest.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/WishlistListRequest.php new file mode 100644 index 00000000..2a758f0d --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Requests/User/WishlistListRequest.php @@ -0,0 +1,37 @@ + ['nullable', 'integer', 'min:1'], + 'per_page' => ['nullable', 'integer', 'min:1', 'max:100'], + ]; + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/CouponIssueResource.php b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/CouponIssueResource.php index d511320d..402ceeaf 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/CouponIssueResource.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Http/Resources/CouponIssueResource.php @@ -4,6 +4,7 @@ namespace Modules\Sirsoft\Ecommerce\Http\Resources; use App\Http\Resources\BaseApiResource; use Illuminate\Http\Request; +use Modules\Sirsoft\Ecommerce\Enums\CouponIssueRecordStatus; use Modules\Sirsoft\Ecommerce\Http\Resources\Traits\HasMultiCurrencyPrices; /** @@ -21,16 +22,23 @@ class CouponIssueResource extends BaseApiResource */ public function toArray($request): array { + // 유효기간이 지난 available 행은 저장소 필터에서 '만료'로 분류되므로, + // 표시 상태도 같은 기준을 따라야 한다. 원시 status 만 쓰면 만료 목록에 + // "사용가능" 라벨이 붙는다. + $displayStatus = $this->isExpired() + ? CouponIssueRecordStatus::EXPIRED + : $this->status; + return [ 'id' => $this->id, 'coupon_id' => $this->coupon_id, 'user_id' => $this->user?->uuid, 'coupon_code' => $this->coupon_code, - // 상태 + // 상태 — status 는 저장된 원시값, *_label/badge 는 유효기간을 반영한 표시값 'status' => $this->status?->value, - 'status_label' => $this->status?->label(), - 'status_badge_color' => $this->status?->badgeColor(), + 'status_label' => $displayStatus?->label(), + 'status_badge_color' => $displayStatus?->badgeColor(), // 날짜 'issued_at' => $this->formatDateTimeStringForUser($this->issued_at), diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php index 25cc4be9..425f3ece 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponIssueRepository.php @@ -133,8 +133,10 @@ class CouponIssueRepository implements CouponIssueRepositoryInterface { $now = Carbon::now(); + // 관계는 relations: 로 넘긴다 — 쿼리에 미리 with() 하면 지연 조인 트레이트가 + // outer 에서도 지워 관계가 로드되지 않는다. $query = $this->model - ->with(['coupon']) + ->newQuery() ->where('user_id', $userId); // 상태별 필터링 @@ -171,6 +173,7 @@ class CouponIssueRepository implements CouponIssueRepositoryInterface columns: ['*'], sort: [['column' => 'created_at', 'direction' => 'desc']], perPage: $perPage, + relations: ['coupon'], ); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php index b0f1d298..ca45dd6b 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/CouponRepository.php @@ -24,7 +24,7 @@ class CouponRepository implements CouponRepositoryInterface use ResolvesSortSpec; /** 허용 정렬 컬럼 (CouponListRequest 와 동일 집합) */ - private const SORTABLE_COLUMNS = ['created_at', 'name', 'discount_value', 'issued_count']; + private const SORTABLE_COLUMNS = ['created_at', 'name', 'discount_value', 'issued_count', 'valid_to']; public function __construct( protected Coupon $model, @@ -261,7 +261,9 @@ class CouponRepository implements CouponRepositoryInterface ->where(fn ($q) => $q->whereNull('total_quantity')->orWhereColumn('issued_count', '<', 'total_quantity')) ->where(fn ($q) => $q->whereNull('valid_to')->orWhere('valid_to', '>=', now())) ->with(['includedProducts', 'excludedProducts', 'includedCategories', 'excludedCategories']) - ->orderBy('created_at', 'desc'); + // 정렬 마지막의 기본키는 전순서 보장용이다 (일괄 등록된 쿠폰의 created_at 동률 대비) + ->orderBy('created_at', 'desc') + ->orderBy('id', 'desc'); if ($perPage !== null) { // audit:allow repository-paginate-column-pruning reason: 다운로드 가능 쿠폰 "정의" 목록 — @@ -314,15 +316,16 @@ class CouponRepository implements CouponRepositoryInterface } // 혜택금액 범위 필터 - if (! empty($filters['min_benefit_amount'])) { + // 0 은 유효한 경계값이다. empty() 로 거르면 0 이 "미입력"이 되어 필터가 무시된다. + if (isset($filters['min_benefit_amount']) && $filters['min_benefit_amount'] !== '') { $query->where('discount_value', '>=', $filters['min_benefit_amount']); } - if (! empty($filters['max_benefit_amount'])) { + if (isset($filters['max_benefit_amount']) && $filters['max_benefit_amount'] !== '') { $query->where('discount_value', '<=', $filters['max_benefit_amount']); } - // 최소주문금액 필터 - if (! empty($filters['min_order_amount'])) { + // 최소주문금액 필터 (0 = 최소주문금액 제한 없는 쿠폰까지 포함하는 유효 경계) + if (isset($filters['min_order_amount']) && $filters['min_order_amount'] !== '') { $query->where('min_order_amount', '>=', $filters['min_order_amount']); } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageTransactionRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageTransactionRepository.php index 8e836d88..76b8a6e9 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageTransactionRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/MileageTransactionRepository.php @@ -185,10 +185,10 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf ->where('exp.type', MileageTransactionTypeEnum::EXPIRED->value) ->whereColumn('exp.source_transaction_id', "{$table}.id"); - $query = MileageTransaction::query() - ->with(['user', 'grantedByUser', 'order']) - ->addSelect('*') - ->addSelect(['expired_amount' => $expiredAmount]); + // 관계·집계·정렬은 쿼리에 붙이지 않는다 — 지연 조인 계약상 여기에는 필터/where 만 둔다. + // 관계는 relations: 인자로, 소멸합 집계는 outerUsing 으로 outer 에서만 붙인다 + // (inner 에 두면 건너뛸 행 전체에 상관 서브쿼리가 돌아 깊은 OFFSET 비용이 그대로 남는다). + $query = MileageTransaction::query(); if (! empty($filters['user_id'])) { $query->where('user_id', $filters['user_id']); @@ -203,12 +203,15 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf $query->where('currency', $filters['currency']); } + // 기간 필터 — whereDate 는 컬럼에 DATE() 를 씌워 인덱스를 못 쓰게 만든다. + // 같은 결과를 내는 범위 조건으로 바꿔 created_at 인덱스를 살린다 + // (종료일은 그날 23:59:59.999999 까지 포함해야 whereDate 와 동일한 경계를 갖는다). if (! empty($filters['start_date'])) { - $query->whereDate('created_at', '>=', $filters['start_date']); + $query->where('created_at', '>=', Carbon::parse($filters['start_date'])->startOfDay()); } if (! empty($filters['end_date'])) { - $query->whereDate('created_at', '<=', $filters['end_date']); + $query->where('created_at', '<=', Carbon::parse($filters['end_date'])->endOfDay()); } // 검색: search_field 별 대상 컬럼/관계 분기 (member/member_id/email/order) @@ -242,12 +245,23 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf } // 적립/사용 이력은 계속 쌓이므로 지연 조인으로 뒤쪽 페이지 비용을 고정한다. - // 정렬은 applySort 가 닫힌 슬러그 집합으로 해석해 이미 쿼리에 적용돼 있다. - $this->applySort($query, $filters['sort'] ?? 'created_at_desc'); - - // audit:allow repository-paginate-column-pruning reason: 정렬이 applySort 로 쿼리에 직접 - // 적용돼 지연 조인 계약(정렬 미적용 쿼리)을 만족하지 않는다. 컬럼 프루닝은 후속 정리 대상. - return $query->paginate($perPage); + // + // columns 를 ['*'] 로 두는 이유: 목록 응답(MileageTransactionResource)이 금액·잔액· + // 만료·메모·연결 주문까지 거의 모든 컬럼을 그대로 노출해 뺄 컬럼이 없다. 이 목록의 + // 이득은 컬럼 프루닝이 아니라 "넓은 컬럼을 읽는 행 수를 이번 페이지로 고정" 하는 쪽이다. + return $this->paginateWithDeferredJoin( + query: $query, + columns: ['*'], + sort: $this->sortSpec($filters['sort'] ?? 'created_at_desc'), + perPage: $perPage, + relations: ['user', 'grantedByUser', 'order'], + // 소멸합 집계는 결과 집합을 좁히지 않으므로 outer 에서만 실행한다. + // `addSelect('*')` 를 함께 부르는 이유: outer 의 select 목록이 한 번 설정되면 + // `get(['*'])` 가 그것을 보존하므로, 명시하지 않으면 본 컬럼이 통째로 빠진다. + outerUsing: fn (Builder $outer) => $outer + ->addSelect('*') + ->addSelect(['expired_amount' => $expiredAmount]), + ); } /** @@ -258,23 +272,31 @@ class MileageTransactionRepository implements MileageTransactionRepositoryInterf */ protected function applySort($query, string $sort): void { - switch ($sort) { - case 'created_at_asc': - $query->orderBy('created_at', 'asc')->orderBy('id', 'asc'); - break; - case 'amount_desc': - $query->orderByDesc('amount')->orderByDesc('id'); - break; - case 'amount_asc': - $query->orderBy('amount', 'asc')->orderBy('id', 'asc'); - break; - case 'created_at_desc': - default: - $query->orderByDesc('created_at')->orderByDesc('id'); - break; + foreach ($this->sortSpec($sort) as $spec) { + $query->orderBy($spec['column'], $spec['direction']); } } + /** + * 정렬 슬러그를 정렬 스펙으로 해석합니다. + * + * 지연 조인은 정렬이 적용되지 않은 쿼리와 정렬 스펙을 따로 받으므로, 쿼리에 직접 + * `orderBy` 를 붙이는 대신 스펙을 돌려준다. 선택지가 닫힌 슬러그 집합이라 요청 값이 + * 그대로 컬럼명으로 새지 않는다. + * + * @param string $sort 정렬 슬러그 + * @return array 정렬 스펙 + */ + protected function sortSpec(string $sort): array + { + return match ($sort) { + 'created_at_asc' => [['column' => 'created_at', 'direction' => 'asc'], ['column' => 'id', 'direction' => 'asc']], + 'amount_desc' => [['column' => 'amount', 'direction' => 'desc'], ['column' => 'id', 'direction' => 'desc']], + 'amount_asc' => [['column' => 'amount', 'direction' => 'asc'], ['column' => 'id', 'direction' => 'asc']], + default => [['column' => 'created_at', 'direction' => 'desc'], ['column' => 'id', 'direction' => 'desc']], + }; + } + /** * {@inheritdoc} */ diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php index 999f6a7e..b5333541 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/OrderRepository.php @@ -7,6 +7,7 @@ use App\Models\ActivityLog; use App\Models\User; use App\Repositories\Concerns\PaginatesWithDeferredJoin; use App\Repositories\Concerns\ResolvesSortSpec; +use App\Repositories\Concerns\SortsByRelatedColumn; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -27,6 +28,7 @@ class OrderRepository implements OrderRepositoryInterface { use PaginatesWithDeferredJoin; use ResolvesSortSpec; + use SortsByRelatedColumn; /** * 주문 목록이 실제로 사용하는 컬럼 @@ -68,6 +70,16 @@ class OrderRepository implements OrderRepositoryInterface * 요청 값을 그대로 orderBy 에 넘기면 없는 컬럼으로 SQL 오류가 나거나 인덱스 없는 넓은 * 컬럼 정렬을 강제할 수 있다. * + * 이 목록은 OrderListRequest 의 `sort_by` `in:` 규칙(ordered_at·paid_at·total_amount) + * 보다 넓다. 그 규칙은 `HookManager::applyFilters` 로 확장에 열려 있어 확장이 정렬 + * 컬럼을 늘릴 수 있고, 그때 이 목록이 더 좁으면 게이트를 통과한 정렬이 조용히 기본 + * 정렬로 되돌아간다. 게이트보다 좁게 두지 않는다 + * (service-repository.md "정렬 컬럼 화이트리스트"). + * + * 다만 created_at·total_shipping_amount·total_paid_amount 는 인덱스가 없어, 확장이 + * 게이트를 넓혀 이 컬럼으로 정렬시키면 지연 조인의 inner 도 전체 스캔이 된다. + * 그 경우 인덱스를 함께 추가해야 한다. + * * @var array */ private const SORTABLE_COLUMNS = [ @@ -82,6 +94,23 @@ class OrderRepository implements OrderRepositoryInterface 'created_at', ]; + /** + * 관계 테이블 컬럼 기준 허용 정렬 (`SortsByRelatedColumn`) + * + * 발송일은 주문이 아니라 배송 테이블에 있고 한 주문에 배송 행이 여러 건일 수 있다. + * 상관 서브쿼리로 정렬하므로 원 행 수가 바뀌지 않아 총 건수·페이지 경계가 유지된다. + * `ecommerce_order_shippings(order_id, shipped_at)` 복합 인덱스가 전제다. + * + * @var array + */ + private const RELATED_SORTABLE_COLUMNS = [ + 'shipped_at' => [ + 'model' => OrderShipping::class, + 'foreign_key' => 'order_id', + 'column' => 'shipped_at', + ], + ]; + public function __construct( protected Order $model ) {} @@ -299,10 +328,12 @@ class OrderRepository implements OrderRepositoryInterface } // 금액 범위 필터 - if (! empty($filters['min_amount'])) { + // 0 은 유효한 경계값이다(예: 결제금액 0원 주문만). empty() 로 거르면 0 이 "미입력"으로 + // 취급돼 필터가 통째로 무시된다 — min_stock/max_stock 과 같은 판정식을 쓴다. + if (isset($filters['min_amount']) && $filters['min_amount'] !== '') { $query->where('total_amount', '>=', (float) $filters['min_amount']); } - if (! empty($filters['max_amount'])) { + if (isset($filters['max_amount']) && $filters['max_amount'] !== '') { $query->where('total_amount', '<=', (float) $filters['max_amount']); } @@ -317,10 +348,11 @@ class OrderRepository implements OrderRepositoryInterface } // 배송비 범위 필터 - if (! empty($filters['min_shipping_amount'])) { + // 0 은 유효한 경계값이다(무료배송 주문만 보기). empty() 로 거르면 무시된다. + if (isset($filters['min_shipping_amount']) && $filters['min_shipping_amount'] !== '') { $query->where('total_shipping_amount', '>=', (float) $filters['min_shipping_amount']); } - if (! empty($filters['max_shipping_amount'])) { + if (isset($filters['max_shipping_amount']) && $filters['max_shipping_amount'] !== '') { $query->where('total_shipping_amount', '<=', (float) $filters['max_shipping_amount']); } @@ -339,8 +371,15 @@ class OrderRepository implements OrderRepositoryInterface $query->whereIn('order_device', $devices); } - // 정렬 — 요청 값은 허용 목록으로만 해석한다 - $sort = $this->resolveSortSpec($filters, self::SORTABLE_COLUMNS, 'ordered_at'); + // 정렬 — 요청 값은 허용 목록으로만 해석한다. + // 발송일(shipped_at)은 배송 테이블에 있어 상관 서브쿼리 정렬로 해석된다. + $sort = $this->resolveSortSpecWithRelated( + $filters, + self::SORTABLE_COLUMNS, + self::RELATED_SORTABLE_COLUMNS, + $this->model, + 'ordered_at', + ); return $this->paginateWithDeferredJoin( query: $query, diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductInquiryRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductInquiryRepository.php index 6eb5605a..7472d009 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductInquiryRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductInquiryRepository.php @@ -36,6 +36,8 @@ class ProductInquiryRepository implements ProductInquiryRepositoryInterface return $this->model->newQuery() ->where('product_id', $productId) ->orderBy('created_at', 'desc') + // 전순서 보장 — created_at 동률의 순서가 호출마다 달라지지 않도록 + ->orderBy('id', 'desc') ->get(); } @@ -47,6 +49,8 @@ class ProductInquiryRepository implements ProductInquiryRepositoryInterface return $this->model->newQuery() ->where('product_id', $productId) ->orderBy('created_at', 'desc') + // 전순서 보장 — 같은 초에 등록된 문의가 페이지 경계에서 뒤섞이지 않도록 + ->orderBy('id', 'desc') // audit:allow repository-paginate-column-pruning reason: 상품 1건에 종속된 문의 목록 — // where(product_id) 로 이미 좁혀져 OFFSET 이 깊어질 수 없고, 목록이 본문을 그대로 쓴다 ->paginate($perPage); @@ -71,7 +75,9 @@ class ProductInquiryRepository implements ProductInquiryRepositoryInterface $query = $this->model->newQuery() ->with(['product']) ->where('user_id', $userId) - ->orderBy('created_at', 'desc'); + // 전순서 보장 — created_at 동률에서 페이지 경계 흔들림 방지 + ->orderBy('created_at', 'desc') + ->orderBy('id', 'desc'); // 답변 여부 필터 if (isset($filters['is_answered']) && $filters['is_answered'] !== '') { diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php index 980d1699..f5570d4e 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php @@ -453,10 +453,11 @@ class ProductRepository implements ProductRepositoryInterface } // 가격 범위 필터 (소수 통화 대응 — float 비교) - if (! empty($filters['min_price'])) { + // 0 은 유효한 경계값이다(무료 상품만 보기). empty() 로 거르면 필터가 무시된다. + if (isset($filters['min_price']) && $filters['min_price'] !== '') { $query->where('selling_price', '>=', (float) $filters['min_price']); } - if (! empty($filters['max_price'])) { + if (isset($filters['max_price']) && $filters['max_price'] !== '') { $query->where('selling_price', '<=', (float) $filters['max_price']); } @@ -574,6 +575,8 @@ class ProductRepository implements ProductRepositoryInterface ->withAvg('visibleReviews as rating_avg', 'rating') ->when($categoryId !== null, fn ($q) => $q->whereHas('categories', fn ($c) => $c->where('ecommerce_categories.id', $categoryId))) ->orderBy($orderBy, $direction) + // 전순서 보장 — 정렬 컬럼(등록일/판매량 등)이 비고유라 페이지 경계가 흔들릴 수 있다 + ->orderBy('id', $direction === 'asc' ? 'asc' : 'desc') // audit:allow repository-paginate-column-pruning reason: 통합검색 — // whereIn(id, $matchedIds) 로 FULLTEXT 매칭 ID 집합에 먼저 한정되므로 스캔 대상이 // 상품 전체가 아니라 매칭 건수로 묶인다(OFFSET 이 훑는 범위도 그 안). 지연 조인의 @@ -691,10 +694,11 @@ class ProductRepository implements ProductRepositoryInterface if (! empty($filters['price_type'])) { $priceField = $filters['price_type']; - if (! empty($filters['min_price'])) { + // 0 은 유효한 경계값 — empty() 판정 금지 (min_stock/max_stock 과 동일 규칙) + if (isset($filters['min_price']) && $filters['min_price'] !== '') { $query->where($priceField, '>=', (float) $filters['min_price']); } - if (! empty($filters['max_price'])) { + if (isset($filters['max_price']) && $filters['max_price'] !== '') { $query->where($priceField, '<=', (float) $filters['max_price']); } } diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php index 26d9fbdf..6526e953 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductReviewRepository.php @@ -37,8 +37,9 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface */ public function getListWithFilters(array $filters, int $perPage = 20): LengthAwarePaginator { - $query = $this->model->newQuery() - ->with(['user', 'product', 'images', 'orderOption.order', 'replyAdmin']); + // 관계는 아래 relations: 인자로만 넘긴다 — 여기서 with() 해도 지연 조인 트레이트가 + // 지우므로 실제로 로드되지 않는다(중복 선언은 계약을 오해하게 만든다). + $query = $this->model->newQuery(); // 검색 키워드 if (! empty($filters['search_keyword'])) { @@ -134,8 +135,8 @@ class ProductReviewRepository implements ProductReviewRepositoryInterface */ public function findByProduct(int $productId, array $filters = [], int $perPage = 10): LengthAwarePaginator { + // 관계는 아래 relations: 인자로만 넘긴다 (위 getListWithFilters 와 같은 이유) $query = $this->model->newQuery() - ->with(['user', 'images']) ->where('product_id', $productId) ->where('status', ReviewStatus::VISIBLE->value); diff --git a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php index 0b0dfe9b..3214ee27 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductWishlistRepository.php @@ -57,8 +57,13 @@ class ProductWishlistRepository implements ProductWishlistRepositoryInterface { return $this->model ->where('user_id', $userId) + // 상품이 소프트 삭제되면 eager load 결과가 null 이 되어 카드가 렌더 실패한다. + // 목록에서 아예 제외해 "빈 셀 0" 을 성립시킨다 (찜 행 자체는 보존). + ->whereHas('product') ->with(['product.brand', 'product.categories', 'product.activeLabelAssignments.label']) ->orderByDesc('created_at') + // 전순서 보장 — 한 번에 여러 건을 담았을 때의 created_at 동률 대비 + ->orderByDesc('id') // audit:allow repository-paginate-column-pruning reason: 사용자 1명에 종속된 찜 목록 — // where(user_id) 로 이미 좁혀지고, 피벗 성격의 테이블이라 넓은 컬럼이 없다 ->paginate($perPage); diff --git a/modules/_bundled/sirsoft-ecommerce/src/Services/UserMileageService.php b/modules/_bundled/sirsoft-ecommerce/src/Services/UserMileageService.php index 64fdd9a3..cfd981f7 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/Services/UserMileageService.php +++ b/modules/_bundled/sirsoft-ecommerce/src/Services/UserMileageService.php @@ -922,6 +922,13 @@ class UserMileageService $firstSource ??= $lot->id; } + // 지급(adminEarn)은 description 을 채우는데 차감·사용은 비워 두면 + // 회원 마일리지 내역의 「내용」 열이 음수 행에서만 공백이 된다. + // 활동 로그와 같은 키를 써서 지급/차감 표기를 대칭으로 맞춘다. + $descriptionKey = $type === MileageTransactionTypeEnum::ADMIN_DEDUCT + ? 'sirsoft-ecommerce::activity_log.description.mileage_admin_deduct' + : 'sirsoft-ecommerce::activity_log.description.mileage_use'; + $tx = $this->ledger->createTransaction(array_merge([ 'user_id' => $userId, 'currency' => $currency, @@ -930,6 +937,7 @@ class UserMileageService 'remaining_amount' => 0, 'balance_after' => $this->ledger->getBalanceByCurrency($userId, $currency), 'source_transaction_id' => $firstSource, + 'description' => __($descriptionKey, ['amount' => $amount]), ], $extra)); $this->cache->recalculateForUser($userId, $currency); diff --git a/modules/_bundled/sirsoft-ecommerce/src/lang/en/messages.php b/modules/_bundled/sirsoft-ecommerce/src/lang/en/messages.php index 3e34d76b..4698a1cf 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/lang/en/messages.php +++ b/modules/_bundled/sirsoft-ecommerce/src/lang/en/messages.php @@ -1213,6 +1213,8 @@ return [ 'reply_deleted' => 'Reply has been deleted.', 'reply_delete_failed' => 'Failed to delete reply.', 'reply_not_found' => 'Reply not found.', + // 서비스가 던진 사유를 그대로 덧붙여, 실패 원인을 서버 기록 없이도 알 수 있게 한다 + 'operation_failed_reason' => ':reason', ], // User addresses diff --git a/modules/_bundled/sirsoft-ecommerce/src/lang/ko/messages.php b/modules/_bundled/sirsoft-ecommerce/src/lang/ko/messages.php index e3596f55..378cd5dd 100644 --- a/modules/_bundled/sirsoft-ecommerce/src/lang/ko/messages.php +++ b/modules/_bundled/sirsoft-ecommerce/src/lang/ko/messages.php @@ -1202,6 +1202,8 @@ return [ 'reply_deleted' => '답변이 삭제되었습니다.', 'reply_delete_failed' => '답변 삭제에 실패했습니다.', 'reply_not_found' => '답변을 찾을 수 없습니다.', + // 서비스가 던진 사유를 그대로 덧붙여, 실패 원인을 서버 기록 없이도 알 수 있게 한다 + 'operation_failed_reason' => ':reason', ], // 사용자 배송지 diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Admin/OrderShippedAtSortTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Admin/OrderShippedAtSortTest.php new file mode 100644 index 00000000..5385a7c3 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Admin/OrderShippedAtSortTest.php @@ -0,0 +1,163 @@ + ['2026-07-05 10:00:00', '2026-07-20 10:00:00'], + 'SORT-B' => ['2026-07-10 10:00:00'], + 'SORT-C' => ['2026-07-01 10:00:00', '2026-07-15 10:00:00', '2026-07-08 10:00:00'], + 'SORT-NONE' => [], + ]; + + /** + * 주문 4건과 배송 6건을 최소 쿼리로 심습니다. + * + * 주문은 팩토리를 쓰되 사용자를 1명으로 고정하고(기본값은 주문마다 User 생성), + * 배송 행은 모델 이벤트가 필요 없으므로 쿼리 빌더로 한 번에 넣는다. + */ + private function seedOrders(): void + { + $user = User::factory()->create(); + $shippingRows = []; + + foreach (self::SHIPMENTS as $orderNumber => $shippedAts) { + $order = Order::factory()->create([ + 'user_id' => $user->id, + 'order_number' => $orderNumber, + 'ordered_at' => '2026-07-01 00:00:00', + ]); + + if ($shippedAts === []) { + continue; + } + + // 배송 행은 주문 옵션을 FK 로 요구한다. 옵션 자체는 정렬 판정과 무관하므로 + // 주문당 1건만 만들고 그 주문의 배송 행이 공유한다. + $option = OrderOption::factory()->create(['order_id' => $order->id]); + + foreach ($shippedAts as $index => $shippedAt) { + $shippingRows[] = [ + 'order_id' => $order->id, + 'order_option_id' => $option->id, + 'shipping_type' => 'delivery', + 'shipping_status' => 'shipped', + 'tracking_number' => $orderNumber.'-'.$index, + 'shipped_at' => $shippedAt, + 'created_at' => now(), + 'updated_at' => now(), + ]; + } + } + + DB::table((new OrderShipping)->getTable())->insert($shippingRows); + } + + /** + * 목록 조회 결과의 주문번호 순서를 반환합니다. + * + * @param string $direction 정렬 방향 (asc|desc) + * @return array{numbers: array, total: int} 주문번호 순서와 총 건수 + */ + private function listSortedByShippedAt(string $direction): array + { + $paginator = app(OrderRepositoryInterface::class) + ->getListWithFilters(['sort_by' => 'shipped_at', 'sort_order' => $direction], 20); + + return [ + 'numbers' => array_map(fn ($row) => $row->order_number, $paginator->items()), + 'total' => $paginator->total(), + ]; + } + + /** + * 발송 이력이 있는 주문만 남긴 순서를 반환합니다. + * + * 발송 이력이 없는 주문의 `NULL` 정렬 위치는 DB 마다 다르므로(선행/후행) 순서 판정에서 + * 제외한다 — 존재 여부는 별도로 단언한다 (데이터베이스 독립성 원칙). + * + * @param array $numbers 주문번호 순서 + * @return array 발송 이력이 있는 주문번호 순서 + */ + private function withShipmentsOnly(array $numbers): array + { + return array_values(array_filter($numbers, fn ($n) => self::SHIPMENTS[$n] !== [])); + } + + /** + * 관계 테이블 컬럼(발송일) 정렬의 세 불변식을 함께 검증한다. + */ + public function test_발송일_정렬은_집계기준_정확하고_총건수와_미발송주문을_보존한다(): void + { + $this->seedOrders(); + + $desc = $this->listSortedByShippedAt('desc'); + $asc = $this->listSortedByShippedAt('asc'); + + // (1) 최근 발송순 — 주문별 가장 늦은 발송일 A(07-20) > C(07-15) > B(07-10) + $this->assertSame( + ['SORT-A', 'SORT-C', 'SORT-B'], + $this->withShipmentsOnly($desc['numbers']), + '주문별 최신 발송일 기준 내림차순이어야 합니다.' + ); + + // (1) 오래된 발송순 — 주문별 가장 이른 발송일 C(07-01) < A(07-05) < B(07-10) + $this->assertSame( + ['SORT-C', 'SORT-A', 'SORT-B'], + $this->withShipmentsOnly($asc['numbers']), + '주문별 최초 발송일 기준 오름차순이어야 합니다.' + ); + + // (2) 배송 행이 6건이어도 주문은 4건 — 총 건수가 늘면 1:N 조인으로 원 행이 부푼 것 + $this->assertSame(4, $desc['total'], '배송 행 수만큼 총 건수가 부풀면 안 됩니다.'); + $this->assertCount(4, $desc['numbers'], '페이지에 실린 행 수도 주문 수와 같아야 합니다.'); + + // (3) 발송 이력이 없는 주문이 빠지면 INNER JOIN 으로 좁혀진 것 + $this->assertContains('SORT-NONE', $desc['numbers'], '미발송 주문이 내림차순 목록에서 사라졌습니다.'); + $this->assertContains('SORT-NONE', $asc['numbers'], '미발송 주문이 오름차순 목록에서 사라졌습니다.'); + } + + /** + * 화면이 제공하는 발송일 정렬 옵션이 게이트에서 422 가 되지 않아야 한다. + */ + public function test_발송일_정렬_요청이_422가_아니다(): void + { + $admin = $this->createAdminUser(['sirsoft-ecommerce.orders.read']); + + $this->actingAs($admin, 'sanctum') + ->getJson('/api/modules/sirsoft-ecommerce/admin/orders?sort_by=shipped_at&sort_order=desc&per_page=20') + ->assertOk(); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/ListPaginationGuardTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/ListPaginationGuardTest.php new file mode 100644 index 00000000..5a2c451a --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/ListPaginationGuardTest.php @@ -0,0 +1,143 @@ +createUser(); + + foreach (['0', '-5', 'abc', '99999'] as $value) { + $this->actingAs($user) + ->getJson('/api/modules/sirsoft-ecommerce/wishlist?per_page='.$value) + ->assertStatus(422); + } + } + + /** + * 찜 목록: 허용 범위 per_page 는 그대로 적용되는지 검증합니다. + * + * @return void + */ + public function test_wishlist_accepts_in_range_per_page(): void + { + $user = $this->createUser(); + $products = Product::factory()->count(3)->create(); + + foreach ($products as $product) { + ProductWishlist::create([ + 'user_id' => $user->id, + 'product_id' => $product->id, + ]); + } + + $response = $this->actingAs($user) + ->getJson('/api/modules/sirsoft-ecommerce/wishlist?per_page=2') + ->assertOk(); + + $this->assertSame(2, $response->json('data.pagination.per_page')); + $this->assertSame(3, $response->json('data.pagination.total')); + } + + /** + * 상품 문의 목록(공개): per_page 경계값이 전부 422 로 거부되는지 검증합니다. + * + * @return void + */ + public function test_product_inquiry_list_rejects_out_of_range_per_page(): void + { + $product = Product::factory()->create(); + + foreach (['0', '-5', 'abc', '99999'] as $value) { + $this->getJson( + '/api/modules/sirsoft-ecommerce/products/'.$product->product_code.'/inquiries?per_page='.$value + )->assertStatus(422); + } + } + + /** + * 마이페이지 내 문의 목록: per_page 경계값이 전부 422 로 거부되는지 검증합니다. + * + * @return void + */ + public function test_user_inquiry_list_rejects_out_of_range_per_page(): void + { + $user = $this->createUser(); + + foreach (['0', '-5', 'abc', '99999'] as $value) { + $this->actingAs($user) + ->getJson('/api/modules/sirsoft-ecommerce/user/inquiries?per_page='.$value) + ->assertStatus(422); + } + } + + /** + * 상품 문의 목록(공개): 쿼리 스트링의 불리언 표기 4종을 모두 받아들이는지 검증합니다. + * + * 쿼리는 언제나 문자열로 도착하므로 `boolean` 규칙만 두면 화면이 보내는 + * `exclude_secret=false` 에서 목록 전체가 422 가 되고 문의 탭이 비어 보인다 (#492 7차 D-38). + * + * @return void + */ + public function test_product_inquiry_list_accepts_boolean_query_spellings(): void + { + $product = Product::factory()->create(); + + foreach (['true', 'false', '1', '0'] as $value) { + $this->getJson( + '/api/modules/sirsoft-ecommerce/products/'.$product->product_code + .'/inquiries?per_page=10&exclude_secret='.$value + )->assertOk(); + } + } + + /** + * 상품 문의 목록(공개): 불리언으로 해석할 수 없는 값은 여전히 거부되는지 검증합니다. + * + * 정규화가 게이트를 통째로 무력화하지 않아야 한다. + * + * @return void + */ + public function test_product_inquiry_list_rejects_non_boolean_exclude_secret(): void + { + $product = Product::factory()->create(); + + $this->getJson( + '/api/modules/sirsoft-ecommerce/products/'.$product->product_code + .'/inquiries?exclude_secret=maybe' + )->assertStatus(422); + } + + /** + * 마이페이지 내 문의 목록: 쿼리 스트링의 불리언 표기 4종을 모두 받아들이는지 검증합니다. + * + * @return void + */ + public function test_user_inquiry_list_accepts_boolean_query_spellings(): void + { + $user = $this->createUser(); + + foreach (['true', 'false', '1', '0'] as $value) { + $this->actingAs($user) + ->getJson('/api/modules/sirsoft-ecommerce/user/inquiries?is_answered='.$value) + ->assertOk(); + } + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserAddressUpdatePresenceValidationTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserAddressUpdatePresenceValidationTest.php index 19b9042e..4d7f30f2 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserAddressUpdatePresenceValidationTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Http/Controllers/User/UserAddressUpdatePresenceValidationTest.php @@ -70,6 +70,35 @@ class UserAddressUpdatePresenceValidationTest extends ModuleTestCase ->assertJsonValidationErrors(['zipcode']); } + /** + * 우편번호만 유효한 값으로 바꾸는 수정은 통과합니다. + * + * 비우기(`''`)는 위 test_update_blanking_zipcode_only_fails 가 막지만, 그 반대 방향인 + * "우편번호만 새 값으로 교체" 는 별개 경로다. 결과 상태 판정이 페이로드에 없는 필드를 + * 기존 레코드로 메우지 못하면 이 정상 수정도 422 가 되어 부분 수정이 깨진다. + */ + public function test_update_zipcode_only_passes(): void + { + $user = User::factory()->create(); + $address = UserAddress::factory()->create([ + 'user_id' => $user->id, + 'zipcode' => '06236', + 'address' => '서울시 강남구 테헤란로 100', + ]); + + $response = $this->actingAs($user) + ->putJson("{$this->baseUrl}/{$address->id}", ['zipcode' => '13529']); + + $response->assertStatus(200); + + $this->assertSame('13529', $address->fresh()->zipcode, '변경한 우편번호가 저장되어야 한다'); + $this->assertSame( + '서울시 강남구 테헤란로 100', + $address->fresh()->address, + '전송하지 않은 주소는 기존 값이 보존되어야 한다' + ); + } + /** * 도로명 주소만 바꾸는 수정은 기존 우편번호를 근거로 통과합니다. */ diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Seeders/ActivityLogDescriptionParamsTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Seeders/ActivityLogDescriptionParamsTest.php new file mode 100644 index 00000000..564fde8e --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Seeders/ActivityLogDescriptionParamsTest.php @@ -0,0 +1,148 @@ +, line: int}> + */ + private function declaredLogShapes(): array + { + $file = dirname(__DIR__, 3).'/database/seeders/ActivityLogSampleSeeder.php'; + + $this->assertFileExists($file, '이커머스 활동 로그 샘플 시더가 있어야 합니다.'); + + $source = file_get_contents($file); + $shapes = []; + + // ['action' => '...', 'key' => 'xxx', ... 'params' => fn (...) => ['a' => ..., 'b' => ...] + preg_match_all("/'key'\s*=>\s*'([a-z0-9_]+)'/i", $source, $keys, PREG_OFFSET_CAPTURE); + + foreach ($keys[1] as $match) { + [$key, $offset] = $match; + + $paramsPos = strpos($source, "'params'", $offset); + + if ($paramsPos === false) { + continue; + } + + // 다음 'key' 선언보다 뒤에 있으면 이 항목의 params 가 아니다 (params 미선언 항목) + $nextKey = strpos($source, "'key'", $offset + 1); + + if ($nextKey !== false && $paramsPos > $nextKey) { + continue; + } + + $open = strpos($source, '[', $paramsPos); + + if ($open === false) { + continue; + } + + $depth = 0; + $end = null; + + for ($p = $open; $p < strlen($source); $p++) { + if ($source[$p] === '[') { + $depth++; + } elseif ($source[$p] === ']') { + $depth--; + + if ($depth === 0) { + $end = $p; + break; + } + } + } + + if ($end === null) { + continue; + } + + preg_match_all("/'([a-z0-9_]+)'\s*=>/i", substr($source, $open, $end - $open), $names); + + $shapes[] = [ + 'key' => $key, + 'params' => $names[1], + 'line' => substr_count(substr($source, 0, $offset), "\n") + 1, + ]; + } + + return $shapes; + } + + /** + * 시더가 만드는 모든 로그의 설명이 치환자를 남기지 않는지 검증합니다. + * + * 렌더 경로는 모델 접근자와 동일하게 필터 훅을 태운다 — 훅이 ID → 이름으로 + * 보강해 주는 키(coupon_id → coupon_name 등)를 결함으로 오판하지 않기 위해서다. + */ + #[Test] + public function seeded_activity_log_descriptions_leave_no_placeholder(): void + { + $shapes = $this->declaredLogShapes(); + + $this->assertNotEmpty($shapes, '시더에서 로그 형태를 도출하지 못했습니다(파서 회귀).'); + + $unresolved = []; + + foreach ($shapes as $shape) { + $langKey = 'sirsoft-ecommerce::activity_log.description.'.$shape['key']; + $template = __($langKey); + + // 시더 배열의 'key' 가 description 키가 아닌 경우(다른 용도)는 건너뛴다 + if ($template === $langKey) { + continue; + } + + if (preg_match('/:[a-zA-Z_]/', $template) !== 1) { + continue; + } + + $params = array_fill_keys($shape['params'], '1'); + + // 훅 체인 전체(applyFilters)를 태우면 다른 모듈의 리스너까지 해석되어 + // 이 모듈 테스트 컨텍스트에서 바인딩이 없는 의존성으로 터진다. + // 검증 대상은 "이커머스 시더 + 이커머스 리졸버" 조합이므로 그 리스너만 직접 호출한다. + $params = app(ActivityLogDescriptionResolver::class) + ->resolveDescriptionParams($params, $langKey, []); + + $rendered = __($langKey, $params); + + if (preg_match('/:([a-zA-Z_][a-zA-Z0-9_]*)/', $rendered, $m) === 1) { + $unresolved[] = sprintf( + '%s (시더 %d행) → "%s" — 치환자 :%s 미충족 (전달: %s)', + $shape['key'], + $shape['line'], + $rendered, + $m[1], + implode(', ', $shape['params']) ?: '없음' + ); + } + } + + $this->assertSame( + [], + $unresolved, + "샘플 활동 로그 설명에 치환자가 그대로 남습니다:\n ".implode("\n ", $unresolved) + ); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Seeders/SeederConsistencyTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Seeders/SeederConsistencyTest.php index 194c7b8d..a055b18a 100644 --- a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Seeders/SeederConsistencyTest.php +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Seeders/SeederConsistencyTest.php @@ -6,8 +6,15 @@ use App\Models\User; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; use Modules\Sirsoft\Ecommerce\Database\Seeders\Sample\EcommerceUserProfileSeeder; +use Modules\Sirsoft\Ecommerce\Database\Seeders\Sample\OrderSeeder; +use Modules\Sirsoft\Ecommerce\Enums\CouponDiscountType; +use Modules\Sirsoft\Ecommerce\Enums\CouponIssueCondition; +use Modules\Sirsoft\Ecommerce\Enums\CouponIssueMethod; +use Modules\Sirsoft\Ecommerce\Enums\CouponTargetType; use Modules\Sirsoft\Ecommerce\Enums\DeliveryMemoPresetEnum; use Modules\Sirsoft\Ecommerce\Models\Cart; +use Modules\Sirsoft\Ecommerce\Models\Coupon; +use Modules\Sirsoft\Ecommerce\Models\CouponIssue; use Modules\Sirsoft\Ecommerce\Models\EcommerceUserProfile; use Modules\Sirsoft\Ecommerce\Models\Order; use Modules\Sirsoft\Ecommerce\Models\OrderAddress; @@ -271,6 +278,53 @@ class SeederConsistencyTest extends ModuleTestCase $this->assertEquals((int) $fresh->unit_price, (int) $mc[$base], 'base 통화 금액은 unit_price 와 동일해야 합니다(오환산 없음).'); } + /** + * OrderSeeder 의 쿠폰 보충 발급이 쿠폰의 발급수(issued_count)까지 함께 올리는지 검증합니다. + * + * 보충 발급은 발급 이력 행을 직접 만들기 때문에, 카운터를 올리지 않으면 + * 쿠폰 목록의 "발급수" 컬럼과 발급 이력 모달의 "총 N건" 이 영구히 어긋난다. + */ + public function test_replenished_coupon_issue_increments_issued_count(): void + { + $user = User::factory()->create(); + $other = User::factory()->create(); + + $coupon = Coupon::create([ + 'name' => ['ko' => '보충 발급 검증 쿠폰', 'en' => 'Replenish Test Coupon'], + 'target_type' => CouponTargetType::ORDER_AMOUNT, + 'discount_type' => CouponDiscountType::FIXED, + 'discount_value' => 1000, + 'issue_method' => CouponIssueMethod::DIRECT, + 'issue_condition' => CouponIssueCondition::MANUAL, + 'issued_count' => 0, + ]); + + $seeder = new OrderSeeder; + $seeder->setCommand($this->mockSeederCommand()); + + // 주문 생성 중 소진된 쿠폰 내역을 주입 (동일 조합 중복은 1건으로 접힌다) + $records = new \ReflectionProperty(OrderSeeder::class, 'usedCouponRecords'); + $records->setAccessible(true); + $records->setValue($seeder, [ + ['user_id' => $user->id, 'coupon_id' => $coupon->id], + ['user_id' => $user->id, 'coupon_id' => $coupon->id], + ['user_id' => $other->id, 'coupon_id' => $coupon->id], + ]); + + $replenish = new \ReflectionMethod(OrderSeeder::class, 'replenishAvailableCoupons'); + $replenish->setAccessible(true); + $replenish->invoke($seeder); + + $issueCount = CouponIssue::where('coupon_id', $coupon->id)->count(); + + $this->assertSame(2, $issueCount, '중복 제거 후 회원 2명분 보충 발급이 생성되어야 합니다.'); + $this->assertSame( + $issueCount, + (int) $coupon->fresh()->issued_count, + '보충 발급 건수만큼 쿠폰 발급수(issued_count)가 올라가야 합니다.' + ); + } + /** * 시더 콘솔 출력을 흡수하는 mock command 를 반환합니다. */ diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Feature/Services/ListDisplayIntegrityTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Services/ListDisplayIntegrityTest.php new file mode 100644 index 00000000..5c728065 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Feature/Services/ListDisplayIntegrityTest.php @@ -0,0 +1,136 @@ +createUser(); + $products = Product::factory()->count(3)->create(); + + foreach ($products as $product) { + ProductWishlist::create([ + 'user_id' => $user->id, + 'product_id' => $product->id, + ]); + } + + $repository = app(ProductWishlistRepositoryInterface::class); + + $this->assertSame(3, $repository->getByUser($user->id, 20)->total()); + + $products->first()->delete(); + + $paginator = $repository->getByUser($user->id, 20); + + $this->assertSame(2, $paginator->total(), '소프트 삭제된 상품의 찜 행이 목록에 남아 있습니다.'); + foreach ($paginator->items() as $item) { + $this->assertNotNull($item->product, '상품이 없는 찜 행이 목록에 포함됐습니다.'); + } + } + + /** + * 유효기간이 지난 쿠폰이 만료 라벨로 표시되는지 검증합니다. + * + * @return void + */ + public function test_expired_coupon_issue_is_labelled_as_expired(): void + { + $user = $this->createUser(); + + $issue = new CouponIssue([ + 'user_id' => $user->id, + 'status' => CouponIssueRecordStatus::AVAILABLE, + 'expired_at' => now()->subDay(), + ]); + + $payload = (new CouponIssueResource($issue))->toArray(request()); + + $this->assertTrue($payload['is_expired']); + $this->assertSame('available', $payload['status'], '저장된 원시 상태는 그대로 노출되어야 합니다.'); + $this->assertSame( + CouponIssueRecordStatus::EXPIRED->label(), + $payload['status_label'], + '유효기간이 지난 쿠폰이 사용가능으로 표시됩니다.' + ); + $this->assertSame(CouponIssueRecordStatus::EXPIRED->badgeColor(), $payload['status_badge_color']); + } + + /** + * 유효기간이 남은 쿠폰은 원래 라벨을 유지하는지 검증합니다. + * + * @return void + */ + public function test_active_coupon_issue_keeps_available_label(): void + { + $user = $this->createUser(); + + $issue = new CouponIssue([ + 'user_id' => $user->id, + 'status' => CouponIssueRecordStatus::AVAILABLE, + 'expired_at' => now()->addDay(), + ]); + + $payload = (new CouponIssueResource($issue))->toArray(request()); + + $this->assertFalse($payload['is_expired']); + $this->assertSame(CouponIssueRecordStatus::AVAILABLE->label(), $payload['status_label']); + } + + /** + * 관리자 차감 거래에 내용(description)이 기록되는지 검증합니다. + * + * @return void + */ + public function test_admin_deduct_records_description(): void + { + $user = $this->createUser(); + $service = app(UserMileageService::class); + + $service->adminEarn($user->id, new MileageAdminEarnDto( + amount: 5000, + currency: 'KRW', + grantedBy: $user->id, + )); + + $deduct = $service->adminDeduct($user->id, new MileageAdminDeductDto( + amount: 1200, + currency: 'KRW', + grantedBy: $user->id, + )); + + $this->assertNotEmpty( + $deduct->description, + '차감 거래의 내용이 비어 있어 회원 마일리지 내역의 「내용」 칸이 공백이 됩니다.' + ); + $this->assertSame( + __('sirsoft-ecommerce::activity_log.description.mileage_admin_deduct', ['amount' => 1200]), + $deduct->description + ); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/ListIndexCoverageTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/ListIndexCoverageTest.php new file mode 100644 index 00000000..c4ea5e66 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Benchmark/ListIndexCoverageTest.php @@ -0,0 +1,27 @@ +assertListIndexCoverageForExtension(new Module); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Layouts/LayoutSortOptionGateParityTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Layouts/LayoutSortOptionGateParityTest.php new file mode 100644 index 00000000..03d56c08 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Layouts/LayoutSortOptionGateParityTest.php @@ -0,0 +1,35 @@ +assertLayoutSortOptionsWithinGate([ + dirname(__DIR__, 3).'/resources/layouts', + ]); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Repositories/NumericRangeFilterZeroBoundaryTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Repositories/NumericRangeFilterZeroBoundaryTest.php new file mode 100644 index 00000000..aed3ae2b --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Repositories/NumericRangeFilterZeroBoundaryTest.php @@ -0,0 +1,79 @@ +}> + */ + public static function rangeFilterProvider(): array + { + $base = dirname(__DIR__, 3).'/src/Repositories/'; + + return [ + '주문 금액·배송비 범위' => [ + $base.'OrderRepository.php', + ['min_amount', 'max_amount', 'min_shipping_amount', 'max_shipping_amount'], + ], + '상품 가격·재고 범위' => [ + $base.'ProductRepository.php', + ['min_price', 'max_price', 'min_stock', 'max_stock'], + ], + '쿠폰 혜택·주문금액 범위' => [ + $base.'CouponRepository.php', + ['min_benefit_amount', 'max_benefit_amount', 'min_order_amount'], + ], + ]; + } + + /** + * @param array $keys 검사할 필터 키 목록 + */ + #[DataProvider('rangeFilterProvider')] + public function test_수치_범위_필터는_empty_판정으로_0을_버리지_않는다(string $file, array $keys): void + { + $this->assertFileExists($file); + $source = file_get_contents($file); + + foreach ($keys as $key) { + $pattern = "/!\s*empty\(\s*\\\$filters\[\s*'".preg_quote($key, '/')."'\s*\]\s*\)/"; + + $this->assertDoesNotMatchRegularExpression( + $pattern, + $source, + sprintf( + "%s 의 '%s' 필터가 empty() 로 판정되고 있습니다. 0 이 유효 경계값이므로 " + ."isset(\$filters['%s']) && \$filters['%s'] !== '' 형태를 사용해야 합니다.", + basename($file), $key, $key, $key + ) + ); + + // 필터 키가 실제로 사용되고는 있는지 (오탈자로 조용히 사라지는 것 방지) + $this->assertStringContainsString( + "'".$key."'", + $source, + sprintf('%s 에서 %s 필터 키를 찾지 못했습니다.', basename($file), $key) + ); + } + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/tests/Unit/Repositories/SortWhitelistGateParityTest.php b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Repositories/SortWhitelistGateParityTest.php new file mode 100644 index 00000000..38b372d2 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/tests/Unit/Repositories/SortWhitelistGateParityTest.php @@ -0,0 +1,142 @@ + + */ + public static function surfaceProvider(): array + { + return [ + '주문 목록' => [ + OrderRepository::class, + OrderListRequest::class, + 'SORTABLE_COLUMNS', + ], + '상품 목록' => [ + ProductRepository::class, + ProductListRequest::class, + 'ADMIN_SORTABLE_COLUMNS', + ], + '추가배송비 템플릿' => [ + ExtraFeeTemplateRepository::class, + ExtraFeeTemplateListRequest::class, + 'SORTABLE_COLUMNS', + ], + '배송정책 목록' => [ + ShippingPolicyRepository::class, + ShippingPolicyListRequest::class, + 'SORTABLE_COLUMNS', + ], + '쿠폰 목록' => [ + CouponRepository::class, + CouponListRequest::class, + 'SORTABLE_COLUMNS', + ], + ]; + } + + #[DataProvider('surfaceProvider')] + public function test_게이트가_허용한_정렬은_저장소도_허용한다( + string $repositoryClass, + string $requestClass, + string $constantName + ): void { + $reflection = new ReflectionClass($repositoryClass); + + $repoColumns = $reflection->getConstant($constantName); + $this->assertIsArray($repoColumns, "{$repositoryClass}::{$constantName} 상수를 찾지 못했습니다."); + + // 관계 테이블 컬럼 기준 정렬(SortsByRelatedColumn)도 저장소가 허용하는 정렬이다. + // 맵의 **키**가 요청 sort_by 값이므로 원 테이블 컬럼 목록과 합쳐서 대조한다. + $repoColumns = array_values($repoColumns); + $related = $reflection->getConstant('RELATED_SORTABLE_COLUMNS'); + + if (is_array($related)) { + $repoColumns = array_merge($repoColumns, array_keys($related)); + } + + $gateColumns = $this->readGateSortColumns($requestClass); + $this->assertNotEmpty($gateColumns, "{$requestClass} 에서 sort_by 허용 목록을 찾지 못했습니다."); + + $missing = array_values(array_diff($gateColumns, $repoColumns)); + + $this->assertSame( + [], + $missing, + sprintf( + '게이트가 통과시키는 정렬 컬럼을 저장소가 허용하지 않습니다 — 그 정렬은 조용히 ' + ."기본 정렬로 되돌아갑니다.\n 누락: %s\n 저장소: %s\n 게이트: %s", + implode(',', $missing), + implode(',', array_values($repoColumns)), + implode(',', $gateColumns) + ) + ); + } + + /** + * FormRequest 소스에서 sort_by 허용 목록을 읽습니다. + * + * @return array 정렬 허용 컬럼 목록 + */ + private function readGateSortColumns(string $class): array + { + $source = file_get_contents((new ReflectionClass($class))->getFileName()); + + if (preg_match("/'sort_by'\s*=>\s*'[^']*\bin:([^'|]+)/", $source, $m)) { + return $this->splitColumns($m[1]); + } + + if (preg_match("/'sort_by'\s*=>\s*\[[^\]]*'in:([^']+)'/", $source, $m)) { + return $this->splitColumns($m[1]); + } + + if (preg_match("/'sort_by'\s*=>\s*\[.*?Rule::in\(\s*\[(.*?)\]/s", $source, $m)) { + return $this->splitColumns(str_replace("'", '', $m[1])); + } + + return []; + } + + /** + * 쉼표 구분 컬럼 문자열을 배열로 변환합니다. + * + * @return array 정리된 컬럼 목록 + */ + private function splitColumns(string $raw): array + { + return array_values(array_filter(array_map('trim', explode(',', $raw)))); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/upgrades/Upgrade_1_0_5.php b/modules/_bundled/sirsoft-ecommerce/upgrades/Upgrade_1_0_5.php index 8445954f..8a0685a7 100644 --- a/modules/_bundled/sirsoft-ecommerce/upgrades/Upgrade_1_0_5.php +++ b/modules/_bundled/sirsoft-ecommerce/upgrades/Upgrade_1_0_5.php @@ -14,6 +14,11 @@ use App\Extension\AbstractUpgradeStep; * 쓰기 지점이 부분취소 재계산 1곳뿐이라 부분취소를 겪은 주문만 값이 정상화되고 * 나머지는 영구 0 이었다. 0 인 주문만 채우고 이미 정상화된 값은 건드리지 않는다. * + * 함께 목록 조회 성능용 인덱스를 기존 사이트에 반영한다 — 상품 리뷰 정렬 인덱스(02)와 + * 주문 목록 "발송일" 정렬용 복합 인덱스(04). 마이그레이션만 추가하면 신규 설치에만 + * 반영되므로 업그레이드 시점에 동일 인덱스를 적용한다. 데이터가 많은 쇼핑몰에서는 + * ALTER TABLE 이 수 분 걸리며 그동안 해당 테이블 쓰기가 대기한다. + * * 모든 비즈니스 로직은 data/1.0.5/migrations/ 로 격리(AbstractUpgradeStep 규약). * * @upgrade-path A diff --git a/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/03_BackfillMileageConsumptionDescriptions.php b/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/03_BackfillMileageConsumptionDescriptions.php new file mode 100644 index 00000000..dad9f744 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/03_BackfillMileageConsumptionDescriptions.php @@ -0,0 +1,79 @@ + 거래 유형 → 다국어 키 */ + private const DESCRIPTION_KEYS = [ + 'admin_deduct' => 'sirsoft-ecommerce::activity_log.description.mileage_admin_deduct', + 'order_use' => 'sirsoft-ecommerce::activity_log.description.mileage_use', + ]; + + /** + * 마이그레이션 식별자를 반환합니다. + * + * @return string 마이그레이션 이름 + */ + public function name(): string + { + return 'BackfillMileageConsumptionDescriptions'; + } + + /** + * 비어 있는 사용·차감 거래 내용을 채웁니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + * @return void + */ + public function run(UpgradeContext $context): void + { + if (! Schema::hasTable(self::TABLE)) { + $context->logger->info('[ecommerce:1.0.5] 마일리지 거래 테이블 부재 — 내용 백필 스킵'); + + return; + } + + $updated = 0; + + foreach (self::DESCRIPTION_KEYS as $type => $key) { + DB::table(self::TABLE) + ->where('type', $type) + ->where(function ($query) { + $query->whereNull('description')->orWhere('description', ''); + }) + ->orderBy('id') + ->chunkById(500, function ($rows) use ($key, &$updated) { + foreach ($rows as $row) { + DB::table(self::TABLE) + ->where('id', $row->id) + ->update([ + 'description' => __($key, ['amount' => abs((float) $row->amount)]), + ]); + $updated++; + } + }); + } + + $context->logger->info("[ecommerce:1.0.5] 마일리지 사용·차감 내용 백필 완료: {$updated}건"); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/04_AddOrderShippingSortIndex.php b/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/04_AddOrderShippingSortIndex.php new file mode 100644 index 00000000..2effae8a --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/04_AddOrderShippingSortIndex.php @@ -0,0 +1,73 @@ + */ + private const INDEX_COLUMNS = ['order_id', 'shipped_at']; + + /** + * 마이그레이션 식별자를 반환합니다. + * + * @return string 마이그레이션 이름 + */ + public function name(): string + { + return 'AddOrderShippingSortIndex'; + } + + /** + * 발송일 정렬 인덱스를 추가합니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + */ + public function run(UpgradeContext $context): void + { + if (! Schema::hasTable(self::TABLE)) { + $context->logger->info('[ecommerce:1.0.5] 주문 배송 테이블 부재 — 발송일 정렬 인덱스 추가 스킵'); + + return; + } + + $existing = array_column(Schema::getIndexes(self::TABLE), 'name'); + + if (in_array(self::INDEX_NAME, $existing, true)) { + $context->logger->info('[ecommerce:1.0.5] 이미 존재하는 인덱스 — 스킵: '.self::INDEX_NAME); + + return; + } + + $context->logger->info('[ecommerce:1.0.5] 발송일 정렬 인덱스 추가 시작: '.self::INDEX_NAME.' (주문이 많으면 수 분 걸릴 수 있고 그동안 배송 정보 쓰기가 대기합니다)'); + + Schema::table(self::TABLE, function ($table) { + $table->index(self::INDEX_COLUMNS, self::INDEX_NAME); + }); + + $context->logger->info('[ecommerce:1.0.5] 발송일 정렬 인덱스 추가 완료: '.self::INDEX_NAME); + } +} diff --git a/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/05_AddListSortIndexes.php b/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/05_AddListSortIndexes.php new file mode 100644 index 00000000..8857fdf2 --- /dev/null +++ b/modules/_bundled/sirsoft-ecommerce/upgrades/data/1.0.5/migrations/05_AddListSortIndexes.php @@ -0,0 +1,89 @@ + [신규 색인명, 컬럼, 교체 대상 기존 색인명(없으면 null)]] + * + * @var array, 2: string|null}> + */ + private const TARGETS = [ + 'ecommerce_orders' => ['idx_orders_deleted_ordered_id', ['deleted_at', 'ordered_at', 'id'], null], + 'ecommerce_products' => ['idx_products_deleted_created_id', ['deleted_at', 'created_at', 'id'], null], + 'ecommerce_product_inquiries' => ['idx_inquiries_created_id', ['created_at', 'id'], null], + 'ecommerce_product_reviews' => ['idx_reviews_deleted_created_id', ['deleted_at', 'created_at', 'id'], 'idx_reviews_deleted_created'], + 'ecommerce_promotion_coupon_issues' => ['idx_coupon_issues_issued_id', ['issued_at', 'id'], 'idx_issued_at'], + ]; + + /** + * 마이그레이션 식별자를 반환합니다. + * + * @return string 마이그레이션 이름 + */ + public function name(): string + { + return 'AddListSortIndexes'; + } + + /** + * 목록 정렬 색인을 적용합니다. + * + * @param UpgradeContext $context 업그레이드 컨텍스트 + */ + public function run(UpgradeContext $context): void + { + foreach (self::TARGETS as $table => [$newIndex, $columns, $oldIndex]) { + if (! Schema::hasTable($table)) { + $context->logger->info("[ecommerce:1.0.5] 테이블 부재 — 색인 적용 스킵: {$table}"); + + continue; + } + + $existing = array_column(Schema::getIndexes($table), 'name'); + + if (in_array($newIndex, $existing, true)) { + $context->logger->info("[ecommerce:1.0.5] 이미 적용된 색인 — 스킵: {$newIndex}"); + } else { + $context->logger->info("[ecommerce:1.0.5] 목록 정렬 색인 적용 시작: {$table} (데이터가 많으면 수 분 걸릴 수 있고 그동안 해당 테이블 쓰기가 대기합니다)"); + + Schema::table($table, function ($blueprint) use ($columns, $newIndex) { + $blueprint->index($columns, $newIndex); + }); + + $context->logger->info("[ecommerce:1.0.5] 색인 적용 완료: {$newIndex}"); + } + + // 기존 색인은 새 색인의 좌측 프리픽스라 남겨 두면 쓰기 비용만 늘어난다. + // 반드시 새 색인 생성 이후에 지운다. + if ($oldIndex !== null && in_array($oldIndex, $existing, true)) { + Schema::table($table, function ($blueprint) use ($oldIndex) { + $blueprint->dropIndex($oldIndex); + }); + + $context->logger->info("[ecommerce:1.0.5] 상위집합에 포함된 기존 색인 제거: {$oldIndex}"); + } + } + } +} diff --git a/modules/_bundled/sirsoft-page/CHANGELOG.md b/modules/_bundled/sirsoft-page/CHANGELOG.md index f8ef96fa..a342c85a 100644 --- a/modules/_bundled/sirsoft-page/CHANGELOG.md +++ b/modules/_bundled/sirsoft-page/CHANGELOG.md @@ -30,6 +30,9 @@ - 다른 페이지의 주소로 특정 페이지의 이전 버전 내용을 열람할 수 있던 문제를 수정했습니다. - 다른 페이지에 속한 버전으로 복원을 시도하면 "복원에 실패했습니다" 라는 서버 오류가 나던 문제를 수정했습니다. 이제 열람과 동일하게 "찾을 수 없음" 으로 안내합니다. - 관리자 목록에서 검색·필터·정렬을 바꿀 때 표가 다시 그려지는 동안 로딩 표시가 나오지 않던 문제를 수정했습니다. 응답이 늦으면 아무 반응이 없는 것처럼 보여 같은 조작을 반복하게 되던 상황이 사라집니다. +- 통합검색에서 페이지 제목으로 찾을 수 없던 문제를 수정했습니다. 한국어·영어 어느 쪽 제목으로도, 제목의 일부만 입력해도 검색됩니다. +- 통합검색의 페이지 결과에서 같은 시각에 등록된 페이지가 여러 건일 때 목록 순서가 흔들리던 문제를 수정했습니다. +- 페이지 관리 목록에서 뒤쪽 페이지를 보던 중 정렬을 바꾸면 이전 페이지 번호에 그대로 머물러 엉뚱한 구간이 보이던 문제를 수정했습니다. 이제 정렬을 바꾸면 첫 페이지부터 보여줍니다. ## [1.0.1] - 2026-07-08 diff --git a/modules/_bundled/sirsoft-page/module.php b/modules/_bundled/sirsoft-page/module.php index db9a2968..cf10e164 100644 --- a/modules/_bundled/sirsoft-page/module.php +++ b/modules/_bundled/sirsoft-page/module.php @@ -189,6 +189,9 @@ class Module extends AbstractModule 'order' => [['created_at', 'desc'], ['id', 'desc']], // 페이지는 소프트 삭제 컬럼이 없다 — 실제 스키마 기준 선언 'soft_delete' => false, + 'index_exempt' => '페이지는 사이트 구조를 구성하는 정의성 데이터라 운영자가 손으로 만드는 수만큼만 늘어난다(수십~수백). ' + .'깊은 OFFSET 이 성립하지 않아 정렬 색인의 이득보다 쓰기 비용이 크다. ' + .'행 수가 수천을 넘기 시작하면 (created_at, id) 색인을 추가할 것.', ], 'pages_screen' => [ 'type' => 'screen', diff --git a/modules/_bundled/sirsoft-page/resources/lang/en/activity_log.php b/modules/_bundled/sirsoft-page/resources/lang/en/activity_log.php deleted file mode 100644 index 3fd3e1d5..00000000 --- a/modules/_bundled/sirsoft-page/resources/lang/en/activity_log.php +++ /dev/null @@ -1,20 +0,0 @@ - [ - // Page management - 'page_index' => 'Page list viewed', - 'page_show' => 'Page details viewed (:title)', - 'page_create' => 'Page created (:title)', - 'page_update' => 'Page updated (:title)', - 'page_delete' => 'Page deleted (:title)', - 'page_publish' => 'Page published (:title)', - 'page_unpublish' => 'Page unpublished (:title)', - 'page_restore' => 'Page restored (:title)', - - // Page attachments - 'page_attachment_upload' => 'Page attachment uploaded (Page: :title)', - 'page_attachment_delete' => 'Page attachment deleted (Page: :title)', - 'page_attachment_reorder' => 'Page attachment order changed (Page: :title)', - ], -]; diff --git a/modules/_bundled/sirsoft-page/resources/lang/ko/activity_log.php b/modules/_bundled/sirsoft-page/resources/lang/ko/activity_log.php deleted file mode 100644 index 681dd448..00000000 --- a/modules/_bundled/sirsoft-page/resources/lang/ko/activity_log.php +++ /dev/null @@ -1,20 +0,0 @@ - [ - // 페이지 관리 - 'page_index' => '페이지 목록 조회', - 'page_show' => '페이지 상세 조회 (:title)', - 'page_create' => '페이지 생성 (:title)', - 'page_update' => '페이지 수정 (:title)', - 'page_delete' => '페이지 삭제 (:title)', - 'page_publish' => '페이지 발행 (:title)', - 'page_unpublish' => '페이지 발행 취소 (:title)', - 'page_restore' => '페이지 복원 (:title)', - - // 페이지 첨부파일 - 'page_attachment_upload' => '페이지 첨부파일 업로드 (페이지: :title)', - 'page_attachment_delete' => '페이지 첨부파일 삭제 (페이지: :title)', - 'page_attachment_reorder' => '페이지 첨부파일 순서 변경 (페이지: :title)', - ], -]; diff --git a/modules/_bundled/sirsoft-page/resources/layouts/admin/admin_page_list.json b/modules/_bundled/sirsoft-page/resources/layouts/admin/admin_page_list.json index 1f71bb3c..697e4f81 100644 --- a/modules/_bundled/sirsoft-page/resources/layouts/admin/admin_page_list.json +++ b/modules/_bundled/sirsoft-page/resources/layouts/admin/admin_page_list.json @@ -811,10 +811,13 @@ "params": { "path": "/admin/pages", "mergeQuery": true, + "replace": true, "query": { "sort_by": "{{$args[0]}}", - "sort_order": "{{$args[1]}}" - } + "sort_order": "{{$args[1]}}", + "page": 1 + }, + "transition_overlay_target": "pages_data_grid__body" } }, { diff --git a/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php b/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php index 053da489..399d72c5 100644 --- a/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php +++ b/modules/_bundled/sirsoft-page/src/Repositories/PageRepository.php @@ -184,6 +184,8 @@ class PageRepository implements PageRepositoryInterface { $results = $this->buildKeywordQuery($keyword) ->orderBy($orderBy, $direction) + // 전순서 보장 — 정렬 컬럼이 비고유라 페이지 경계가 흔들릴 수 있다 + ->orderBy('id', $direction === 'asc' ? 'asc' : 'desc') // audit:allow repository-paginate-column-pruning reason: 통합검색 전용 진입점 — // 유일한 호출자(SearchPagesListener)가 전체 탭은 limit=5, 페이지 탭은 limit=PHP_INT_MAX 로 // 1페이지에 결과를 받아가므로 OFFSET 이 발생하지 않는다 @@ -227,7 +229,24 @@ class PageRepository implements PageRepositoryInterface return Page::query() ->published() ->where(function ($q) use ($keyword) { - DatabaseFulltextEngine::whereFulltext($q, 'title', $keyword, 'and'); + // 제목은 다국어 JSON 컬럼이라 FULLTEXT 에 의존하지 않고 로케일별 JSON 경로로 찾는다 + // (메뉴 목록과 동일한 관례). + // + // 실측 배경 (#492 D-25): 운영 DB 에서 `MATCH(title)` 이 **어떤 키워드로도** 0 을 + // 반환했다 — `이용약관`(제목 전체) · `약관`(부분) · `Terms` · `Service` 전부 0 인데 + // 같은 행을 `LIKE` 로는 찾는다. 인덱스는 `WITH PARSER ngram` 으로 존재하고 + // `ngram_token_size=2` 인데도 그렇다. 즉 이 컬럼의 FT 인덱스가 실제 행을 담고 + // 있지 않다. 다국어 제목은 짧은 낱말이라 부분 일치가 기대 동작이므로, + // FT 인덱스 상태에 의존하지 않는 로케일 경로 LIKE 로 검색 계약을 고정한다. + // (본문은 길이가 있어 FULLTEXT 가 유효하므로 그대로 둔다.) + $first = true; + foreach ($this->translatableLocales() as $locale) { + $method = $first ? 'where' : 'orWhere'; + $q->{$method}('title->'.$locale, 'like', '%'.$keyword.'%'); + $first = false; + } + + // 본문은 순수 텍스트/HTML 이라 FULLTEXT 가 유효하다. DatabaseFulltextEngine::whereFulltext($q, 'content', $keyword, 'or'); $q->orWhere('slug', 'like', '%'.$keyword.'%'); }); @@ -331,4 +350,19 @@ class PageRepository implements PageRepositoryInterface return $filters; } + + /** + * 다국어 JSON 필드에 저장될 수 있는 로케일 목록을 반환합니다. + * + * 번역 파일이 없어도 데이터는 저장될 수 있으므로 UI 표시 언어(`supported_locales`)보다 넓은 + * `translatable_locales` 를 기준으로 한다 (MenuRepository 와 동일 관례). + * + * @return list 로케일 목록 + */ + private function translatableLocales(): array + { + $locales = config('app.translatable_locales', config('app.supported_locales', [])); + + return empty($locales) ? [app()->getLocale()] : array_values($locales); + } } diff --git a/modules/_bundled/sirsoft-page/tests/Feature/Repositories/PageRepositorySearchTest.php b/modules/_bundled/sirsoft-page/tests/Feature/Repositories/PageRepositorySearchTest.php index 6ca03668..dbfd37a5 100644 --- a/modules/_bundled/sirsoft-page/tests/Feature/Repositories/PageRepositorySearchTest.php +++ b/modules/_bundled/sirsoft-page/tests/Feature/Repositories/PageRepositorySearchTest.php @@ -4,8 +4,8 @@ namespace Modules\Sirsoft\Page\Tests\Feature\Repositories; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; -use Illuminate\Support\Facades\Route; use Modules\Sirsoft\Page\Models\Page; +use Modules\Sirsoft\Page\Providers\PageServiceProvider; use Modules\Sirsoft\Page\Repositories\PageRepository; use Tests\TestCase; @@ -50,7 +50,7 @@ class PageRepositorySearchTest extends TestCase $this->registerModuleAutoload(); // 모듈 ServiceProvider 등록 (Repository 바인딩) - $this->app->register(\Modules\Sirsoft\Page\Providers\PageServiceProvider::class); + $this->app->register(PageServiceProvider::class); // 모듈 마이그레이션 실행 (pages 테이블) $this->artisan('migrate', [ @@ -124,6 +124,46 @@ class PageRepositorySearchTest extends TestCase $this->assertEquals('test-terms-search', $result['items']->first()->slug); } + /** + * 제목만으로 매칭되는지 확인 (본문·슬러그에는 키워드가 없다). + * + * 회귀 배경 (#492 D-25): 운영 DB 에서 제목 검색이 어떤 키워드로도 0 건이었다 + * (`이용약관` / `약관` / `Terms` / `Service` 전부 0, 같은 행을 `LIKE` 로는 찾음). + * 기존 제목 테스트는 초록이었는데, 본문에 같은 낱말이 들어 있어 **content 매칭으로** + * 통과하고 있었기 때문이다. 이 케이스는 본문·슬러그에서 키워드를 완전히 제거해 + * 제목 경로만 남긴다. + * + * 주의: 이 테스트는 fresh 테스트 DB 에서는 수정 전에도 통과한다(그 DB 의 FT 인덱스는 + * 정상 동작한다). 운영 DB 의 FT 인덱스 상태 차이를 재현하지는 못하며, 여기서 잠그는 것은 + * **제목 검색이 본문 매칭에 얹혀 통과하지 않는다**는 계약이다. + */ + public function test_search_by_keyword_matches_title_only(): void + { + $this->createPublishedPage('이용약관', 'aaa-bbb-ccc', '본문에는 검색어가 없습니다.'); + + $koFull = $this->repository->searchByKeyword('이용약관'); + $koPartial = $this->repository->searchByKeyword('약관'); + + $this->assertEquals(1, $koFull['total'], '제목 전체 일치가 검색되지 않았다'); + $this->assertEquals('aaa-bbb-ccc', $koFull['items']->first()->slug); + $this->assertEquals(1, $koPartial['total'], '제목 부분 일치가 검색되지 않았다'); + } + + /** + * 다른 로케일(en) 제목으로도 검색되는지 확인. + */ + public function test_search_by_keyword_matches_other_locale_title(): void + { + $page = $this->createPublishedPage('이용약관', 'ddd-eee-fff', '본문에는 검색어가 없습니다.'); + $page->title = ['ko' => '이용약관', 'en' => 'Terms of Service']; + $page->save(); + + $result = $this->repository->searchByKeyword('Terms'); + + $this->assertEquals(1, $result['total'], '영문 제목으로 검색되지 않았다'); + $this->assertEquals('ddd-eee-fff', $result['items']->first()->slug); + } + /** * 본문에 키워드가 포함된 페이지가 검색되는지 확인 */ diff --git a/modules/_bundled/sirsoft-page/tests/Unit/Benchmark/ListIndexCoverageTest.php b/modules/_bundled/sirsoft-page/tests/Unit/Benchmark/ListIndexCoverageTest.php new file mode 100644 index 00000000..651130cd --- /dev/null +++ b/modules/_bundled/sirsoft-page/tests/Unit/Benchmark/ListIndexCoverageTest.php @@ -0,0 +1,27 @@ +assertListIndexCoverageForExtension(new Module); + } +} diff --git a/plugins/_bundled/sirsoft-ckeditor5/docs/api/upload.md b/plugins/_bundled/sirsoft-ckeditor5/docs/api/upload.md index 5381be58..d924361d 100644 --- a/plugins/_bundled/sirsoft-ckeditor5/docs/api/upload.md +++ b/plugins/_bundled/sirsoft-ckeditor5/docs/api/upload.md @@ -64,6 +64,7 @@ Content-Type: application/octet-stream CKEditor5 의 SimpleUploadAdapter 가 에디터에 드롭/붙여넣은 이미지를 업로드하는 관리자 엔드포인트다. 컨트롤러가 `AdminBaseController` 를 상속하므로 실제 인증은 `auth:sanctum` **에 더해 관리자(admin) 미들웨어**가 적용된다(생성기 표기는 `auth:sanctum` 만 노출). - **응답 형식이 표준 envelope 가 아니다.** SimpleUploadAdapter 규격상 성공 시 HTTP 201 + 최상위 `{"url": "..."}`, 실패 시 4xx/5xx + `{"error": {"message": "..."}}` 를 반환한다. `ResponseHelper` 를 쓰지 않으므로 `data`/`success` 필드가 없다. + 이 규격은 우리 코드가 정한 것이 아니다 — 응답을 파싱하는 주체가 CDN 으로 로드되는 CKEditor5 43.3.1 의 `SimpleUploadAdapter`(`resources/js/handlers/initEditor.ts` 의 `editorConfig.simpleUpload`)이므로 파싱 규약을 바꿀 수 없다. 컨트롤러의 각 응답 지점에는 이 사유로 `audit:allow response-helper-bypass` 를 명시해 두었으니, envelope 로 감싸는 "수정" 을 하면 에디터의 이미지 업로드가 조용히 실패한다. - **요청 파라미터**: multipart body 의 `upload` 필드(이미지 파일 1개). 허용 MIME 은 `jpeg,jpg,png,gif,webp`, 최대 크기는 플러그인 설정 `imageMaxSizeMb`(기본 2MB) 로 동적 결정된다. 검증 실패도 CKEditor 규격(`{"error":{"message":...}}`, HTTP 422)으로 응답한다. - **선택 권한 게이트**: query 파라미터 `permission` 이 주어지면, 현재 사용자가 해당 권한을 갖지 못한 경우 403 `{"error":{"message":...}}`. 에디터를 임베드하는 화면이 업로드 권한을 세분화할 때 사용한다. - 업로드 성공 시 반환하는 `url` 은 공개 서빙 엔드포인트(`GET /images/{hash}`)의 절대 URL 이다. diff --git a/plugins/_bundled/sirsoft-ckeditor5/src/Http/Controllers/ImageUploadController.php b/plugins/_bundled/sirsoft-ckeditor5/src/Http/Controllers/ImageUploadController.php index 54225af4..cf7ad713 100644 --- a/plugins/_bundled/sirsoft-ckeditor5/src/Http/Controllers/ImageUploadController.php +++ b/plugins/_bundled/sirsoft-ckeditor5/src/Http/Controllers/ImageUploadController.php @@ -14,8 +14,10 @@ use Plugins\Sirsoft\Ckeditor5\Services\ImageUploadService; * CKEditor5 SimpleUploadAdapter가 요구하는 형식으로 이미지를 저장하고 * 공개 URL을 반환합니다. * - * 응답 형식: { "url": "https://..." } - * ResponseHelper 사용 불가 — CKEditor SimpleUploadAdapter는 최상위 { url } 키를 요구합니다. + * 응답 형식: { "url": "https://..." } / 실패 시 { "error": { "message": "..." } } + * ResponseHelper 사용 불가 — 응답을 파싱하는 SimpleUploadAdapter 가 CDN 으로 로드되는 + * CKEditor5 43.3.1 상위 코드라 봉투 구조를 해석하지 못합니다 (파싱 규약 변경 불가). + * 이 사유로 각 응답 지점에 audit:allow response-helper-bypass 를 명시했습니다. */ class ImageUploadController extends AdminBaseController { @@ -41,6 +43,7 @@ class ImageUploadController extends AdminBaseController $user = $this->getCurrentUser(); if ($uploadPermission && $user && ! $user->hasPermission($uploadPermission)) { + // audit:allow response-helper-bypass reason: CDN 상위 SimpleUploadAdapter 가 최상위 error.message 를 읽는다 (봉투 불가) return response()->json([ 'error' => ['message' => __('sirsoft-ckeditor5::messages.upload.forbidden')], ], 403); @@ -52,13 +55,14 @@ class ImageUploadController extends AdminBaseController $user?->id ); - // CKEditor SimpleUploadAdapter가 요구하는 최상위 { url } 형식 + // audit:allow response-helper-bypass reason: CDN 상위 SimpleUploadAdapter 가 최상위 url 을 읽는다 (봉투 불가) return response()->json(['url' => $image->download_url], 201); } catch (\Exception $e) { Log::error('[sirsoft-ckeditor5] 이미지 업로드 실패', [ 'error' => $e->getMessage(), ]); + // audit:allow response-helper-bypass reason: CDN 상위 SimpleUploadAdapter 가 최상위 error.message 를 읽는다 (봉투 불가) return response()->json([ 'error' => ['message' => __('sirsoft-ckeditor5::messages.upload.failed')], ], 500); diff --git a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md index 2d159329..ddfde8af 100644 --- a/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-gdpr/CHANGELOG.md @@ -13,6 +13,12 @@ - 거부 후 다시 동의하면 상태가 동의로 갱신되며, 거부·동의 이력은 모두 보존됩니다. - 마이페이지 「쿠키 동의 관리」에 「모두 동의」 버튼 추가 — 동의하지 않은 선택 항목을 한 번에 동의할 수 있습니다. - 자동 차단 정책 안내에 보호 범위 한계를 명시했습니다 — 이 자동 차단은 플러그인·모듈이 등록한 스크립트만 대상이며, 운영자가 템플릿 외부 스크립트 등으로 head 영역에 직접 삽입한 외부 스크립트는 보호 범위 밖임을 안내합니다. +### Fixed + +- 정책 버전 발행 이력이 20건까지만 보이고 그보다 오래된 이력은 열어볼 방법이 없던 문제를 수정했습니다. 이력 아래에 이전·다음 이동과 총 건수 표시를 추가해 전체 이력을 확인할 수 있습니다. +- 관리자 「GDPR 동의 이력」의 출처 칸에 "회원가입", "마이페이지 일괄 재동의" 대신 번역되지 않은 내부 문자열이 그대로 보이던 문제를 수정했습니다. +- 「GDPR 동의 이력」의 출처 필터에 "회원가입"과 "마이페이지 일괄 재동의"가 빠져 있어, 그 경로로 남은 동의 기록만 골라볼 수 없던 문제를 수정했습니다. +- 정책 버전 목록의 정렬 기준을 고정해, 페이지를 넘길 때 순서가 흔들리지 않도록 했습니다. ### Changed diff --git a/plugins/_bundled/sirsoft-gdpr/docs/api/consent-log.md b/plugins/_bundled/sirsoft-gdpr/docs/api/consent-log.md index 8e019ebf..433f7a51 100644 --- a/plugins/_bundled/sirsoft-gdpr/docs/api/consent-log.md +++ b/plugins/_bundled/sirsoft-gdpr/docs/api/consent-log.md @@ -47,7 +47,7 @@ _목록 응답: `data.data[]` 배열 항목의 필드 + `data.pagination`._ | session_id | string | `c50916f6-58ee-42a6-a046-60a0c9ecfad2` | session 식별자 (연관 리소스 참조) | | consent_key | string | `cookie_marketing` | 변경된 동의 항목 키 (쿠키 카테고리 키). 어떤 동의 항목의 부여/철회가 기록된 행인지 나타냅니다 | | action | string | `revoked` | 이 이력 행의 변경 유형 (`granted`=동의 부여, `revoked`=철회, `rejected`=명시적 거부). `rejected`는 배너에서 "동의하지 않고 계속하기"로 선택형 카테고리를 거부한 행위이며 관리자 화면에 amber 배지로 표시됩니다. Art.7(1) 입증 트레일의 행위 구분입니다 | -| source | string | `banner` | 동의 변경이 발생한 경로 (`banner`=쿠키 배너, `preference_center`=환경설정 센터, `mypage`=마이페이지, 그 외 `register`/`order`/`withdraw`) | +| source | string | `banner` | 동의 변경이 발생한 경로. 허용 어휘는 `ConsentSource` enum 5종 — `banner`=쿠키 배너, `preference_center`=환경설정 센터, `register`=회원가입, `mypage`=마이페이지, `mypage_renew_all`=정책 개정 후 마이페이지 일괄 재동의(서버 자체 기록) | | policy_version | string | `10` | 변경 시점의 정책 버전 문자열. 해당 동의가 어느 정책 버전 기준으로 표명되었는지 기록해 정책 갱신 후 재동의 판정과 감사에 사용됩니다 | | categories | object | `{"cookie_analytics":false,"cookie_marketing":false,"cooki…` | 변경 시점 전체 카테고리별 동의 여부 스냅샷 (키→boolean). MySQL JSON 컬럼이라 키가 알파벳 순으로 정규화 저장됩니다 | | categories_snapshot | array | `[{"key":"cookie_necessary","label_key":"sirsoft-gdpr.cons…` | `categories` 객체를 관리자 화면 iteration 친화 배열(`{key, label_key, granted}`)로 변환한 것. 필수→분석→마케팅 UX 위계 순으로 재정렬되며 `label_key`는 프론트가 다국어로 해석합니다 | @@ -89,6 +89,6 @@ HTTP/1.1 200 -**설명** 관리자 감사 화면에서 `gdpr_user_consent_histories` 테이블의 동의 로그를 페이지네이션으로 조회합니다. `auth:sanctum`과 `sirsoft-gdpr.privacy.view` 권한이 필요합니다. `email`, `session_id`, `consent_keys[]`, `actions[]`(granted|revoked|rejected), `sources[]`(banner|preference_center|mypage), `per_page`(1~100, 기본 20) 쿼리 필터를 지원합니다. DPO 감사 용도로 IP 주소와 User-Agent까지 노출되는 조회 전용 엔드포인트입니다. +**설명** 관리자 감사 화면에서 `gdpr_user_consent_histories` 테이블의 동의 로그를 페이지네이션으로 조회합니다. `auth:sanctum`과 `sirsoft-gdpr.privacy.view` 권한이 필요합니다. `email`, `session_id`, `consent_keys[]`, `actions[]`(granted|revoked|rejected — `ConsentAction` enum 전 케이스), `sources[]`(banner|preference_center|register|mypage|mypage_renew_all — `ConsentSource` enum 전 케이스), `per_page`(1~100, 기본 20) 쿼리 필터를 지원합니다. DPO 감사 용도로 IP 주소와 User-Agent까지 노출되는 조회 전용 엔드포인트입니다. diff --git a/plugins/_bundled/sirsoft-gdpr/lang/en/messages.php b/plugins/_bundled/sirsoft-gdpr/lang/en/messages.php index e9c5bf1b..4a3f4b48 100644 --- a/plugins/_bundled/sirsoft-gdpr/lang/en/messages.php +++ b/plugins/_bundled/sirsoft-gdpr/lang/en/messages.php @@ -138,6 +138,9 @@ return [ 'snapshot_action' => 'Snapshot', ], ], + 'history_page_summary' => ':total total · page :current / :last', + 'history_prev' => 'Previous', + 'history_next' => 'Next', 'snapshot_view_short' => 'View', 'material_modal' => [ 'title' => 'Publish new policy version + trigger re-consent', @@ -195,7 +198,9 @@ return [ 'source' => [ 'banner' => 'Banner', 'preference_center' => 'Preferences', + 'register' => 'Sign-up', 'mypage' => 'MyPage', + 'mypage_renew_all' => 'MyPage bulk re-consent', ], 'col' => [ 'created_at' => 'Time', diff --git a/plugins/_bundled/sirsoft-gdpr/lang/ko/messages.php b/plugins/_bundled/sirsoft-gdpr/lang/ko/messages.php index fb52b1b4..993e3aa6 100644 --- a/plugins/_bundled/sirsoft-gdpr/lang/ko/messages.php +++ b/plugins/_bundled/sirsoft-gdpr/lang/ko/messages.php @@ -138,6 +138,9 @@ return [ 'snapshot_action' => '본문', ], ], + 'history_page_summary' => '총 :total건 · :current / :last 페이지', + 'history_prev' => '이전', + 'history_next' => '다음', 'snapshot_view_short' => '보기', 'material_modal' => [ 'title' => '정책 버전 발행 + 재동의 트리거', @@ -195,7 +198,9 @@ return [ 'source' => [ 'banner' => '배너', 'preference_center' => '환경설정', + 'register' => '회원가입', 'mypage' => '마이페이지', + 'mypage_renew_all' => '마이페이지 일괄 재동의', ], 'col' => [ 'created_at' => '시점', diff --git a/plugins/_bundled/sirsoft-gdpr/resources/lang/en.json b/plugins/_bundled/sirsoft-gdpr/resources/lang/en.json index 2ae96555..441bf3ad 100644 --- a/plugins/_bundled/sirsoft-gdpr/resources/lang/en.json +++ b/plugins/_bundled/sirsoft-gdpr/resources/lang/en.json @@ -45,7 +45,7 @@ "title": "GDPR Settings", "description": "Operators control the cookie consent banner and the MyPage privacy card.", "section": { - "operator": "Operator \/ Privacy Policy", + "operator": "Operator / Privacy Policy", "operator_desc": "Register the operator name, data storage location, and privacy policy page slug shown in the cookie banner and policy page.", "cookie_banner": "Cookie Consent Banner", "cookie_banner_desc": "Configure visibility, position, categories, and policy version of the cookie consent banner — all in one place.", @@ -77,14 +77,14 @@ "placeholder": "e.g., privacy-policy" }, "legal_entity_name": { - "label": "Legal Entity \/ Operator Name", + "label": "Legal Entity / Operator Name", "hint": "The operator name shown in the cookie banner and MyPage.", "placeholder": "e.g., Acme Corp." }, "data_storage_location": { "label": "Data Storage Location", "hint": "Country where user data is stored, shown to users. e.g., \"United States (AWS)\", \"Germany\". Do not enter IP addresses, cloud region codes (e.g., ap-northeast-2), or datacenter addresses — these are security-sensitive.", - "placeholder": "e.g., Korea (self-hosted IDC) \/ United States (AWS)" + "placeholder": "e.g., Korea (self-hosted IDC) / United States (AWS)" }, "banner_enabled": { "label": "Show Cookie Banner", @@ -201,7 +201,10 @@ "open_policy_page": "Open policy page", "no_policy_slug": "No privacy policy page registered." }, - "not_found": "Policy version not found." + "not_found": "Policy version not found.", + "history_page_summary": "{total} total · page {current} / {last}", + "history_prev": "Previous", + "history_next": "Next" } }, "admin": { @@ -233,7 +236,9 @@ "source": { "banner": "Banner", "preference_center": "Preferences", - "mypage": "MyPage" + "mypage": "MyPage", + "register": "Sign-up", + "mypage_renew_all": "MyPage bulk re-consent" }, "col": { "created_at": "Time", diff --git a/plugins/_bundled/sirsoft-gdpr/resources/lang/ko.json b/plugins/_bundled/sirsoft-gdpr/resources/lang/ko.json index e286f8fa..8ab8e172 100644 --- a/plugins/_bundled/sirsoft-gdpr/resources/lang/ko.json +++ b/plugins/_bundled/sirsoft-gdpr/resources/lang/ko.json @@ -45,10 +45,10 @@ "title": "GDPR 설정", "description": "쿠키 동의 배너와 마이페이지 동의 관리 카드의 동작을 운영자가 제어합니다.", "section": { - "operator": "운영 주체 \/ 개인정보처리방침", + "operator": "운영 주체 / 개인정보처리방침", "operator_desc": "쿠키 배너·정책 페이지에 표시될 운영 주체, 데이터 저장 위치, 정책 페이지 슬러그를 등록합니다.", "cookie_banner": "쿠키 동의 배너", - "cookie_banner_desc": "방문자에게 표시되는 쿠키 동의 배너의 노출\/위치\/카테고리\/정책 버전을 한 곳에서 설정합니다.", + "cookie_banner_desc": "방문자에게 표시되는 쿠키 동의 배너의 노출/위치/카테고리/정책 버전을 한 곳에서 설정합니다.", "cookie_banner_settings": "배너 표시 설정", "cookie_categories": "쿠키 카테고리", "cookie_policy_version": "쿠키 정책 버전", @@ -77,14 +77,14 @@ "placeholder": "예: privacy-policy" }, "legal_entity_name": { - "label": "법인명 \/ 운영 주체", + "label": "법인명 / 운영 주체", "hint": "쿠키 배너와 마이페이지에 표시되는 사이트 운영 주체 이름입니다.", "placeholder": "예: (주)회사명" }, "data_storage_location": { "label": "데이터 저장 위치", "hint": "사용자에게 안내할 데이터 저장 국가를 입력하세요. 예: \"대한민국\", \"미국 (AWS)\". IP 주소, 클라우드 리전 코드(예: ap-northeast-2), 데이터센터 주소는 보안상 입력하지 마세요.", - "placeholder": "예: 대한민국 (자체 데이터센터) \/ 미국 (AWS)" + "placeholder": "예: 대한민국 (자체 데이터센터) / 미국 (AWS)" }, "banner_enabled": { "label": "쿠키 배너 노출", @@ -201,13 +201,16 @@ "open_policy_page": "본문 페이지 열기", "no_policy_slug": "등록된 정책 본문 페이지가 없습니다." }, - "not_found": "해당 정책 버전을 찾을 수 없습니다." + "not_found": "해당 정책 버전을 찾을 수 없습니다.", + "history_page_summary": "총 {total}건 · {current} / {last} 페이지", + "history_prev": "이전", + "history_next": "다음" } }, "admin": { "consent_log": { "title": "GDPR 동의 이력", - "description": "회원·게스트의 동의\/철회 변경 이력을 조회합니다. IP·User-Agent 등 감사 정보가 함께 노출됩니다.", + "description": "회원·게스트의 동의/철회 변경 이력을 조회합니다. IP·User-Agent 등 감사 정보가 함께 노출됩니다.", "refresh": "새로고침", "empty": "조회된 동의 이력이 없습니다.", "clear_filters": "필터 초기화", @@ -233,7 +236,9 @@ "source": { "banner": "배너", "preference_center": "환경설정", - "mypage": "마이페이지" + "mypage": "마이페이지", + "register": "회원가입", + "mypage_renew_all": "마이페이지 일괄 재동의" }, "col": { "created_at": "시점", @@ -259,7 +264,7 @@ "title": "쿠키 동의 관리", "description": "사이트가 사용 중인 쿠키 항목에 대한 동의를 변경하거나 철회할 수 있습니다.", "section_consents": "내 동의 현황", - "section_consents_hint": "동의\/철회 시점에 따라 즉시 반영됩니다. 필수 항목은 철회할 수 없습니다.", + "section_consents_hint": "동의/철회 시점에 따라 즉시 반영됩니다. 필수 항목은 철회할 수 없습니다.", "col_consent_key": "동의 항목", "col_consented": "상태", "col_consented_at": "동의일", @@ -312,7 +317,7 @@ "save": "선택 저장", "policy_link": "개인정보처리방침", "preferences_title": "쿠키 환경설정", - "preferences_description": "카테고리별 쿠키 사용을 개별 동의\/철회할 수 있습니다. 필수 카테고리는 끌 수 없습니다.", + "preferences_description": "카테고리별 쿠키 사용을 개별 동의/철회할 수 있습니다. 필수 카테고리는 끌 수 없습니다.", "category_required_badge": "필수", "view_policy_label": "변경된 정책 본문 보기", "operator_and_storage_line": "본 사이트는 {entity}이(가) 운영하며, 데이터는 {location}에 저장됩니다", diff --git a/plugins/_bundled/sirsoft-gdpr/resources/layouts/admin/gdpr_consent_log.json b/plugins/_bundled/sirsoft-gdpr/resources/layouts/admin/gdpr_consent_log.json index ba88ddf5..4e11615e 100644 --- a/plugins/_bundled/sirsoft-gdpr/resources/layouts/admin/gdpr_consent_log.json +++ b/plugins/_bundled/sirsoft-gdpr/resources/layouts/admin/gdpr_consent_log.json @@ -1027,6 +1027,98 @@ "text": "$t:sirsoft-gdpr.admin.consent_log.source.mypage" } ] + }, + { + "type": "basic", + "name": "Label", + "props": { + "className": "inline-clickable" + }, + "children": [ + { + "type": "basic", + "name": "Input", + "props": { + "type": "checkbox", + "className": "checkbox", + "checked": "{{(_local.filter.sources || []).includes('register')}}" + }, + "actions": [ + { + "type": "change", + "handler": "sequence", + "params": { + "actions": [ + { + "handler": "setState", + "params": { + "target": "local", + "filter.sources": "{{$event.target.checked ? [...(_local.filter.sources || []).filter(s => s !== 'register'), 'register'] : (_local.filter.sources || []).filter(s => s !== 'register')}}" + } + }, + { + "actionRef": "searchConsentLogs" + } + ] + } + } + ] + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-label" + }, + "text": "$t:sirsoft-gdpr.admin.consent_log.source.register" + } + ] + }, + { + "type": "basic", + "name": "Label", + "props": { + "className": "inline-clickable" + }, + "children": [ + { + "type": "basic", + "name": "Input", + "props": { + "type": "checkbox", + "className": "checkbox", + "checked": "{{(_local.filter.sources || []).includes('mypage_renew_all')}}" + }, + "actions": [ + { + "type": "change", + "handler": "sequence", + "params": { + "actions": [ + { + "handler": "setState", + "params": { + "target": "local", + "filter.sources": "{{$event.target.checked ? [...(_local.filter.sources || []).filter(s => s !== 'mypage_renew_all'), 'mypage_renew_all'] : (_local.filter.sources || []).filter(s => s !== 'mypage_renew_all')}}" + } + }, + { + "actionRef": "searchConsentLogs" + } + ] + } + } + ] + }, + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-label" + }, + "text": "$t:sirsoft-gdpr.admin.consent_log.source.mypage_renew_all" + } + ] } ] } diff --git a/plugins/_bundled/sirsoft-gdpr/resources/layouts/admin/plugin_settings.json b/plugins/_bundled/sirsoft-gdpr/resources/layouts/admin/plugin_settings.json index ece7337b..dbe8fccc 100644 --- a/plugins/_bundled/sirsoft-gdpr/resources/layouts/admin/plugin_settings.json +++ b/plugins/_bundled/sirsoft-gdpr/resources/layouts/admin/plugin_settings.json @@ -38,10 +38,11 @@ "id": "gdprPolicyVersionHistory", "label_key": "$t:sirsoft-gdpr.editor.data_source.gdprPolicyVersionHistory", "type": "api", - "endpoint": "/api/plugins/sirsoft-gdpr/admin/policy-versions", + "endpoint": "/api/plugins/sirsoft-gdpr/admin/policy-versions?page={{_local.policyHistoryPage ?? 1}}", "method": "GET", "auto_fetch": false, - "auth_required": true + "auth_required": true, + "comment": "정책 버전 이력 — 페이지는 _local.policyHistoryPage 로 관리하고 페이저가 setState + refetchDataSource 로 이동시킨다." }, { "comment": "정책 버전 단건 snapshot — _global.viewingPolicyVersion 정수를 endpoint path 에 보간. v배지/이력 행 클릭 시 setState global.viewingPolicyVersion → openModal policy_version_snapshot_modal 시퀀스에서 자동 fetch.", @@ -63,7 +64,8 @@ "isSaving": false, "hasChanges": false, "errors": null, - "policyHistoryOpen": false + "policyHistoryOpen": false, + "policyHistoryPage": 1 } } ], @@ -929,7 +931,8 @@ "handler": "setState", "params": { "target": "local", - "policyHistoryOpen": "{{!_local.policyHistoryOpen}}" + "policyHistoryOpen": "{{!_local.policyHistoryOpen}}", + "policyHistoryPage": 1 } }, { @@ -1171,6 +1174,103 @@ ] } ] + }, + { + "id": "policy_version_history_pager", + "comment": "이력 페이저 — 총건수/현재 페이지를 함께 표시한다. 없으면 최신 20건만 보이고 나머지가 조용히 잘린다.", + "type": "basic", + "name": "Div", + "if": "{{(gdprPolicyVersionHistory?.data?.pagination?.last_page ?? 1) > 1}}", + "props": { + "className": "flex items-center justify-between gap-3 pt-3 mt-3 border-t border-gray-200 dark:border-gray-700" + }, + "children": [ + { + "type": "basic", + "name": "Span", + "props": { + "className": "text-label-subtle text-sm" + }, + "text": "$t:sirsoft-gdpr.settings.policy_version.history_page_summary|current={{gdprPolicyVersionHistory?.data?.pagination?.current_page ?? 1}}|last={{gdprPolicyVersionHistory?.data?.pagination?.last_page ?? 1}}|total={{gdprPolicyVersionHistory?.data?.pagination?.total ?? 0}}" + }, + { + "type": "basic", + "name": "Div", + "props": { + "className": "flex items-center gap-2" + }, + "children": [ + { + "id": "policy_history_prev", + "type": "basic", + "name": "Button", + "props": { + "type": "button", + "className": "btn btn-sm btn-neutral", + "disabled": "{{(gdprPolicyVersionHistory?.data?.pagination?.current_page ?? 1) <= 1}}" + }, + "text": "$t:sirsoft-gdpr.settings.policy_version.history_prev", + "actions": [ + { + "type": "click", + "handler": "sequence", + "params": { + "actions": [ + { + "handler": "setState", + "params": { + "target": "local", + "policyHistoryPage": "{{Math.min(Math.max(1, (_local.policyHistoryPage ?? 1) + (-1)), gdprPolicyVersionHistory?.data?.pagination?.last_page ?? 1)}}" + } + }, + { + "handler": "refetchDataSource", + "params": { + "dataSourceId": "gdprPolicyVersionHistory" + } + } + ] + } + } + ] + }, + { + "id": "policy_history_next", + "type": "basic", + "name": "Button", + "props": { + "type": "button", + "className": "btn btn-sm btn-neutral", + "disabled": "{{(gdprPolicyVersionHistory?.data?.pagination?.current_page ?? 1) >= (gdprPolicyVersionHistory?.data?.pagination?.last_page ?? 1)}}" + }, + "text": "$t:sirsoft-gdpr.settings.policy_version.history_next", + "actions": [ + { + "type": "click", + "handler": "sequence", + "params": { + "actions": [ + { + "handler": "setState", + "params": { + "target": "local", + "policyHistoryPage": "{{Math.min(Math.max(1, (_local.policyHistoryPage ?? 1) + (1)), gdprPolicyVersionHistory?.data?.pagination?.last_page ?? 1)}}" + } + }, + { + "handler": "refetchDataSource", + "params": { + "dataSourceId": "gdprPolicyVersionHistory" + } + } + ] + } + } + ] + } + ] + } + ] } ] } diff --git a/plugins/_bundled/sirsoft-gdpr/src/Enums/ConsentSource.php b/plugins/_bundled/sirsoft-gdpr/src/Enums/ConsentSource.php new file mode 100644 index 00000000..b7ae22a1 --- /dev/null +++ b/plugins/_bundled/sirsoft-gdpr/src/Enums/ConsentSource.php @@ -0,0 +1,66 @@ +value); + } + + /** + * 모든 케이스의 string 값 목록. + * + * @return array + */ + public static function allValues(): array + { + return array_column(self::cases(), 'value'); + } + + /** + * 사용자 요청으로 직접 지정할 수 있는 출처 값 목록. + * + * `register` / `mypage_renew_all` 은 서버가 스스로 기록하는 경로이므로 + * 공개 요청 본문에서 지정할 수 없습니다. + * + * @return array + */ + public static function requestSelectableValues(): array + { + return [ + self::Banner->value, + self::PreferenceCenter->value, + self::Register->value, + self::Mypage->value, + ]; + } +} diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Admin/GdprAdminConsentLogController.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Admin/GdprAdminConsentLogController.php index 19bafea5..34a12c5f 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Admin/GdprAdminConsentLogController.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Admin/GdprAdminConsentLogController.php @@ -5,7 +5,7 @@ namespace Plugins\Sirsoft\Gdpr\Http\Controllers\Admin; use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\AdminBaseController; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; +use Plugins\Sirsoft\Gdpr\Http\Requests\IndexConsentLogRequest; use Plugins\Sirsoft\Gdpr\Http\Resources\GdprConsentLogResource; use Plugins\Sirsoft\Gdpr\Services\GdprConsentLogService; @@ -40,25 +40,15 @@ class GdprAdminConsentLogController extends AdminBaseController * - session_id: 게스트 세션 ID 부분 일치 (DataGrid 가 앞 8자만 표시하므로 prefix/중간 모두 허용) * - consent_keys[]: 동의 항목 키 배열 (whereIn) * - actions[]: granted|revoked 배열 - * - sources[]: banner|preference_center|mypage 배열 + * - sources[]: 출처 배열 (허용 어휘는 ConsentSource enum — banner|preference_center|register|mypage|mypage_renew_all) * - per_page: 페이지 크기 (1~100, 기본 20) * - * @param Request $request HTTP 요청 + * @param IndexConsentLogRequest $request HTTP 요청 * @return JsonResponse */ - public function index(Request $request): JsonResponse + public function index(IndexConsentLogRequest $request): JsonResponse { - $filters = [ - 'email' => $request->query('email'), - 'session_id' => $request->query('session_id'), - 'consent_keys' => $this->normalizeArrayParam($request->query('consent_keys')), - 'actions' => $this->normalizeArrayParam($request->query('actions')), - 'sources' => $this->normalizeArrayParam($request->query('sources')), - ]; - - $perPage = max(1, min(100, (int) $request->query('per_page', 20))); - - $paginator = $this->consentLogService->paginateForAdmin($filters, $perPage); + $paginator = $this->consentLogService->paginateForAdmin($request->filters(), $request->perPage()); return ResponseHelper::success('messages.success', [ 'data' => GdprConsentLogResource::collection($paginator->items()), @@ -72,26 +62,4 @@ class GdprAdminConsentLogController extends AdminBaseController ], ]); } - - /** - * 쿼리 파라미터 배열을 정규화합니다. - * - * `?consent_keys[]=a&consent_keys[]=b` → ['a', 'b'] - * `?consent_keys=a` → ['a'] (단일 값도 배열로 정규화) - * `null` 또는 빈 값 → [] - * - * @param mixed $value 쿼리 값 - * @return array - */ - private function normalizeArrayParam(mixed $value): array - { - if ($value === null || $value === '') { - return []; - } - if (is_array($value)) { - return array_values(array_filter(array_map('strval', $value), fn ($v) => $v !== '')); - } - - return [(string) $value]; - } } diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Admin/GdprAdminPolicyVersionController.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Admin/GdprAdminPolicyVersionController.php index b2054953..f8f6d7f2 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Admin/GdprAdminPolicyVersionController.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Admin/GdprAdminPolicyVersionController.php @@ -5,7 +5,7 @@ namespace Plugins\Sirsoft\Gdpr\Http\Controllers\Admin; use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\AdminBaseController; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; +use Plugins\Sirsoft\Gdpr\Http\Requests\IndexPolicyVersionRequest; use Plugins\Sirsoft\Gdpr\Http\Requests\PublishPolicyVersionRequest; use Plugins\Sirsoft\Gdpr\Http\Resources\GdprPolicyVersionDetailResource; use Plugins\Sirsoft\Gdpr\Http\Resources\GdprPolicyVersionResource; @@ -28,8 +28,8 @@ class GdprAdminPolicyVersionController extends AdminBaseController /** * GdprAdminPolicyVersionController 생성자 * - * @param GdprPolicyVersionService $service 정책 버전 서비스 - * @param GdprSettingsService $settingsService 현재 settings snapshot 캡처용 + * @param GdprPolicyVersionService $service 정책 버전 서비스 + * @param GdprSettingsService $settingsService 현재 settings snapshot 캡처용 */ public function __construct( private readonly GdprPolicyVersionService $service, @@ -44,14 +44,12 @@ class GdprAdminPolicyVersionController extends AdminBaseController * 쿼리 파라미터: * - per_page: 페이지 크기 (1~100, 기본 20) * - * @param Request $request HTTP 요청 + * @param IndexPolicyVersionRequest $request HTTP 요청 * @return JsonResponse */ - public function index(Request $request): JsonResponse + public function index(IndexPolicyVersionRequest $request): JsonResponse { - $perPage = max(1, min(100, (int) $request->query('per_page', 20))); - - $paginator = $this->service->paginate($perPage); + $paginator = $this->service->paginate($request->perPage()); return ResponseHelper::success('messages.success', [ 'data' => GdprPolicyVersionResource::collection($paginator->items()), @@ -77,7 +75,7 @@ class GdprAdminPolicyVersionController extends AdminBaseController * 호출 시 현재 settings snapshot 을 자동 캡처하여 새 정책 버전 발행 * (settings 자체는 변경되지 않음 — 발행만 수행). * - * @param PublishPolicyVersionRequest $request memo 필수 검증 + * @param PublishPolicyVersionRequest $request memo 필수 검증 * @return JsonResponse */ public function store(PublishPolicyVersionRequest $request): JsonResponse @@ -118,7 +116,7 @@ class GdprAdminPolicyVersionController extends AdminBaseController * * 권한: sirsoft-gdpr.privacy.view (라우트 미들웨어에서 검증). * - * @param int $version 조회할 정책 버전 정수 (URL path param) + * @param int $version 조회할 정책 버전 정수 (URL path param) * @return JsonResponse 200 + detail / 404 (버전 부재) */ public function show(int $version): JsonResponse diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Public/GdprCookieConsentController.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Public/GdprCookieConsentController.php index 4a6cb07f..d7243f56 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Public/GdprCookieConsentController.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/Public/GdprCookieConsentController.php @@ -6,6 +6,9 @@ use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\PublicBaseController; use Illuminate\Http\JsonResponse; use Plugins\Sirsoft\Gdpr\Concerns\IssuesGuestSessionCookie; +use Illuminate\Http\Request; +use Illuminate\Support\Str; +use Plugins\Sirsoft\Gdpr\Http\Requests\CookieConsentStatusRequest; use Plugins\Sirsoft\Gdpr\Http\Requests\StoreCookieConsentRequest; use Plugins\Sirsoft\Gdpr\Services\GdprConsentService; @@ -24,7 +27,7 @@ class GdprCookieConsentController extends PublicBaseController /** * GdprCookieConsentController 생성자 * - * @param GdprConsentService $consentService GDPR 동의 서비스 + * @param GdprConsentService $consentService GDPR 동의 서비스 */ public function __construct( private readonly GdprConsentService $consentService, @@ -38,7 +41,7 @@ class GdprCookieConsentController extends PublicBaseController * 회원이면 user_id 기반 status upsert + history INSERT, * 게스트면 session_id 기반 history INSERT. * - * @param StoreCookieConsentRequest $request 검증된 요청 + * @param StoreCookieConsentRequest $request 검증된 요청 * @return JsonResponse */ public function store(StoreCookieConsentRequest $request): JsonResponse @@ -90,10 +93,10 @@ class GdprCookieConsentController extends PublicBaseController * 배너 표시 여부 결정용. has_consented = 현재 정책 버전으로 동의 완료 여부. * 회원이 sanctum 토큰으로 호출하면 user_id 기반, 미인증이면 session_id 기반. * - * @param \Illuminate\Http\Request $request 요청 + * @param CookieConsentStatusRequest $request 요청 * @return JsonResponse */ - public function status(\Illuminate\Http\Request $request): JsonResponse + public function status(CookieConsentStatusRequest $request): JsonResponse { $userId = $request->user()?->id; $sessionId = $userId === null ? $this->resolveGuestSessionId($request) : null; @@ -127,7 +130,7 @@ class GdprCookieConsentController extends PublicBaseController * (위조된 값은 미식별 게스트로 취급). 쿠키가 없으면 Laravel session ID를 * fallback으로 사용합니다. * - * @param \Illuminate\Http\Request $request 요청 + * @param Request $request 요청 * @return string|null */ private function resolveGuestSessionId($request): ?string @@ -153,7 +156,7 @@ class GdprCookieConsentController extends PublicBaseController */ private function issueGuestSessionId(): string { - return (string) \Illuminate\Support\Str::uuid(); + return (string) Str::uuid(); } /** @@ -162,8 +165,8 @@ class GdprCookieConsentController extends PublicBaseController * 1년 유효, path=/, SameSite=Lax. HTTPS 환경에서는 Secure 자동 적용. * 쿠키 값은 HMAC 서명이 붙어 위변조 시 서버가 거부합니다. * - * @param JsonResponse $response 응답 - * @param string $sessionId 발급된 세션 ID + * @param JsonResponse $response 응답 + * @param string $sessionId 발급된 세션 ID * @return void */ private function attachGuestSessionCookie(JsonResponse $response, string $sessionId): void diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/User/GdprConsentController.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/User/GdprConsentController.php index b398fc73..a469a75f 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/User/GdprConsentController.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Controllers/User/GdprConsentController.php @@ -5,6 +5,7 @@ namespace Plugins\Sirsoft\Gdpr\Http\Controllers\User; use App\Helpers\ResponseHelper; use App\Http\Controllers\Api\Base\AuthBaseController; use Illuminate\Http\JsonResponse; +use Plugins\Sirsoft\Gdpr\Enums\ConsentSource; use Plugins\Sirsoft\Gdpr\Http\Requests\GrantConsentRequest; use Plugins\Sirsoft\Gdpr\Http\Requests\RevokeConsentRequest; use Plugins\Sirsoft\Gdpr\Http\Resources\GdprUserConsentHistoryResource; @@ -23,7 +24,7 @@ class GdprConsentController extends AuthBaseController /** * GdprConsentController 생성자 * - * @param GdprConsentService $consentService GDPR 동의 서비스 + * @param GdprConsentService $consentService GDPR 동의 서비스 */ public function __construct( private readonly GdprConsentService $consentService, @@ -78,7 +79,7 @@ class GdprConsentController extends AuthBaseController /** * 본인 동의 철회. * - * @param RevokeConsentRequest $request 검증된 요청 + * @param RevokeConsentRequest $request 검증된 요청 * @return JsonResponse */ public function revoke(RevokeConsentRequest $request): JsonResponse @@ -95,7 +96,7 @@ class GdprConsentController extends AuthBaseController sessionId: null, consentKey: $consentKey, value: false, - source: 'mypage', + source: ConsentSource::Mypage->value, ); return ResponseHelper::success('sirsoft-gdpr::messages.consent.revoked', [ @@ -109,7 +110,7 @@ class GdprConsentController extends AuthBaseController * Art.7(3) 자유 변경권의 부여 방향 — 마이페이지 「내 동의 현황」 에서 * 「다시 동의」 / 「동의」 버튼 클릭 시 호출. 화이트리스트 검사는 FormRequest 가 수행. * - * @param GrantConsentRequest $request 검증된 요청 + * @param GrantConsentRequest $request 검증된 요청 * @return JsonResponse */ public function grant(GrantConsentRequest $request): JsonResponse @@ -126,7 +127,7 @@ class GdprConsentController extends AuthBaseController sessionId: null, consentKey: $consentKey, value: true, - source: 'mypage', + source: ConsentSource::Mypage->value, ); return ResponseHelper::success('sirsoft-gdpr::messages.consent.granted_again', [ @@ -162,5 +163,4 @@ class GdprConsentController extends AuthBaseController ['renewed' => $renewed], ); } - } diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/CookieConsentStatusRequest.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/CookieConsentStatusRequest.php new file mode 100644 index 00000000..236d52ca --- /dev/null +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/CookieConsentStatusRequest.php @@ -0,0 +1,34 @@ +> 검증 규칙 (사용 파라미터 없음) + */ + public function rules(): array + { + return []; + } +} diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/IndexConsentLogRequest.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/IndexConsentLogRequest.php new file mode 100644 index 00000000..d8495d96 --- /dev/null +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/IndexConsentLogRequest.php @@ -0,0 +1,137 @@ + + */ + private const ARRAY_FILTERS = ['consent_keys', 'actions', 'sources']; + + /** + * 요청 권한 — 라우트 permission 미들웨어가 담당하므로 true 고정. + * + * @return bool 항상 true + */ + public function authorize(): bool + { + return true; + } + + /** + * 단일 값으로 도착한 배열 필터를 검증 전에 배열로 정규화합니다. + * + * `?sources[]=banner` 와 `?sources=banner` 를 모두 허용하기 위한 것으로, 정규화하지 + * 않으면 후자가 `array` 규칙에 걸려 목록 전체가 422 가 됩니다. + */ + protected function prepareForValidation(): void + { + $normalized = []; + + foreach (self::ARRAY_FILTERS as $key) { + if (! $this->has($key)) { + continue; + } + + $value = $this->input($key); + + if (is_array($value)) { + continue; + } + + $normalized[$key] = ($value === null || $value === '') ? [] : [(string) $value]; + } + + if ($normalized !== []) { + $this->merge($normalized); + } + } + + /** + * 검증 규칙을 반환합니다. + * + * @return array> 검증 규칙 + */ + public function rules(): array + { + return [ + 'email' => ['nullable', 'string', 'max:255'], + 'session_id' => ['nullable', 'string', 'max:100'], + 'consent_keys' => ['nullable', 'array'], + 'consent_keys.*' => ['string', 'max:100'], + 'actions' => ['nullable', 'array'], + 'actions.*' => ['string', Rule::in(ConsentAction::allValues())], + 'sources' => ['nullable', 'array'], + 'sources.*' => ['string', Rule::in(ConsentSource::allValues())], + // 상·하한은 규칙이 아니라 접근자에서 클램프한다 — 기존 컨트롤러가 범위를 벗어난 + // per_page 를 거부하지 않고 조정해 응답하던 계약을 유지한다. + 'per_page' => ['nullable', 'integer'], + ]; + } + + /** + * 목록 필터를 반환합니다. + * + * @return array 필터 배열 (배열 필터는 항상 배열) + */ + public function filters(): array + { + return [ + 'email' => $this->validated('email'), + 'session_id' => $this->validated('session_id'), + 'consent_keys' => $this->arrayFilter('consent_keys'), + 'actions' => $this->arrayFilter('actions'), + 'sources' => $this->arrayFilter('sources'), + ]; + } + + /** + * 페이지 크기를 반환합니다. + * + * @return int 페이지 크기 (1 ~ 상한으로 클램프) + */ + public function perPage(): int + { + $perPage = (int) ($this->validated('per_page') ?? self::DEFAULT_PER_PAGE); + + return max(1, min(self::MAX_PER_PAGE, $perPage)); + } + + /** + * 배열 필터에서 빈 값을 제거해 반환합니다. + * + * @param string $key 필터 키 + * @return array 정규화된 값 목록 + */ + private function arrayFilter(string $key): array + { + /** @var array $values */ + $values = $this->validated($key) ?? []; + + return array_values(array_filter( + array_map('strval', $values), + fn (string $value) => $value !== '' + )); + } +} diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/IndexPolicyVersionRequest.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/IndexPolicyVersionRequest.php new file mode 100644 index 00000000..c3e98327 --- /dev/null +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/IndexPolicyVersionRequest.php @@ -0,0 +1,53 @@ +> 검증 규칙 + */ + public function rules(): array + { + // 상·하한은 규칙이 아니라 접근자에서 클램프한다 — 기존 컨트롤러가 범위를 벗어난 + // per_page 를 거부하지 않고 조정해 응답하던 계약을 유지한다. + return [ + 'per_page' => ['nullable', 'integer'], + ]; + } + + /** + * 페이지 크기를 반환합니다. + * + * @return int 페이지 크기 (1 ~ 상한으로 클램프) + */ + public function perPage(): int + { + $perPage = (int) ($this->validated('per_page') ?? self::DEFAULT_PER_PAGE); + + return max(1, min(self::MAX_PER_PAGE, $perPage)); + } +} diff --git a/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/StoreCookieConsentRequest.php b/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/StoreCookieConsentRequest.php index 8846f945..06cc86f7 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/StoreCookieConsentRequest.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Http/Requests/StoreCookieConsentRequest.php @@ -3,6 +3,9 @@ namespace Plugins\Sirsoft\Gdpr\Http\Requests; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; +use Illuminate\Validation\Validator; +use Plugins\Sirsoft\Gdpr\Enums\ConsentSource; use Plugins\Sirsoft\Gdpr\Services\CookieCategoryService; /** @@ -18,7 +21,7 @@ class StoreCookieConsentRequest extends FormRequest /** * StoreCookieConsentRequest 생성자 * - * @param CookieCategoryService $categoryService 쿠키 카테고리 설정 서비스 + * @param CookieCategoryService $categoryService 쿠키 카테고리 설정 서비스 */ public function __construct(private readonly CookieCategoryService $categoryService) { @@ -45,7 +48,8 @@ class StoreCookieConsentRequest extends FormRequest return [ 'consents' => ['required', 'array', 'min:1'], 'consents.*' => ['required', 'boolean'], - 'source' => ['required', 'string', 'in:banner,preference_center,register,mypage'], + // 허용 어휘의 SSoT 는 ConsentSource enum 이다 (화면 필터·라벨도 같은 목록에서 파생) + 'source' => ['required', 'string', Rule::in(ConsentSource::requestSelectableValues())], // 명시적 거부 신호 (이슈 #430). 배너 "동의하지 않고 계속하기" 버튼이 전송. // 미전송(null) 시 일반 저장(기존 동작). 값은 'reject' 만 허용. 'intent' => ['sometimes', 'nullable', 'string', 'in:reject'], @@ -71,12 +75,12 @@ class StoreCookieConsentRequest extends FormRequest /** * 검증 후 동의 키 화이트리스트 + 필수 항목 false 차단. * - * @param \Illuminate\Validation\Validator $validator + * @param Validator $validator * @return void */ - public function withValidator(\Illuminate\Validation\Validator $validator): void + public function withValidator(Validator $validator): void { - $validator->after(function (\Illuminate\Validation\Validator $validator) { + $validator->after(function (Validator $validator) { $consents = (array) $this->input('consents', []); $allowedKeys = $this->categoryService->getAllConsentKeys(); diff --git a/plugins/_bundled/sirsoft-gdpr/src/Listeners/GdprAuthConsentListener.php b/plugins/_bundled/sirsoft-gdpr/src/Listeners/GdprAuthConsentListener.php index 9ec9ed7b..5e9ec001 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Listeners/GdprAuthConsentListener.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Listeners/GdprAuthConsentListener.php @@ -4,6 +4,7 @@ namespace Plugins\Sirsoft\Gdpr\Listeners; use App\Contracts\Extension\HookListenerInterface; use App\Models\User; +use Plugins\Sirsoft\Gdpr\Enums\ConsentSource; use Plugins\Sirsoft\Gdpr\Services\CookieCategoryService; use Plugins\Sirsoft\Gdpr\Services\GdprConsentService; @@ -29,8 +30,8 @@ class GdprAuthConsentListener implements HookListenerInterface /** * GdprAuthConsentListener 생성자 * - * @param GdprConsentService $consentService 동의 서비스 - * @param CookieCategoryService $categoryService 쿠키 카테고리 서비스 (키 화이트리스트) + * @param GdprConsentService $consentService 동의 서비스 + * @param CookieCategoryService $categoryService 쿠키 카테고리 서비스 (키 화이트리스트) */ public function __construct( private readonly GdprConsentService $consentService, @@ -58,7 +59,7 @@ class GdprAuthConsentListener implements HookListenerInterface /** * 인터페이스 요구 메서드. * - * @param mixed ...$args + * @param mixed ...$args * @return void */ public function handle(...$args): void @@ -69,10 +70,10 @@ class GdprAuthConsentListener implements HookListenerInterface /** * 회원가입 시 폼 데이터에서 GDPR 쿠키 동의를 추출하여 기록. * - * @param User $user 가입 완료된 사용자 - * @param array $data 요청 데이터 - * @param string|null $agreedAt ISO8601 문자열 동의 일시 (코어 AuthService 가 toIso8601String 으로 직렬화 후 전달, 현재 미사용) - * @param string|null $ip IP 주소 (현재 미사용, Service 가 request()->ip() 직접 사용) + * @param User $user 가입 완료된 사용자 + * @param array $data 요청 데이터 + * @param string|null $agreedAt ISO8601 문자열 동의 일시 (코어 AuthService 가 toIso8601String 으로 직렬화 후 전달, 현재 미사용) + * @param string|null $ip IP 주소 (현재 미사용, Service 가 request()->ip() 직접 사용) * @return void */ public function recordRegisterConsents(User $user, array $data, ?string $agreedAt = null, ?string $ip = null): void @@ -88,7 +89,7 @@ class GdprAuthConsentListener implements HookListenerInterface userId: $user->id, sessionId: null, consents: $consents, - source: 'register', + source: ConsentSource::Register->value, ); } @@ -99,8 +100,8 @@ class GdprAuthConsentListener implements HookListenerInterface * - agree_cookie_analytics (sirsoft-marketing 컨벤션) * - cookie_analytics (직접) * - * @param array $data 요청 데이터 - * @param array $allowedKeys 허용된 동의 키 목록 + * @param array $data 요청 데이터 + * @param array $allowedKeys 허용된 동의 키 목록 * @return array [consent_key => bool] */ private function extractConsents(array $data, array $allowedKeys): array diff --git a/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprPolicyVersionRepository.php b/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprPolicyVersionRepository.php index 78c04fcd..e80a4107 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprPolicyVersionRepository.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Repositories/GdprPolicyVersionRepository.php @@ -20,6 +20,8 @@ class GdprPolicyVersionRepository implements GdprPolicyVersionRepositoryInterfac { return GdprPolicyVersion::query() ->orderByDesc('version') + // 전순서 보장 — version 이 유니크하더라도 정렬 계약을 키로 닫아 둔다 + ->orderByDesc('id') ->first(); } @@ -62,6 +64,8 @@ class GdprPolicyVersionRepository implements GdprPolicyVersionRepositoryInterfac return GdprPolicyVersion::query() ->with('createdBy') ->orderByDesc('version') + // 전순서 보장 — version 이 유니크하더라도 정렬 계약을 키로 닫아 둔다 + ->orderByDesc('id') // audit:allow repository-paginate-column-pruning reason: 발행 이력은 정책 변경 시에만 // 늘어나는 정의성 테이블이라 깊은 OFFSET 이 성립하지 않는다. 넓은 컬럼(snapshot JSON)은 // 아래 컬럼 목록에서 제외했고, 본문은 상세 조회(getByVersion)에서만 읽는다. diff --git a/plugins/_bundled/sirsoft-gdpr/src/Services/GdprConsentService.php b/plugins/_bundled/sirsoft-gdpr/src/Services/GdprConsentService.php index 714c81e7..4b7a56e1 100644 --- a/plugins/_bundled/sirsoft-gdpr/src/Services/GdprConsentService.php +++ b/plugins/_bundled/sirsoft-gdpr/src/Services/GdprConsentService.php @@ -7,6 +7,7 @@ use App\Services\PluginSettingsService; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Plugins\Sirsoft\Gdpr\Enums\ConsentAction; +use Plugins\Sirsoft\Gdpr\Enums\ConsentSource; use Plugins\Sirsoft\Gdpr\Models\GdprUserConsent; use Plugins\Sirsoft\Gdpr\Models\GdprUserConsentHistory; use Plugins\Sirsoft\Gdpr\Repositories\Contracts\GdprUserConsentHistoryRepositoryInterface; @@ -28,11 +29,11 @@ class GdprConsentService /** * GdprConsentService 생성자 * - * @param GdprUserConsentRepositoryInterface $statusRepository 동의 상태 Repository - * @param GdprUserConsentHistoryRepositoryInterface $historyRepository 동의 이력 Repository - * @param PluginSettingsService $pluginSettings 플러그인 설정 서비스 - * @param CookieCategoryService $categoryService 쿠키 카테고리 카탈로그 서비스 - * @param GdprPolicyVersionService|null $policyVersionService 정책 버전 서비스 — null 인 경우 lazy resolve (테스트 호환성) + * @param GdprUserConsentRepositoryInterface $statusRepository 동의 상태 Repository + * @param GdprUserConsentHistoryRepositoryInterface $historyRepository 동의 이력 Repository + * @param PluginSettingsService $pluginSettings 플러그인 설정 서비스 + * @param CookieCategoryService $categoryService 쿠키 카테고리 카탈로그 서비스 + * @param GdprPolicyVersionService|null $policyVersionService 정책 버전 서비스 — null 인 경우 lazy resolve (테스트 호환성) */ public function __construct( private readonly GdprUserConsentRepositoryInterface $statusRepository, @@ -68,13 +69,13 @@ class GdprConsentService * 게스트: history만 INSERT (status 미사용). * 동일 상태 재요청은 noop으로 처리하여 중복 이력 방지. * - * @param int|null $userId 회원 ID (게스트면 NULL) - * @param string|null $sessionId 게스트 세션 ID (회원이면 NULL) - * @param string $consentKey 동의 항목 키 - * @param bool $value 동의 여부 - * @param string $source 변경 경로 (banner/preference_center/register/mypage/order/withdraw) - * @param array|null $categories 카테고리 스냅샷 (배너 일괄 변경 시) - * @param bool $isRejection 명시적 거부 신호 (이슈 #430). 선택형 미동의 항목을 is_rejected=true 로 저장. + * @param int|null $userId 회원 ID (게스트면 NULL) + * @param string|null $sessionId 게스트 세션 ID (회원이면 NULL) + * @param string $consentKey 동의 항목 키 + * @param bool $value 동의 여부 + * @param string $source 변경 경로 (허용 어휘는 ConsentSource enum — banner/preference_center/register/mypage/mypage_renew_all) + * @param array|null $categories 카테고리 스냅샷 (배너 일괄 변경 시) + * @param bool $isRejection 명시적 거부 신호 (이슈 #430). 선택형 미동의 항목을 is_rejected=true 로 저장. * @return void */ public function updateConsent( @@ -136,8 +137,8 @@ class GdprConsentService $data['consent_count'] = ($existing?->consent_count ?? 0) + 1; } - HookManager::doAction(self::PLUGIN_ID . '.consent.before_update', $userId, $consentKey, $data); - $data = HookManager::applyFilters(self::PLUGIN_ID . '.consent.filter_update_data', $data, $userId, $consentKey); + HookManager::doAction(self::PLUGIN_ID.'.consent.before_update', $userId, $consentKey, $data); + $data = HookManager::applyFilters(self::PLUGIN_ID.'.consent.filter_update_data', $data, $userId, $consentKey); $consent = $this->statusRepository->upsert($userId, $consentKey, $data); } @@ -166,19 +167,19 @@ class GdprConsentService } $hookName = $value - ? self::PLUGIN_ID . '.consent.granted' - : self::PLUGIN_ID . '.consent.revoked'; + ? self::PLUGIN_ID.'.consent.granted' + : self::PLUGIN_ID.'.consent.revoked'; HookManager::doAction($hookName, $consent, $source); } /** * 여러 동의 항목을 일괄 업데이트합니다. * - * @param int|null $userId 회원 ID - * @param string|null $sessionId 게스트 세션 ID - * @param array $consents 동의 데이터 [key => bool] - * @param string $source 변경 경로 - * @param bool $isRejection 명시적 거부 신호 (이슈 #430). 선택형 미동의 항목을 is_rejected=true 로 저장. + * @param int|null $userId 회원 ID + * @param string|null $sessionId 게스트 세션 ID + * @param array $consents 동의 데이터 [key => bool] + * @param string $source 변경 경로 + * @param bool $isRejection 명시적 거부 신호 (이슈 #430). 선택형 미동의 항목을 is_rejected=true 로 저장. * @return void */ public function updateConsents(?int $userId, ?string $sessionId, array $consents, string $source, bool $isRejection = false): void @@ -195,7 +196,7 @@ class GdprConsentService * * 로그인 후 클라이언트 동기화에 사용됩니다. * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return Collection */ public function getActiveConsents(int $userId): Collection @@ -215,7 +216,7 @@ class GdprConsentService * (consent_count=0, is_consented=false) * - 카탈로그에 없는 status row (예: 운영자가 카테고리 삭제) → 결과에서 제외 (UI 노출 부적합) * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return Collection */ public function getMyConsentMatrix(int $userId): Collection @@ -230,7 +231,7 @@ class GdprConsentService continue; } - $consentKey = 'cookie_' . $bareKey; + $consentKey = 'cookie_'.$bareKey; if ($statusByKey->has($consentKey)) { $rows->push($statusByKey->get($consentKey)); @@ -251,7 +252,7 @@ class GdprConsentService /** * 회원의 모든 동의 상태를 반환합니다 (활성·철회 포함). * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return Collection */ public function getAllConsents(int $userId): Collection @@ -262,7 +263,7 @@ class GdprConsentService /** * 회원의 동의 이력을 반환합니다. * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return Collection */ public function getHistories(int $userId): Collection @@ -273,7 +274,7 @@ class GdprConsentService /** * 게스트 세션의 동의 이력을 반환합니다. * - * @param string $sessionId 게스트 세션 ID + * @param string $sessionId 게스트 세션 ID * @return Collection */ public function getGuestHistories(string $sessionId): Collection @@ -287,7 +288,7 @@ class GdprConsentService * status 테이블의 is_consented=true 행을 false로 UPDATE + * 각 항목별 history에 source=withdraw로 revoked 행 INSERT. * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return void */ public function revokeAllOnWithdraw(int $userId): void @@ -306,7 +307,7 @@ class GdprConsentService * history 테이블은 user_id·ip_address·user_agent를 NULL로 익명화하여 * 감사 추적용 행을 보존합니다 (GDPR Art.17 + Art.7(1) 양립). * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return void */ public function purgeOnUserDelete(int $userId): void @@ -320,7 +321,7 @@ class GdprConsentService * * 회원의 가장 최근 동의가 현재 정책 버전과 다르면 재동의 트리거 대상. * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return bool 재동의 필요 여부 */ public function needsRenewal(int $userId): bool @@ -362,7 +363,7 @@ class GdprConsentService * - 활성 + 옛 버전 → policy_version = current, last_source = 'mypage_renew_all' * - 각 갱신 행마다 history append (action=granted, source=mypage_renew_all) — Art.7(1) 입증 트레일 * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return int 갱신된 동의 행 수 */ public function renewAllForCurrentPolicy(int $userId): int @@ -392,13 +393,13 @@ class GdprConsentService $data = [ 'is_consented' => true, 'policy_version' => $current, - 'last_source' => 'mypage_renew_all', + 'last_source' => ConsentSource::MypageRenewAll->value, 'consent_category' => $this->resolveCategory($consentKey), 'consent_count' => (int) $consent->consent_count + 1, ]; - HookManager::doAction(self::PLUGIN_ID . '.consent.before_update', $userId, $consentKey, $data); - $data = HookManager::applyFilters(self::PLUGIN_ID . '.consent.filter_update_data', $data, $userId, $consentKey); + HookManager::doAction(self::PLUGIN_ID.'.consent.before_update', $userId, $consentKey, $data); + $data = HookManager::applyFilters(self::PLUGIN_ID.'.consent.filter_update_data', $data, $userId, $consentKey); $updated = $this->statusRepository->upsert($userId, $consentKey, $data); @@ -407,14 +408,14 @@ class GdprConsentService 'session_id' => null, 'consent_key' => $consentKey, 'action' => 'granted', - 'source' => 'mypage_renew_all', + 'source' => ConsentSource::MypageRenewAll->value, 'policy_version' => $current, 'categories' => $this->buildCategoriesSnapshotForUser($userId), 'ip_address' => request()->ip(), 'user_agent' => substr((string) request()->userAgent(), 0, 500), ]); - HookManager::doAction(self::PLUGIN_ID . '.consent.granted', $updated, 'mypage_renew_all'); + HookManager::doAction(self::PLUGIN_ID.'.consent.granted', $updated, ConsentSource::MypageRenewAll->value); $renewed++; } @@ -429,8 +430,8 @@ class GdprConsentService * 회원: status 테이블에서 cookie_ 접두사 동의 중 정책 버전 일치하는 항목이 1개 이상이면 동의 완료. * 게스트: history 의 가장 최근 항목 중 정책 버전 일치하는 cookie_ 접두사 동의가 1개 이상이면 동의 완료. * - * @param int|null $userId 회원 ID (게스트면 NULL) - * @param string|null $sessionId 게스트 세션 ID (회원이면 NULL) + * @param int|null $userId 회원 ID (게스트면 NULL) + * @param string|null $sessionId 게스트 세션 ID (회원이면 NULL) * @return bool 현재 정책 버전으로 동의 완료 여부 */ public function hasCurrentCookieConsent(?int $userId, ?string $sessionId): bool @@ -474,8 +475,8 @@ class GdprConsentService * 회원: status 테이블에서 cookie_ 접두사 1건 이상 → true * 게스트: history 테이블에서 session_id + cookie_ 접두사 1건 이상 → true * - * @param int|null $userId 회원 ID (게스트면 NULL) - * @param string|null $sessionId 게스트 세션 ID (회원이면 NULL) + * @param int|null $userId 회원 ID (게스트면 NULL) + * @param string|null $sessionId 게스트 세션 ID (회원이면 NULL) * @return bool 동의 이력 존재 여부 */ public function hasAnyConsentHistory(?int $userId, ?string $sessionId): bool @@ -515,8 +516,8 @@ class GdprConsentService * 회원: status 테이블의 cookie_ 접두사 + 정책 버전 일치 행을 카테고리별로 매핑. * 게스트: history 의 정책 버전 일치 cookie_ 접두사 항목 중 가장 최근 action 을 사용. * - * @param int|null $userId 회원 ID (게스트면 NULL) - * @param string|null $sessionId 게스트 세션 ID (회원이면 NULL) + * @param int|null $userId 회원 ID (게스트면 NULL) + * @param string|null $sessionId 게스트 세션 ID (회원이면 NULL) * @return array 카테고리 키 → 동의 여부 (예: ['necessary' => true, 'analytics' => false]) */ public function getCurrentCookieConsents(?int $userId, ?string $sessionId): array @@ -571,7 +572,7 @@ class GdprConsentService /** * consent_key로부터 category 분류를 추정합니다. * - * @param string $consentKey 동의 항목 키 + * @param string $consentKey 동의 항목 키 * @return string|null */ private function resolveCategory(string $consentKey): ?string @@ -592,7 +593,7 @@ class GdprConsentService * * 동의 row 가 한 건도 없는 회원은 null 반환 — history.categories null 과 동일 의미. * - * @param int $userId 회원 ID + * @param int $userId 회원 ID * @return array|null 카테고리 키 → 동의 여부 매트릭스 */ private function buildCategoriesSnapshotForUser(int $userId): ?array diff --git a/plugins/_bundled/sirsoft-gdpr/tests/Unit/ConsentSourceVocabularyParityTest.php b/plugins/_bundled/sirsoft-gdpr/tests/Unit/ConsentSourceVocabularyParityTest.php new file mode 100644 index 00000000..6c3ab263 --- /dev/null +++ b/plugins/_bundled/sirsoft-gdpr/tests/Unit/ConsentSourceVocabularyParityTest.php @@ -0,0 +1,182 @@ +/'last_source' => 인자로 넘어가는 리터럴 + $sources = [ + 'src/Listeners/GdprAuthConsentListener.php', + 'src/Services/GdprConsentService.php', + 'src/Http/Controllers/User/GdprConsentController.php', + ]; + + $literals = []; + foreach ($sources as $relative) { + $code = file_get_contents($this->pluginPath($relative)); + + // 기록 지점이 enum 을 참조하고 있어야 한다 — 리터럴로 되돌아가면 화면·라벨이 그 값을 모른다 + $this->assertStringContainsString( + 'ConsentSource::', + $code, + "{$relative} 이 출처를 enum 이 아닌 값으로 기록합니다." + ); + + preg_match_all("/(?:source:|'source'\s*=>|'last_source'\s*=>)\s*'([a-z_]+)'/", $code, $m); + $literals = array_merge($literals, $m[1]); + } + + foreach (array_unique($literals) as $literal) { + $this->assertContains( + $literal, + $declared, + "기록 경로가 enum 에 없는 출처 '{$literal}' 를 저장합니다 — ConsentSource 에 케이스를 추가하세요." + ); + } + } + + /** + * 관리자 동의 이력 화면의 출처 필터 옵션이 enum 전 케이스를 덮는지 검사합니다. + */ + #[Test] + public function 화면_출처_필터_옵션이_enum_전_케이스를_덮는다(): void + { + $layout = file_get_contents( + $this->pluginPath('resources/layouts/admin/gdpr_consent_log.json') + ); + + foreach (ConsentSource::allValues() as $value) { + $this->assertStringContainsString( + "includes('{$value}')", + $layout, + "출처 필터에 '{$value}' 체크박스가 없습니다 — 그 출처로 기록된 행은 어떤 필터로도 도달할 수 없습니다." + ); + $this->assertStringContainsString( + "consent_log.source.{$value}", + $layout, + "출처 필터 '{$value}' 의 라벨 바인딩이 없습니다." + ); + } + } + + /** + * 모든 출처 값에 ko/en 라벨이 정의되어 있는지 검사합니다. + * + * 백엔드(`lang/{locale}/messages.php`)와 프론트엔드(`resources/lang/{locale}.json`)를 + * **둘 다** 봅니다. 관리자 화면의 `$t:` 는 프론트엔드 JSON 에서 해석되므로 PHP 쪽만 + * 채우면 화면에는 여전히 원시 키가 남습니다 (#492 7차에서 실제로 겪은 누락). + */ + #[Test] + public function 모든_출처_값에_ko_en_라벨이_정의되어_있다(): void + { + foreach (['ko', 'en'] as $locale) { + $backend = require $this->pluginPath("lang/{$locale}/messages.php"); + $frontend = json_decode( + file_get_contents($this->pluginPath("resources/lang/{$locale}.json")), + true + ); + + $surfaces = [ + "lang/{$locale}/messages.php" => $backend['admin']['consent_log']['source'] ?? [], + "resources/lang/{$locale}.json" => $frontend['admin']['consent_log']['source'] ?? [], + ]; + + foreach ($surfaces as $where => $labels) { + foreach (ConsentSource::allValues() as $value) { + $this->assertArrayHasKey( + $value, + $labels, + "{$where} 에 출처 '{$value}' 라벨이 없습니다 — 목록 셀에 원시 키가 노출됩니다." + ); + $this->assertNotSame('', trim((string) $labels[$value])); + } + } + } + } + + /** + * 정책 버전 이력 페이저가 쓰는 다국어 키가 양쪽 표면에 모두 정의되어 있는지 검사합니다. + */ + #[Test] + public function 정책_버전_이력_페이저_라벨이_양쪽_표면에_있다(): void + { + $keys = ['history_page_summary', 'history_prev', 'history_next']; + + foreach (['ko', 'en'] as $locale) { + $backend = require $this->pluginPath("lang/{$locale}/messages.php"); + $frontend = json_decode( + file_get_contents($this->pluginPath("resources/lang/{$locale}.json")), + true + ); + + $surfaces = [ + "lang/{$locale}/messages.php" => $backend['settings']['policy_version'] ?? [], + "resources/lang/{$locale}.json" => $frontend['settings']['policy_version'] ?? [], + ]; + + foreach ($surfaces as $where => $labels) { + foreach ($keys as $key) { + $this->assertArrayHasKey($key, $labels, "{$where} 에 '{$key}' 가 없습니다."); + } + } + + // 치환 문법이 표면마다 다르다 — 백엔드(Laravel)는 `:name`, 프론트 엔진은 `{name}` 만 치환한다. + // 뒤바꿔 적으면 키는 해석되지만 숫자가 자리표시자 그대로 화면에 남는다. + foreach (['total', 'current', 'last'] as $param) { + $this->assertStringContainsString( + ':'.$param, + (string) ($backend['settings']['policy_version']['history_page_summary'] ?? ''), + "lang/{$locale}/messages.php 는 Laravel 문법 ':{$param}' 을 써야 합니다." + ); + $this->assertStringContainsString( + '{'.$param.'}', + (string) ($frontend['settings']['policy_version']['history_page_summary'] ?? ''), + "resources/lang/{$locale}.json 은 프론트 엔진 문법 '{{$param}}' 을 써야 합니다 — ':{$param}' 는 치환되지 않고 그대로 노출됩니다." + ); + } + } + } + + /** + * 공개 요청으로 지정 가능한 출처는 서버 자체 기록 경로를 포함하지 않아야 합니다. + */ + #[Test] + public function 서버_자체_기록_출처는_요청으로_지정할_수_없다(): void + { + $selectable = ConsentSource::requestSelectableValues(); + + $this->assertNotContains(ConsentSource::MypageRenewAll->value, $selectable); + $this->assertContains(ConsentSource::Banner->value, $selectable); + } +} diff --git a/plugins/_bundled/sirsoft-verification_kginicis/CHANGELOG.md b/plugins/_bundled/sirsoft-verification_kginicis/CHANGELOG.md index 73e6708a..7a130a03 100644 --- a/plugins/_bundled/sirsoft-verification_kginicis/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-verification_kginicis/CHANGELOG.md @@ -4,12 +4,13 @@ All notable changes to this plugin will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## [1.0.2] - 2026-07-29 +## [1.0.2] - 2026-08-01 ### Changed - 코어 최소 요구 버전을 7.0.6 으로 상향했습니다. 운영 모드 전환 시 라이브 자격증명을 필수로 검사하는 기능이 코어 7.0.6 의 설정 저장 검증 표면을 사용합니다. 그보다 낮은 코어에서는 이 검사가 동작하지 않아 라이브 모드를 빈 자격증명으로 저장할 수 있으므로, 플러그인을 업데이트하기 전에 코어를 7.0.6 이상으로 먼저 업데이트해 주세요. - 설정 화면의 중복 가입 관련 항목 이름을 「중복체크 기준」·「중복 가입 차단」으로 통일했습니다. 같은 화면에 다른 본인확인 수단의 카드가 함께 놓일 때 용어가 갈리지 않도록 맞췄습니다. +- 본인인증 팝업의 결과 전달 페이지가 주소로 전달받은 값의 형태를 먼저 확인하도록 정비했습니다. 정상 인증 흐름은 그대로이며, 주소를 임의로 조작해 들어오는 요청이 결과 페이지 안으로 흘러들지 않습니다. ### Fixed diff --git a/plugins/_bundled/sirsoft-verification_kginicis/docs/api/identity.md b/plugins/_bundled/sirsoft-verification_kginicis/docs/api/identity.md index f8e430e0..8769231f 100644 --- a/plugins/_bundled/sirsoft-verification_kginicis/docs/api/identity.md +++ b/plugins/_bundled/sirsoft-verification_kginicis/docs/api/identity.md @@ -105,3 +105,46 @@ Authorization: Bearer {YOUR_TOKEN} ``` + +--- + +## PG 연동 web 엔드포인트 (docgen 수집 범위 밖) + +아래 두 경로는 `src/routes/web.php` 에 선언된 브라우저 왕복 경로라 `api:docgen` 의 라우트 수집 +대상(`/api/plugins/...`)에 포함되지 않는다. JSON API 가 아니므로 응답 필드 표 대신 요청 계약과 +분기 동작만 기재한다. + +### POST /plugin/inicis/callback + +- **라우트명**: `plugin.verification_kginicis.callback` +- **호출자**: KG이니시스 (매뉴얼 STEP2 — 외부 form POST) +- **미들웨어**: `web` (CSRF 검증은 `ValidateCsrfToken` 제외 — 외부 호출이라 토큰이 없다) +- **FormRequest**: `InicisCallbackRequest` — **검증 규칙 없음(의도된 결정)** + +이니시스는 매뉴얼에 명시되지 않은 필드를 가맹점 설정·인증 수단에 따라 함께 보낸다. 요청 +파라미터를 화이트리스트로 닫으면 정상 콜백이 422 로 거부되어 인증이 끊기므로, 이 엔드포인트는 +본문을 그대로 통과시키고 의미 검증(서명 대조·거래 매칭·challenge 해석)은 전적으로 +`InicisCallbackResolver` 가 담당한다. 빈 규칙의 FormRequest 를 두는 이유는 "검증하지 않는다" 가 +누락이 아니라 결정임을 코드에 남기기 위해서다. + +**응답**: 항상 `302` — `/plugin/inicis/popup-bridge` 로 결과 query 를 붙여 redirect 한다. + +### GET /plugin/inicis/popup-bridge + +- **라우트명**: `plugin.verification_kginicis.popup-bridge` +- **호출자**: 사용자 브라우저 (callback 이 redirect) +- **FormRequest**: `InicisPopupBridgeRequest` + +**요청 파라미터** + +| 이름 | 타입 | 필수 | 설명 | +| --- | --- | --- | --- | +| verification_token | string | 아니오 | 인증 성공 시 코어가 발급한 검증 토큰 | +| challenge_id | string | 아니오 | 인증 시도 식별자 | +| identity_error | string | 아니오 | 인증 실패 사유 키 | + +길이 상한을 두지 않는다 — 토큰 길이는 코어 발급 정책에 종속되므로, 여기서 상한을 정하면 정책이 +바뀔 때 사용자가 팝업 안에서 오류 화면을 보게 된다. + +**응답**: `200 text/html` — 데스크톱(`window.opener` 존재)은 부모창에 `postMessage` 후 창을 닫고, +모바일은 `sessionStorage` 의 redirectStash 를 복원해 원래 페이지로 이동한다. diff --git a/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Controllers/InicisCallbackController.php b/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Controllers/InicisCallbackController.php index 50dfc946..ed252531 100644 --- a/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Controllers/InicisCallbackController.php +++ b/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Controllers/InicisCallbackController.php @@ -4,7 +4,7 @@ namespace Plugins\Sirsoft\VerificationKginicis\Http\Controllers; use App\Http\Controllers\Api\Base\PublicBaseController; use Illuminate\Http\RedirectResponse; -use Illuminate\Http\Request; +use Plugins\Sirsoft\VerificationKginicis\Http\Requests\InicisCallbackRequest; use Plugins\Sirsoft\VerificationKginicis\Services\InicisCallbackResolver; /** @@ -15,8 +15,9 @@ use Plugins\Sirsoft\VerificationKginicis\Services\InicisCallbackResolver; * 본 컨트롤러는 InicisCallbackResolver Service 에 비즈니스 로직을 위임하고, 결과를 * popup-bridge 페이지로 302 redirect 한다. * - * FormRequest 미사용 사유: 외부 PG 가 보내는 임의 필드를 strict validation 으로 차단하면 - * 이니시스 응답을 받을 수 없다. 본 컨트롤러는 raw form POST 를 그대로 Resolver 에 전달. + * 검증 규칙을 두지 않는 사유: 외부 PG 가 보내는 임의 필드를 strict validation 으로 차단하면 + * 이니시스 응답을 받을 수 없다. 본 컨트롤러는 raw form POST 를 그대로 Resolver 에 전달하며, + * 그 결정을 InicisCallbackRequest (빈 규칙) 로 명시한다. * * @since 1.0.0-beta.1 */ @@ -34,10 +35,10 @@ class InicisCallbackController extends PublicBaseController /** * 이니시스 콜백 수신 + Resolver 위임 + bridge 페이지로 302 redirect. * - * @param Request $request 이니시스 form POST body + * @param InicisCallbackRequest $request 이니시스 form POST body * @return RedirectResponse */ - public function handle(Request $request): RedirectResponse + public function handle(InicisCallbackRequest $request): RedirectResponse { $outcome = $this->resolver->resolve( callbackInput: $request->all(), diff --git a/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Controllers/InicisPopupBridgeController.php b/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Controllers/InicisPopupBridgeController.php index 6de105b7..da29059f 100644 --- a/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Controllers/InicisPopupBridgeController.php +++ b/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Controllers/InicisPopupBridgeController.php @@ -3,8 +3,8 @@ namespace Plugins\Sirsoft\VerificationKginicis\Http\Controllers; use App\Http\Controllers\Api\Base\PublicBaseController; -use Illuminate\Http\Request; use Illuminate\Http\Response; +use Plugins\Sirsoft\VerificationKginicis\Http\Requests\InicisPopupBridgeRequest; /** * 이니시스 매뉴얼 STEP4 결과 전달 페이지 — 데스크톱 팝업 / 모바일 redirect 분기. @@ -30,16 +30,15 @@ class InicisPopupBridgeController extends PublicBaseController /** * Bridge 페이지를 렌더링한다. * - * @param Request $request callback 컨트롤러가 전달한 query (verification_token / challenge_id / identity_error) + * @param InicisPopupBridgeRequest $request callback 컨트롤러가 전달한 query (verification_token / challenge_id / identity_error) * @return Response */ - public function show(Request $request): Response + public function show(InicisPopupBridgeRequest $request): Response { - $payloadJson = json_encode([ - 'verification_token' => (string) $request->query('verification_token', ''), - 'challenge_id' => (string) $request->query('challenge_id', ''), - 'identity_error' => (string) $request->query('identity_error', ''), - ], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE); + $payloadJson = json_encode( + $request->bridgePayload(), + JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE + ); return $this->htmlResponse($this->renderBridgeHtml($payloadJson)); } diff --git a/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Requests/InicisCallbackRequest.php b/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Requests/InicisCallbackRequest.php new file mode 100644 index 00000000..2cf6fcd0 --- /dev/null +++ b/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Requests/InicisCallbackRequest.php @@ -0,0 +1,39 @@ +> 빈 규칙 (사유는 클래스 설명 참조) + */ + public function rules(): array + { + return []; + } +} diff --git a/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Requests/InicisPopupBridgeRequest.php b/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Requests/InicisPopupBridgeRequest.php new file mode 100644 index 00000000..952e8de6 --- /dev/null +++ b/plugins/_bundled/sirsoft-verification_kginicis/src/Http/Requests/InicisPopupBridgeRequest.php @@ -0,0 +1,53 @@ +> 검증 규칙 + */ + public function rules(): array + { + return [ + 'verification_token' => ['nullable', 'string'], + 'challenge_id' => ['nullable', 'string'], + 'identity_error' => ['nullable', 'string'], + ]; + } + + /** + * bridge 스크립트에 전달할 payload 를 반환합니다. + * + * @return array 세 값 (미지정은 빈 문자열) + */ + public function bridgePayload(): array + { + return [ + 'verification_token' => (string) ($this->validated('verification_token') ?? ''), + 'challenge_id' => (string) ($this->validated('challenge_id') ?? ''), + 'identity_error' => (string) ($this->validated('identity_error') ?? ''), + ]; + } +} diff --git a/public/build/core/template-engine.min.js b/public/build/core/template-engine.min.js index e5a9507f..d9287905 100644 --- a/public/build/core/template-engine.min.js +++ b/public/build/core/template-engine.min.js @@ -1,20 +1,20 @@ -var gT=Object.defineProperty;var pT=(Xt,Ya,Xa)=>Ya in Xt?gT(Xt,Ya,{enumerable:!0,configurable:!0,writable:!0,value:Xa}):Xt[Ya]=Xa;var $=(Xt,Ya,Xa)=>pT(Xt,typeof Ya!="symbol"?Ya+"":Ya,Xa);(function(Xt){"use strict";function Ya(o,e){for(var n=0;nr[i]})}}}return Object.freeze(Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}))}function Xa(o){return o&&o.__esModule&&Object.prototype.hasOwnProperty.call(o,"default")?o.default:o}var Yu={exports:{}},We={};var Ig;function zS(){if(Ig)return We;Ig=1;var o=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),n=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),u=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),b=Symbol.for("react.activity"),v=Symbol.iterator;function S(A){return A===null||typeof A!="object"?null:(A=v&&A[v]||A["@@iterator"],typeof A=="function"?A:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},x=Object.assign,k={};function O(A,K,ue){this.props=A,this.context=K,this.refs=k,this.updater=ue||_}O.prototype.isReactComponent={},O.prototype.setState=function(A,K){if(typeof A!="object"&&typeof A!="function"&&A!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,A,K,"setState")},O.prototype.forceUpdate=function(A){this.updater.enqueueForceUpdate(this,A,"forceUpdate")};function M(){}M.prototype=O.prototype;function T(A,K,ue){this.props=A,this.context=K,this.refs=k,this.updater=ue||_}var D=T.prototype=new M;D.constructor=T,x(D,O.prototype),D.isPureReactComponent=!0;var j=Array.isArray;function H(){}var P={H:null,A:null,T:null,S:null},X=Object.prototype.hasOwnProperty;function pe(A,K,ue){var he=ue.ref;return{$$typeof:o,type:A,key:K,ref:he!==void 0?he:null,props:ue}}function _e(A,K){return pe(A.type,K,A.props)}function we(A){return typeof A=="object"&&A!==null&&A.$$typeof===o}function Me(A){var K={"=":"=0",":":"=2"};return"$"+A.replace(/[=:]/g,function(ue){return K[ue]})}var qe=/\/+/g;function Je(A,K){return typeof A=="object"&&A!==null&&A.key!=null?Me(""+A.key):K.toString(36)}function gt(A){switch(A.status){case"fulfilled":return A.value;case"rejected":throw A.reason;default:switch(typeof A.status=="string"?A.then(H,H):(A.status="pending",A.then(function(K){A.status==="pending"&&(A.status="fulfilled",A.value=K)},function(K){A.status==="pending"&&(A.status="rejected",A.reason=K)})),A.status){case"fulfilled":return A.value;case"rejected":throw A.reason}}throw A}function Q(A,K,ue,he,le){var de=typeof A;(de==="undefined"||de==="boolean")&&(A=null);var Oe=!1;if(A===null)Oe=!0;else switch(de){case"bigint":case"string":case"number":Oe=!0;break;case"object":switch(A.$$typeof){case o:case e:Oe=!0;break;case m:return Oe=A._init,Q(Oe(A._payload),K,ue,he,le)}}if(Oe)return le=le(A),Oe=he===""?"."+Je(A,0):he,j(le)?(ue="",Oe!=null&&(ue=Oe.replace(qe,"$&/")+"/"),Q(le,K,ue,"",function(ke){return ke})):le!=null&&(we(le)&&(le=_e(le,ue+(le.key==null||A&&A.key===le.key?"":(""+le.key).replace(qe,"$&/")+"/")+Oe)),K.push(le)),1;Oe=0;var He=he===""?".":he+":";if(j(A))for(var Pe=0;Pe>>1,ge=Q[ae];if(0>>1;aei(ue,ve))hei(le,ue)?(Q[ae]=le,Q[he]=ve,ae=he):(Q[ae]=ue,Q[K]=ve,ae=K);else if(hei(le,ve))Q[ae]=le,Q[he]=ve,ae=he;else break e}}return fe}function i(Q,fe){var ve=Q.sortIndex-fe.sortIndex;return ve!==0?ve:Q.id-fe.id}if(o.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var l=performance;o.unstable_now=function(){return l.now()}}else{var u=Date,d=u.now();o.unstable_now=function(){return u.now()-d}}var f=[],g=[],m=1,b=null,v=3,S=!1,_=!1,x=!1,k=!1,O=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,T=typeof setImmediate<"u"?setImmediate:null;function D(Q){for(var fe=n(g);fe!==null;){if(fe.callback===null)r(g);else if(fe.startTime<=Q)r(g),fe.sortIndex=fe.expirationTime,e(f,fe);else break;fe=n(g)}}function j(Q){if(x=!1,D(Q),!_)if(n(f)!==null)_=!0,H||(H=!0,Me());else{var fe=n(g);fe!==null&>(j,fe.startTime-Q)}}var H=!1,P=-1,X=5,pe=-1;function _e(){return k?!0:!(o.unstable_now()-peQ&&_e());){var ae=b.callback;if(typeof ae=="function"){b.callback=null,v=b.priorityLevel;var ge=ae(b.expirationTime<=Q);if(Q=o.unstable_now(),typeof ge=="function"){b.callback=ge,D(Q),fe=!0;break t}b===n(f)&&r(f),D(Q)}else r(f);b=n(f)}if(b!==null)fe=!0;else{var A=n(g);A!==null&>(j,A.startTime-Q),fe=!1}}break e}finally{b=null,v=ve,S=!1}fe=void 0}}finally{fe?Me():H=!1}}}var Me;if(typeof T=="function")Me=function(){T(we)};else if(typeof MessageChannel<"u"){var qe=new MessageChannel,Je=qe.port2;qe.port1.onmessage=we,Me=function(){Je.postMessage(null)}}else Me=function(){O(we,0)};function gt(Q,fe){P=O(function(){Q(o.unstable_now())},fe)}o.unstable_IdlePriority=5,o.unstable_ImmediatePriority=1,o.unstable_LowPriority=4,o.unstable_NormalPriority=3,o.unstable_Profiling=null,o.unstable_UserBlockingPriority=2,o.unstable_cancelCallback=function(Q){Q.callback=null},o.unstable_forceFrameRate=function(Q){0>Q||125ae?(Q.sortIndex=ve,e(g,Q),n(f)===null&&Q===n(g)&&(x?(M(P),P=-1):x=!0,gt(j,ve-ae))):(Q.sortIndex=ge,e(f,Q),_||S||(_=!0,H||(H=!0,Me()))),Q},o.unstable_shouldYield=_e,o.unstable_wrapCallback=function(Q){var fe=v;return function(){var ve=v;v=fe;try{return Q.apply(this,arguments)}finally{v=ve}}}})(Zu)),Zu}var jg;function US(){return jg||(jg=1,Qu.exports=jS()),Qu.exports}var ed={exports:{}},wn={};var Ug;function BS(){if(Ug)return wn;Ug=1;var o=Xu();function e(f){var g="https://react.dev/errors/"+f;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(e){console.error(e)}}return o(),ed.exports=BS(),ed.exports}var qg;function PS(){if(qg)return Ys;qg=1;var o=US(),e=Xu(),n=Pg();function r(t){var a="https://react.dev/errors/"+t;if(1ge||(t.current=ae[ge],ae[ge]=null,ge--)}function ue(t,a){ge++,ae[ge]=t.current,t.current=a}var he=A(null),le=A(null),de=A(null),Oe=A(null);function He(t,a){switch(ue(de,a),ue(le,t),ue(he,null),a.nodeType){case 9:case 11:t=(t=a.documentElement)&&(t=t.namespaceURI)?iS(t):0;break;default:if(t=a.tagName,a=a.namespaceURI)a=iS(a),t=sS(a,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}K(he),ue(he,t)}function Pe(){K(he),K(le),K(de)}function ke(t){t.memoizedState!==null&&ue(Oe,t);var a=he.current,s=sS(a,t.type);a!==s&&(ue(le,t),ue(he,s))}function Ot(t){le.current===t&&(K(he),K(le)),Oe.current===t&&(K(Oe),Al._currentValue=ve)}var ut,Ct;function vt(t){if(ut===void 0)try{throw Error()}catch(s){var a=s.stack.trim().match(/\n( *(at )?)/);ut=a&&a[1]||"",Ct=-1Ya in Ft?Zx(Ft,Ya,{enumerable:!0,configurable:!0,writable:!0,value:Xa}):Ft[Ya]=Xa;var $=(Ft,Ya,Xa)=>eT(Ft,typeof Ya!="symbol"?Ya+"":Ya,Xa);(function(Ft){"use strict";function Ya(o,e){for(var a=0;ar[i]})}}}return Object.freeze(Object.defineProperty(o,Symbol.toStringTag,{value:"Module"}))}function Xa(o){return o&&o.__esModule&&Object.prototype.hasOwnProperty.call(o,"default")?o.default:o}var Vu={exports:{}},Ke={};var $g;function LS(){if($g)return Ke;$g=1;var o=Symbol.for("react.transitional.element"),e=Symbol.for("react.portal"),a=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.consumer"),u=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),f=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),m=Symbol.for("react.lazy"),b=Symbol.for("react.activity"),v=Symbol.iterator;function S(x){return x===null||typeof x!="object"?null:(x=v&&x[v]||x["@@iterator"],typeof x=="function"?x:null)}var E={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},A=Object.assign,T={};function L(x,K,re){this.props=x,this.context=K,this.refs=T,this.updater=re||E}L.prototype.isReactComponent={},L.prototype.setState=function(x,K){if(typeof x!="object"&&typeof x!="function"&&x!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,x,K,"setState")},L.prototype.forceUpdate=function(x){this.updater.enqueueForceUpdate(this,x,"forceUpdate")};function M(){}M.prototype=L.prototype;function R(x,K,re){this.props=x,this.context=K,this.refs=T,this.updater=re||E}var D=R.prototype=new M;D.constructor=R,A(D,L.prototype),D.isPureReactComponent=!0;var U=Array.isArray;function j(){}var P={H:null,A:null,T:null,S:null},X=Object.prototype.hasOwnProperty;function ge(x,K,re){var de=re.ref;return{$$typeof:o,type:x,key:K,ref:de!==void 0?de:null,props:re}}function Ee(x,K){return ge(x.type,K,x.props)}function Ce(x){return typeof x=="object"&&x!==null&&x.$$typeof===o}function $e(x){var K={"=":"=0",":":"=2"};return"$"+x.replace(/[=:]/g,function(re){return K[re]})}var Pe=/\/+/g;function Ye(x,K){return typeof x=="object"&&x!==null&&x.key!=null?$e(""+x.key):K.toString(36)}function ht(x){switch(x.status){case"fulfilled":return x.value;case"rejected":throw x.reason;default:switch(typeof x.status=="string"?x.then(j,j):(x.status="pending",x.then(function(K){x.status==="pending"&&(x.status="fulfilled",x.value=K)},function(K){x.status==="pending"&&(x.status="rejected",x.reason=K)})),x.status){case"fulfilled":return x.value;case"rejected":throw x.reason}}throw x}function Q(x,K,re,de,ie){var ye=typeof x;(ye==="undefined"||ye==="boolean")&&(x=null);var Ne=!1;if(x===null)Ne=!0;else switch(ye){case"bigint":case"string":case"number":Ne=!0;break;case"object":switch(x.$$typeof){case o:case e:Ne=!0;break;case m:return Ne=x._init,Q(Ne(x._payload),K,re,de,ie)}}if(Ne)return ie=ie(x),Ne=de===""?"."+Ye(x,0):de,U(ie)?(re="",Ne!=null&&(re=Ne.replace(Pe,"$&/")+"/"),Q(ie,K,re,"",function(Ae){return Ae})):ie!=null&&(Ce(ie)&&(ie=Ee(ie,re+(ie.key==null||x&&x.key===ie.key?"":(""+ie.key).replace(Pe,"$&/")+"/")+Ne)),K.push(ie)),1;Ne=0;var Se=de===""?".":de+":";if(U(x))for(var He=0;He>>1,he=Q[te];if(0>>1;tei(re,ve))dei(ie,re)?(Q[te]=ie,Q[de]=ve,te=de):(Q[te]=re,Q[K]=ve,te=K);else if(dei(ie,ve))Q[te]=ie,Q[de]=ve,te=de;else break e}}return fe}function i(Q,fe){var ve=Q.sortIndex-fe.sortIndex;return ve!==0?ve:Q.id-fe.id}if(o.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var l=performance;o.unstable_now=function(){return l.now()}}else{var u=Date,d=u.now();o.unstable_now=function(){return u.now()-d}}var f=[],g=[],m=1,b=null,v=3,S=!1,E=!1,A=!1,T=!1,L=typeof setTimeout=="function"?setTimeout:null,M=typeof clearTimeout=="function"?clearTimeout:null,R=typeof setImmediate<"u"?setImmediate:null;function D(Q){for(var fe=a(g);fe!==null;){if(fe.callback===null)r(g);else if(fe.startTime<=Q)r(g),fe.sortIndex=fe.expirationTime,e(f,fe);else break;fe=a(g)}}function U(Q){if(A=!1,D(Q),!E)if(a(f)!==null)E=!0,j||(j=!0,$e());else{var fe=a(g);fe!==null&&ht(U,fe.startTime-Q)}}var j=!1,P=-1,X=5,ge=-1;function Ee(){return T?!0:!(o.unstable_now()-geQ&&Ee());){var te=b.callback;if(typeof te=="function"){b.callback=null,v=b.priorityLevel;var he=te(b.expirationTime<=Q);if(Q=o.unstable_now(),typeof he=="function"){b.callback=he,D(Q),fe=!0;break t}b===a(f)&&r(f),D(Q)}else r(f);b=a(f)}if(b!==null)fe=!0;else{var x=a(g);x!==null&&ht(U,x.startTime-Q),fe=!1}}break e}finally{b=null,v=ve,S=!1}fe=void 0}}finally{fe?$e():j=!1}}}var $e;if(typeof R=="function")$e=function(){R(Ce)};else if(typeof MessageChannel<"u"){var Pe=new MessageChannel,Ye=Pe.port2;Pe.port1.onmessage=Ce,$e=function(){Ye.postMessage(null)}}else $e=function(){L(Ce,0)};function ht(Q,fe){P=L(function(){Q(o.unstable_now())},fe)}o.unstable_IdlePriority=5,o.unstable_ImmediatePriority=1,o.unstable_LowPriority=4,o.unstable_NormalPriority=3,o.unstable_Profiling=null,o.unstable_UserBlockingPriority=2,o.unstable_cancelCallback=function(Q){Q.callback=null},o.unstable_forceFrameRate=function(Q){0>Q||125te?(Q.sortIndex=ve,e(g,Q),a(f)===null&&Q===a(g)&&(A?(M(P),P=-1):A=!0,ht(U,ve-te))):(Q.sortIndex=he,e(f,Q),E||S||(E=!0,j||(j=!0,$e()))),Q},o.unstable_shouldYield=Ee,o.unstable_wrapCallback=function(Q){var fe=v;return function(){var ve=v;v=fe;try{return Q.apply(this,arguments)}finally{v=ve}}}})(Yu)),Yu}var Hg;function $S(){return Hg||(Hg=1,Wu.exports=MS()),Wu.exports}var Xu={exports:{}},mn={};var zg;function NS(){if(zg)return mn;zg=1;var o=Fu();function e(f){var g="https://react.dev/errors/"+f;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(e){console.error(e)}}return o(),Xu.exports=NS(),Xu.exports}var Bg;function IS(){if(Bg)return Fs;Bg=1;var o=$S(),e=Fu(),a=Ug();function r(t){var n="https://react.dev/errors/"+t;if(1he||(t.current=te[he],te[he]=null,he--)}function re(t,n){he++,te[he]=t.current,t.current=n}var de=x(null),ie=x(null),ye=x(null),Ne=x(null);function Se(t,n){switch(re(ye,n),re(ie,t),re(de,null),n.nodeType){case 9:case 11:t=(t=n.documentElement)&&(t=t.namespaceURI)?Qv(t):0;break;default:if(t=n.tagName,n=n.namespaceURI)n=Qv(n),t=Zv(n,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}K(de),re(de,t)}function He(){K(de),K(ie),K(ye)}function Ae(t){t.memoizedState!==null&&re(Ne,t);var n=de.current,s=Zv(n,t.type);n!==s&&(re(ie,t),re(de,s))}function Et(t){ie.current===t&&(K(de),K(ie)),Ne.current===t&&(K(Ne),wl._currentValue=ve)}var kt,_t;function ft(t){if(kt===void 0)try{throw Error()}catch(s){var n=s.stack.trim().match(/\n( *(at )?)/);kt=n&&n[1]||"",_t=-1)":-1h||U[c]!==Y[h]){var ne=` -`+U[c].replace(" at new "," at ");return t.displayName&&ne.includes("")&&(ne=ne.replace("",t.displayName)),ne}while(1<=c&&0<=h);break}}}finally{kt=!1,Error.prepareStackTrace=s}return(s=t?t.displayName||t.name:"")?vt(s):""}function Nt(t,a){switch(t.tag){case 26:case 27:case 5:return vt(t.type);case 16:return vt("Lazy");case 13:return t.child!==a&&a!==null?vt("Suspense Fallback"):vt("Suspense");case 19:return vt("SuspenseList");case 0:case 15:return Ee(t.type,!1);case 11:return Ee(t.type.render,!1);case 1:return Ee(t.type,!0);case 31:return vt("Activity");default:return""}}function zt(t){try{var a="",s=null;do a+=Nt(t,s),s=t,t=t.return;while(t);return a}catch(c){return` +`+kt+t+_t}var un=!1;function gt(t,n){if(!t||un)return"";un=!0;var s=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var c={DetermineComponentFrameRoot:function(){try{if(n){var ue=function(){throw Error()};if(Object.defineProperty(ue.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ue,[])}catch(ee){var J=ee}Reflect.construct(t,[],ue)}else{try{ue.call()}catch(ee){J=ee}t.call(ue.prototype)}}else{try{throw Error()}catch(ee){J=ee}(ue=t())&&typeof ue.catch=="function"&&ue.catch(function(){})}}catch(ee){if(ee&&J&&typeof ee.stack=="string")return[ee.stack,J.stack]}return[null,null]}};c.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var h=Object.getOwnPropertyDescriptor(c.DetermineComponentFrameRoot,"name");h&&h.configurable&&Object.defineProperty(c.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var p=c.DetermineComponentFrameRoot(),w=p[0],k=p[1];if(w&&k){var z=w.split(` +`),Y=k.split(` +`);for(h=c=0;ch||z[c]!==Y[h]){var ae=` +`+z[c].replace(" at new "," at ");return t.displayName&&ae.includes("")&&(ae=ae.replace("",t.displayName)),ae}while(1<=c&&0<=h);break}}}finally{un=!1,Error.prepareStackTrace=s}return(s=t?t.displayName||t.name:"")?ft(s):""}function Ua(t,n){switch(t.tag){case 26:case 27:case 5:return ft(t.type);case 16:return ft("Lazy");case 13:return t.child!==n&&n!==null?ft("Suspense Fallback"):ft("Suspense");case 19:return ft("SuspenseList");case 0:case 15:return gt(t.type,!1);case 11:return gt(t.type.render,!1);case 1:return gt(t.type,!0);case 31:return ft("Activity");default:return""}}function kn(t){try{var n="",s=null;do n+=Ua(t,s),s=t,t=t.return;while(t);return n}catch(c){return` Error generating stack: `+c.message+` -`+c.stack}}var qt=Object.prototype.hasOwnProperty,Lt=o.unstable_scheduleCallback,_n=o.unstable_cancelCallback,hn=o.unstable_shouldYield,Ba=o.unstable_requestPaint,Jt=o.unstable_now,Dr=o.unstable_getCurrentPriorityLevel,tr=o.unstable_ImmediatePriority,An=o.unstable_UserBlockingPriority,sn=o.unstable_NormalPriority,tt=o.unstable_LowPriority,na=o.unstable_IdlePriority,kn=o.log,nr=o.unstable_setDisableYieldValue,_a=null,on=null;function Dn(t){if(typeof kn=="function"&&nr(t),on&&typeof on.setStrictMode=="function")try{on.setStrictMode(_a,t)}catch{}}var q=Math.clz32?Math.clz32:Z,ie=Math.log,oe=Math.LN2;function Z(t){return t>>>=0,t===0?32:31-(ie(t)/oe|0)|0}var be=256,me=262144,Te=4194304;function xe(t){var a=t&42;if(a!==0)return a;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function je(t,a,s){var c=t.pendingLanes;if(c===0)return 0;var h=0,p=t.suspendedLanes,C=t.pingedLanes;t=t.warmLanes;var R=c&134217727;return R!==0?(c=R&~p,c!==0?h=xe(c):(C&=R,C!==0?h=xe(C):s||(s=R&~t,s!==0&&(h=xe(s))))):(R=c&~p,R!==0?h=xe(R):C!==0?h=xe(C):s||(s=c&~t,s!==0&&(h=xe(s)))),h===0?0:a!==0&&a!==h&&(a&p)===0&&(p=h&-h,s=a&-a,p>=s||p===32&&(s&4194048)!==0)?a:h}function ze(t,a){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&a)===0}function $e(t,a){switch(t){case 1:case 2:case 4:case 8:case 64:return a+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function De(){var t=Te;return Te<<=1,(Te&62914560)===0&&(Te=4194304),t}function Ye(t){for(var a=[],s=0;31>s;s++)a.push(t);return a}function Ke(t,a){t.pendingLanes|=a,a!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function Ae(t,a,s,c,h,p){var C=t.pendingLanes;t.pendingLanes=s,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=s,t.entangledLanes&=s,t.errorRecoveryDisabledLanes&=s,t.shellSuspendCounter=0;var R=t.entanglements,U=t.expirationTimes,Y=t.hiddenUpdates;for(s=C&~s;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var Cf=/[\n"\\]/g;function Hn(t){return t.replace(Cf,function(a){return"\\"+a.charCodeAt(0).toString(16)+" "})}function ps(t,a,s,c,h,p,C,R){t.name="",C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"?t.type=C:t.removeAttribute("type"),a!=null?C==="number"?(a===0&&t.value===""||t.value!=a)&&(t.value=""+Tn(a)):t.value!==""+Tn(a)&&(t.value=""+Tn(a)):C!=="submit"&&C!=="reset"||t.removeAttribute("value"),a!=null?rr(t,C,Tn(a)):s!=null?rr(t,C,Tn(s)):c!=null&&t.removeAttribute("value"),h==null&&p!=null&&(t.defaultChecked=!!p),h!=null&&(t.checked=h&&typeof h!="function"&&typeof h!="symbol"),R!=null&&typeof R!="function"&&typeof R!="symbol"&&typeof R!="boolean"?t.name=""+Tn(R):t.removeAttribute("name")}function wi(t,a,s,c,h,p,C,R){if(p!=null&&typeof p!="function"&&typeof p!="symbol"&&typeof p!="boolean"&&(t.type=p),a!=null||s!=null){if(!(p!=="submit"&&p!=="reset"||a!=null)){$o(t);return}s=s!=null?""+Tn(s):"",a=a!=null?""+Tn(a):s,R||a===t.value||(t.value=a),t.defaultValue=a}c=c??h,c=typeof c!="function"&&typeof c!="symbol"&&!!c,t.checked=R?t.checked:!!c,t.defaultChecked=!!c,C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"&&(t.name=C),$o(t)}function rr(t,a,s){a==="number"&&Nr(t.ownerDocument)===t||t.defaultValue===""+s||(t.defaultValue=""+s)}function Ir(t,a,s,c){if(t=t.options,a){a={};for(var h=0;h"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),jo=!1;if(Ta)try{var Ei={};Object.defineProperty(Ei,"passive",{get:function(){jo=!0}}),window.addEventListener("test",Ei,Ei),window.removeEventListener("test",Ei,Ei)}catch{jo=!1}var Ga=null,Uo=null,ys=null;function Nc(){if(ys)return ys;var t,a=Uo,s=a.length,c,h="value"in Ga?Ga.value:Ga.textContent,p=h.length;for(t=0;t=te),Fe=" ",lt=!1;function pt(t,a){switch(t){case"keyup":return L.indexOf(a.keyCode)!==-1;case"keydown":return a.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function jt(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var ka=!1;function Uf(t,a){switch(t){case"compositionend":return jt(a);case"keypress":return a.which!==32?null:(lt=!0,Fe);case"textInput":return t=a.data,t===Fe&<?null:t;default:return null}}function Pc(t,a){if(ka)return t==="compositionend"||!N&&pt(t,a)?(t=Nc(),ys=Uo=Ga=null,ka=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(a.ctrlKey||a.altKey||a.metaKey)||a.ctrlKey&&a.altKey){if(a.char&&1=a)return{node:s,offset:a-t};t=c}e:{for(;s;){if(s.nextSibling){s=s.nextSibling;break e}s=s.parentNode}s=void 0}s=Ey(s)}}function Ay(t,a){return t&&a?t===a?!0:t&&t.nodeType===3?!1:a&&a.nodeType===3?Ay(t,a.parentNode):"contains"in t?t.contains(a):t.compareDocumentPosition?!!(t.compareDocumentPosition(a)&16):!1:!1}function xy(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var a=Nr(t.document);a instanceof t.HTMLIFrameElement;){try{var s=typeof a.contentWindow.location.href=="string"}catch{s=!1}if(s)t=a.contentWindow;else break;a=Nr(t.document)}return a}function Vf(t){var a=t&&t.nodeName&&t.nodeName.toLowerCase();return a&&(a==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||a==="textarea"||t.contentEditable==="true")}var KA=Ta&&"documentMode"in document&&11>=document.documentMode,ws=null,Ff=null,Xo=null,Kf=!1;function Ty(t,a,s){var c=s.window===s?s.document:s.nodeType===9?s:s.ownerDocument;Kf||ws==null||ws!==Nr(c)||(c=ws,"selectionStart"in c&&Vf(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}),Xo&&Yo(Xo,c)||(Xo=c,c=$u(Ff,"onSelect"),0>=C,h-=C,Va=1<<32-q(a)+h|s<Ze?(it=Ie,Ie=null):it=Ie.sibling;var yt=J(F,Ie,W[Ze],se);if(yt===null){Ie===null&&(Ie=it);break}t&&Ie&&yt.alternate===null&&a(F,Ie),G=p(yt,G,Ze),mt===null?Ge=yt:mt.sibling=yt,mt=yt,Ie=it}if(Ze===W.length)return s(F,Ie),ct&&lr(F,Ze),Ge;if(Ie===null){for(;ZeZe?(it=Ie,Ie=null):it=Ie.sibling;var li=J(F,Ie,yt.value,se);if(li===null){Ie===null&&(Ie=it);break}t&&Ie&&li.alternate===null&&a(F,Ie),G=p(li,G,Ze),mt===null?Ge=li:mt.sibling=li,mt=li,Ie=it}if(yt.done)return s(F,Ie),ct&&lr(F,Ze),Ge;if(Ie===null){for(;!yt.done;Ze++,yt=W.next())yt=ce(F,yt.value,se),yt!==null&&(G=p(yt,G,Ze),mt===null?Ge=yt:mt.sibling=yt,mt=yt);return ct&&lr(F,Ze),Ge}for(Ie=c(Ie);!yt.done;Ze++,yt=W.next())yt=ee(Ie,F,Ze,yt.value,se),yt!==null&&(t&&yt.alternate!==null&&Ie.delete(yt.key===null?Ze:yt.key),G=p(yt,G,Ze),mt===null?Ge=yt:mt.sibling=yt,mt=yt);return t&&Ie.forEach(function(hT){return a(F,hT)}),ct&&lr(F,Ze),Ge}function Rt(F,G,W,se){if(typeof W=="object"&&W!==null&&W.type===x&&W.key===null&&(W=W.props.children),typeof W=="object"&&W!==null){switch(W.$$typeof){case S:e:{for(var Ge=W.key;G!==null;){if(G.key===Ge){if(Ge=W.type,Ge===x){if(G.tag===7){s(F,G.sibling),se=h(G,W.props.children),se.return=F,F=se;break e}}else if(G.elementType===Ge||typeof Ge=="object"&&Ge!==null&&Ge.$$typeof===X&&ji(Ge)===G.type){s(F,G.sibling),se=h(G,W.props),nl(se,W),se.return=F,F=se;break e}s(F,G);break}else a(F,G);G=G.sibling}W.type===x?(se=$i(W.props.children,F.mode,se,W.key),se.return=F,F=se):(se=Jc(W.type,W.key,W.props,null,F.mode,se),nl(se,W),se.return=F,F=se)}return C(F);case _:e:{for(Ge=W.key;G!==null;){if(G.key===Ge)if(G.tag===4&&G.stateNode.containerInfo===W.containerInfo&&G.stateNode.implementation===W.implementation){s(F,G.sibling),se=h(G,W.children||[]),se.return=F,F=se;break e}else{s(F,G);break}else a(F,G);G=G.sibling}se=eh(W,F.mode,se),se.return=F,F=se}return C(F);case X:return W=ji(W),Rt(F,G,W,se)}if(gt(W))return Le(F,G,W,se);if(Me(W)){if(Ge=Me(W),typeof Ge!="function")throw Error(r(150));return W=Ge.call(W),Ve(F,G,W,se)}if(typeof W.then=="function")return Rt(F,G,ru(W),se);if(W.$$typeof===T)return Rt(F,G,eu(F,W),se);iu(F,W)}return typeof W=="string"&&W!==""||typeof W=="number"||typeof W=="bigint"?(W=""+W,G!==null&&G.tag===6?(s(F,G.sibling),se=h(G,W),se.return=F,F=se):(s(F,G),se=Zf(W,F.mode,se),se.return=F,F=se),C(F)):s(F,G)}return function(F,G,W,se){try{tl=0;var Ge=Rt(F,G,W,se);return Ls=null,Ge}catch(Ie){if(Ie===Os||Ie===nu)throw Ie;var mt=Wn(29,Ie,null,F.mode);return mt.lanes=se,mt.return=F,mt}}}var Bi=Xy(!0),Jy=Xy(!1),Vr=!1;function fh(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function hh(t,a){t=t.updateQueue,a.updateQueue===t&&(a.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function Fr(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function Kr(t,a,s){var c=t.updateQueue;if(c===null)return null;if(c=c.shared,(St&2)!==0){var h=c.pending;return h===null?a.next=a:(a.next=h.next,h.next=a),c.pending=a,a=Xc(t),$y(t,null,s),a}return Yc(t,c,a,s),Xc(t)}function al(t,a,s){if(a=a.updateQueue,a!==null&&(a=a.shared,(s&4194048)!==0)){var c=a.lanes;c&=t.pendingLanes,s|=c,a.lanes=s,Ue(t,s)}}function gh(t,a){var s=t.updateQueue,c=t.alternate;if(c!==null&&(c=c.updateQueue,s===c)){var h=null,p=null;if(s=s.firstBaseUpdate,s!==null){do{var C={lane:s.lane,tag:s.tag,payload:s.payload,callback:null,next:null};p===null?h=p=C:p=p.next=C,s=s.next}while(s!==null);p===null?h=p=a:p=p.next=a}else h=p=a;s={baseState:c.baseState,firstBaseUpdate:h,lastBaseUpdate:p,shared:c.shared,callbacks:c.callbacks},t.updateQueue=s;return}t=s.lastBaseUpdate,t===null?s.firstBaseUpdate=a:t.next=a,s.lastBaseUpdate=a}var ph=!1;function rl(){if(ph){var t=Ds;if(t!==null)throw t}}function il(t,a,s,c){ph=!1;var h=t.updateQueue;Vr=!1;var p=h.firstBaseUpdate,C=h.lastBaseUpdate,R=h.shared.pending;if(R!==null){h.shared.pending=null;var U=R,Y=U.next;U.next=null,C===null?p=Y:C.next=Y,C=U;var ne=t.alternate;ne!==null&&(ne=ne.updateQueue,R=ne.lastBaseUpdate,R!==C&&(R===null?ne.firstBaseUpdate=Y:R.next=Y,ne.lastBaseUpdate=U))}if(p!==null){var ce=h.baseState;C=0,ne=Y=U=null,R=p;do{var J=R.lane&-536870913,ee=J!==R.lane;if(ee?(rt&J)===J:(c&J)===J){J!==0&&J===ks&&(ph=!0),ne!==null&&(ne=ne.next={lane:0,tag:R.tag,payload:R.payload,callback:null,next:null});e:{var Le=t,Ve=R;J=a;var Rt=s;switch(Ve.tag){case 1:if(Le=Ve.payload,typeof Le=="function"){ce=Le.call(Rt,ce,J);break e}ce=Le;break e;case 3:Le.flags=Le.flags&-65537|128;case 0:if(Le=Ve.payload,J=typeof Le=="function"?Le.call(Rt,ce,J):Le,J==null)break e;ce=b({},ce,J);break e;case 2:Vr=!0}}J=R.callback,J!==null&&(t.flags|=64,ee&&(t.flags|=8192),ee=h.callbacks,ee===null?h.callbacks=[J]:ee.push(J))}else ee={lane:J,tag:R.tag,payload:R.payload,callback:R.callback,next:null},ne===null?(Y=ne=ee,U=ce):ne=ne.next=ee,C|=J;if(R=R.next,R===null){if(R=h.shared.pending,R===null)break;ee=R,R=ee.next,ee.next=null,h.lastBaseUpdate=ee,h.shared.pending=null}}while(!0);ne===null&&(U=ce),h.baseState=U,h.firstBaseUpdate=Y,h.lastBaseUpdate=ne,p===null&&(h.shared.lanes=0),Qr|=C,t.lanes=C,t.memoizedState=ce}}function Qy(t,a){if(typeof t!="function")throw Error(r(191,t));t.call(a)}function Zy(t,a){var s=t.callbacks;if(s!==null)for(t.callbacks=null,t=0;tp?p:8;var C=Q.T,R={};Q.T=R,$h(t,!1,a,s);try{var U=h(),Y=Q.S;if(Y!==null&&Y(R,U),U!==null&&typeof U=="object"&&typeof U.then=="function"){var ne=nx(U,c);ll(t,a,ne,Zn(t))}else ll(t,a,c,Zn(t))}catch(ce){ll(t,a,{then:function(){},status:"rejected",reason:ce},Zn())}finally{fe.p=p,C!==null&&R.types!==null&&(C.types=R.types),Q.T=C}}function lx(){}function Lh(t,a,s,c){if(t.tag!==5)throw Error(r(476));var h=Ob(t).queue;Db(t,h,a,ve,s===null?lx:function(){return Lb(t),s(c)})}function Ob(t){var a=t.memoizedState;if(a!==null)return a;a={memoizedState:ve,baseState:ve,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:fr,lastRenderedState:ve},next:null};var s={};return a.next={memoizedState:s,baseState:s,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:fr,lastRenderedState:s},next:null},t.memoizedState=a,t=t.alternate,t!==null&&(t.memoizedState=a),a}function Lb(t){var a=Ob(t);a.next===null&&(a=t.alternate.memoizedState),ll(t,a.next.queue,{},Zn())}function Mh(){return bn(Al)}function Mb(){return Yt().memoizedState}function $b(){return Yt().memoizedState}function cx(t){for(var a=t.return;a!==null;){switch(a.tag){case 24:case 3:var s=Zn();t=Fr(s);var c=Kr(a,t,s);c!==null&&(Pn(c,a,s),al(c,a,s)),a={cache:lh()},t.payload=a;return}a=a.return}}function ux(t,a,s){var c=Zn();s={lane:c,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null},pu(t)?Ib(a,s):(s=Jf(t,a,s,c),s!==null&&(Pn(s,t,c),Hb(s,a,c)))}function Nb(t,a,s){var c=Zn();ll(t,a,s,c)}function ll(t,a,s,c){var h={lane:c,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null};if(pu(t))Ib(a,h);else{var p=t.alternate;if(t.lanes===0&&(p===null||p.lanes===0)&&(p=a.lastRenderedReducer,p!==null))try{var C=a.lastRenderedState,R=p(C,s);if(h.hasEagerState=!0,h.eagerState=R,Kn(R,C))return Yc(t,a,h,0),Dt===null&&Wc(),!1}catch{}if(s=Jf(t,a,h,c),s!==null)return Pn(s,t,c),Hb(s,a,c),!0}return!1}function $h(t,a,s,c){if(c={lane:2,revertLane:fg(),gesture:null,action:c,hasEagerState:!1,eagerState:null,next:null},pu(t)){if(a)throw Error(r(479))}else a=Jf(t,s,c,2),a!==null&&Pn(a,t,2)}function pu(t){var a=t.alternate;return t===Qe||a!==null&&a===Qe}function Ib(t,a){$s=lu=!0;var s=t.pending;s===null?a.next=a:(a.next=s.next,s.next=a),t.pending=a}function Hb(t,a,s){if((s&4194048)!==0){var c=a.lanes;c&=t.pendingLanes,s|=c,a.lanes=s,Ue(t,s)}}var cl={readContext:bn,use:du,useCallback:Gt,useContext:Gt,useEffect:Gt,useImperativeHandle:Gt,useLayoutEffect:Gt,useInsertionEffect:Gt,useMemo:Gt,useReducer:Gt,useRef:Gt,useState:Gt,useDebugValue:Gt,useDeferredValue:Gt,useTransition:Gt,useSyncExternalStore:Gt,useId:Gt,useHostTransitionStatus:Gt,useFormState:Gt,useActionState:Gt,useOptimistic:Gt,useMemoCache:Gt,useCacheRefresh:Gt};cl.useEffectEvent=Gt;var zb={readContext:bn,use:du,useCallback:function(t,a){return On().memoizedState=[t,a===void 0?null:a],t},useContext:bn,useEffect:wb,useImperativeHandle:function(t,a,s){s=s!=null?s.concat([t]):null,hu(4194308,4,Ab.bind(null,a,t),s)},useLayoutEffect:function(t,a){return hu(4194308,4,t,a)},useInsertionEffect:function(t,a){hu(4,2,t,a)},useMemo:function(t,a){var s=On();a=a===void 0?null:a;var c=t();if(Pi){Dn(!0);try{t()}finally{Dn(!1)}}return s.memoizedState=[c,a],c},useReducer:function(t,a,s){var c=On();if(s!==void 0){var h=s(a);if(Pi){Dn(!0);try{s(a)}finally{Dn(!1)}}}else h=a;return c.memoizedState=c.baseState=h,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:h},c.queue=t,t=t.dispatch=ux.bind(null,Qe,t),[c.memoizedState,t]},useRef:function(t){var a=On();return t={current:t},a.memoizedState=t},useState:function(t){t=Th(t);var a=t.queue,s=Nb.bind(null,Qe,a);return a.dispatch=s,[t.memoizedState,s]},useDebugValue:Dh,useDeferredValue:function(t,a){var s=On();return Oh(s,t,a)},useTransition:function(){var t=Th(!1);return t=Db.bind(null,Qe,t.queue,!0,!1),On().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,a,s){var c=Qe,h=On();if(ct){if(s===void 0)throw Error(r(407));s=s()}else{if(s=a(),Dt===null)throw Error(r(349));(rt&127)!==0||ib(c,a,s)}h.memoizedState=s;var p={value:s,getSnapshot:a};return h.queue=p,wb(ob.bind(null,c,p,t),[t]),c.flags|=2048,Is(9,{destroy:void 0},sb.bind(null,c,p,s,a),null),s},useId:function(){var t=On(),a=Dt.identifierPrefix;if(ct){var s=Fa,c=Va;s=(c&~(1<<32-q(c)-1)).toString(32)+s,a="_"+a+"R_"+s,s=cu++,0<\/script>",p=p.removeChild(p.firstChild);break;case"select":p=typeof c.is=="string"?C.createElement("select",{is:c.is}):C.createElement("select"),c.multiple?p.multiple=!0:c.size&&(p.size=c.size);break;default:p=typeof c.is=="string"?C.createElement(h,{is:c.is}):C.createElement(h)}}p[ln]=a,p[xn]=c;e:for(C=a.child;C!==null;){if(C.tag===5||C.tag===6)p.appendChild(C.stateNode);else if(C.tag!==4&&C.tag!==27&&C.child!==null){C.child.return=C,C=C.child;continue}if(C===a)break e;for(;C.sibling===null;){if(C.return===null||C.return===a)break e;C=C.return}C.sibling.return=C.return,C=C.sibling}a.stateNode=p;e:switch(Sn(p,h,c),h){case"button":case"input":case"select":case"textarea":c=!!c.autoFocus;break e;case"img":c=!0;break e;default:c=!1}c&&gr(a)}}return $t(a),Wh(a,a.type,t===null?null:t.memoizedProps,a.pendingProps,s),null;case 6:if(t&&a.stateNode!=null)t.memoizedProps!==c&&gr(a);else{if(typeof c!="string"&&a.stateNode===null)throw Error(r(166));if(t=de.current,Ts(a)){if(t=a.stateNode,s=a.memoizedProps,c=null,h=yn,h!==null)switch(h.tag){case 27:case 5:c=h.memoizedProps}t[ln]=a,t=!!(t.nodeValue===s||c!==null&&c.suppressHydrationWarning===!0||aS(t.nodeValue,s)),t||qr(a,!0)}else t=Nu(t).createTextNode(c),t[ln]=a,a.stateNode=t}return $t(a),null;case 31:if(s=a.memoizedState,t===null||t.memoizedState!==null){if(c=Ts(a),s!==null){if(t===null){if(!c)throw Error(r(318));if(t=a.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[ln]=a}else Ni(),(a.flags&128)===0&&(a.memoizedState=null),a.flags|=4;$t(a),t=!1}else s=rh(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=s),t=!0;if(!t)return a.flags&256?(Xn(a),a):(Xn(a),null);if((a.flags&128)!==0)throw Error(r(558))}return $t(a),null;case 13:if(c=a.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(h=Ts(a),c!==null&&c.dehydrated!==null){if(t===null){if(!h)throw Error(r(318));if(h=a.memoizedState,h=h!==null?h.dehydrated:null,!h)throw Error(r(317));h[ln]=a}else Ni(),(a.flags&128)===0&&(a.memoizedState=null),a.flags|=4;$t(a),h=!1}else h=rh(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=h),h=!0;if(!h)return a.flags&256?(Xn(a),a):(Xn(a),null)}return Xn(a),(a.flags&128)!==0?(a.lanes=s,a):(s=c!==null,t=t!==null&&t.memoizedState!==null,s&&(c=a.child,h=null,c.alternate!==null&&c.alternate.memoizedState!==null&&c.alternate.memoizedState.cachePool!==null&&(h=c.alternate.memoizedState.cachePool.pool),p=null,c.memoizedState!==null&&c.memoizedState.cachePool!==null&&(p=c.memoizedState.cachePool.pool),p!==h&&(c.flags|=2048)),s!==t&&s&&(a.child.flags|=8192),Su(a,a.updateQueue),$t(a),null);case 4:return Pe(),t===null&&mg(a.stateNode.containerInfo),$t(a),null;case 10:return ur(a.type),$t(a),null;case 19:if(K(Wt),c=a.memoizedState,c===null)return $t(a),null;if(h=(a.flags&128)!==0,p=c.rendering,p===null)if(h)dl(c,!1);else{if(Vt!==0||t!==null&&(t.flags&128)!==0)for(t=a.child;t!==null;){if(p=ou(t),p!==null){for(a.flags|=128,dl(c,!1),t=p.updateQueue,a.updateQueue=t,Su(a,t),a.subtreeFlags=0,t=s,s=a.child;s!==null;)Ny(s,t),s=s.sibling;return ue(Wt,Wt.current&1|2),ct&&lr(a,c.treeForkCount),a.child}t=t.sibling}c.tail!==null&&Jt()>Au&&(a.flags|=128,h=!0,dl(c,!1),a.lanes=4194304)}else{if(!h)if(t=ou(p),t!==null){if(a.flags|=128,h=!0,t=t.updateQueue,a.updateQueue=t,Su(a,t),dl(c,!0),c.tail===null&&c.tailMode==="hidden"&&!p.alternate&&!ct)return $t(a),null}else 2*Jt()-c.renderingStartTime>Au&&s!==536870912&&(a.flags|=128,h=!0,dl(c,!1),a.lanes=4194304);c.isBackwards?(p.sibling=a.child,a.child=p):(t=c.last,t!==null?t.sibling=p:a.child=p,c.last=p)}return c.tail!==null?(t=c.tail,c.rendering=t,c.tail=t.sibling,c.renderingStartTime=Jt(),t.sibling=null,s=Wt.current,ue(Wt,h?s&1|2:s&1),ct&&lr(a,c.treeForkCount),t):($t(a),null);case 22:case 23:return Xn(a),yh(),c=a.memoizedState!==null,t!==null?t.memoizedState!==null!==c&&(a.flags|=8192):c&&(a.flags|=8192),c?(s&536870912)!==0&&(a.flags&128)===0&&($t(a),a.subtreeFlags&6&&(a.flags|=8192)):$t(a),s=a.updateQueue,s!==null&&Su(a,s.retryQueue),s=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(s=t.memoizedState.cachePool.pool),c=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(c=a.memoizedState.cachePool.pool),c!==s&&(a.flags|=2048),t!==null&&K(zi),null;case 24:return s=null,t!==null&&(s=t.memoizedState.cache),a.memoizedState.cache!==s&&(a.flags|=2048),ur(en),$t(a),null;case 25:return null;case 30:return null}throw Error(r(156,a.tag))}function px(t,a){switch(nh(a),a.tag){case 1:return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 3:return ur(en),Pe(),t=a.flags,(t&65536)!==0&&(t&128)===0?(a.flags=t&-65537|128,a):null;case 26:case 27:case 5:return Ot(a),null;case 31:if(a.memoizedState!==null){if(Xn(a),a.alternate===null)throw Error(r(340));Ni()}return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 13:if(Xn(a),t=a.memoizedState,t!==null&&t.dehydrated!==null){if(a.alternate===null)throw Error(r(340));Ni()}return t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 19:return K(Wt),null;case 4:return Pe(),null;case 10:return ur(a.type),null;case 22:case 23:return Xn(a),yh(),t!==null&&K(zi),t=a.flags,t&65536?(a.flags=t&-65537|128,a):null;case 24:return ur(en),null;case 25:return null;default:return null}}function lv(t,a){switch(nh(a),a.tag){case 3:ur(en),Pe();break;case 26:case 27:case 5:Ot(a);break;case 4:Pe();break;case 31:a.memoizedState!==null&&Xn(a);break;case 13:Xn(a);break;case 19:K(Wt);break;case 10:ur(a.type);break;case 22:case 23:Xn(a),yh(),t!==null&&K(zi);break;case 24:ur(en)}}function fl(t,a){try{var s=a.updateQueue,c=s!==null?s.lastEffect:null;if(c!==null){var h=c.next;s=h;do{if((s.tag&t)===t){c=void 0;var p=s.create,C=s.inst;c=p(),C.destroy=c}s=s.next}while(s!==h)}}catch(R){_t(a,a.return,R)}}function Xr(t,a,s){try{var c=a.updateQueue,h=c!==null?c.lastEffect:null;if(h!==null){var p=h.next;c=p;do{if((c.tag&t)===t){var C=c.inst,R=C.destroy;if(R!==void 0){C.destroy=void 0,h=a;var U=s,Y=R;try{Y()}catch(ne){_t(h,U,ne)}}}c=c.next}while(c!==p)}}catch(ne){_t(a,a.return,ne)}}function cv(t){var a=t.updateQueue;if(a!==null){var s=t.stateNode;try{Zy(a,s)}catch(c){_t(t,t.return,c)}}}function uv(t,a,s){s.props=qi(t.type,t.memoizedProps),s.state=t.memoizedState;try{s.componentWillUnmount()}catch(c){_t(t,a,c)}}function hl(t,a){try{var s=t.ref;if(s!==null){switch(t.tag){case 26:case 27:case 5:var c=t.stateNode;break;case 30:c=t.stateNode;break;default:c=t.stateNode}typeof s=="function"?t.refCleanup=s(c):s.current=c}}catch(h){_t(t,a,h)}}function Ka(t,a){var s=t.ref,c=t.refCleanup;if(s!==null)if(typeof c=="function")try{c()}catch(h){_t(t,a,h)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof s=="function")try{s(null)}catch(h){_t(t,a,h)}else s.current=null}function dv(t){var a=t.type,s=t.memoizedProps,c=t.stateNode;try{e:switch(a){case"button":case"input":case"select":case"textarea":s.autoFocus&&c.focus();break e;case"img":s.src?c.src=s.src:s.srcSet&&(c.srcset=s.srcSet)}}catch(h){_t(t,t.return,h)}}function Yh(t,a,s){try{var c=t.stateNode;Hx(c,t.type,s,a),c[xn]=a}catch(h){_t(t,t.return,h)}}function fv(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&ai(t.type)||t.tag===4}function Xh(t){e:for(;;){for(;t.sibling===null;){if(t.return===null||fv(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&ai(t.type)||t.flags&2||t.child===null||t.tag===4)continue e;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function Jh(t,a,s){var c=t.tag;if(c===5||c===6)t=t.stateNode,a?(s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s).insertBefore(t,a):(a=s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s,a.appendChild(t),s=s._reactRootContainer,s!=null||a.onclick!==null||(a.onclick=xa));else if(c!==4&&(c===27&&ai(t.type)&&(s=t.stateNode,a=null),t=t.child,t!==null))for(Jh(t,a,s),t=t.sibling;t!==null;)Jh(t,a,s),t=t.sibling}function wu(t,a,s){var c=t.tag;if(c===5||c===6)t=t.stateNode,a?s.insertBefore(t,a):s.appendChild(t);else if(c!==4&&(c===27&&ai(t.type)&&(s=t.stateNode),t=t.child,t!==null))for(wu(t,a,s),t=t.sibling;t!==null;)wu(t,a,s),t=t.sibling}function hv(t){var a=t.stateNode,s=t.memoizedProps;try{for(var c=t.type,h=a.attributes;h.length;)a.removeAttributeNode(h[0]);Sn(a,c,s),a[ln]=t,a[xn]=s}catch(p){_t(t,t.return,p)}}var pr=!1,an=!1,Qh=!1,gv=typeof WeakSet=="function"?WeakSet:Set,gn=null;function mx(t,a){if(t=t.containerInfo,vg=Pu,t=xy(t),Vf(t)){if("selectionStart"in t)var s={start:t.selectionStart,end:t.selectionEnd};else e:{s=(s=t.ownerDocument)&&s.defaultView||window;var c=s.getSelection&&s.getSelection();if(c&&c.rangeCount!==0){s=c.anchorNode;var h=c.anchorOffset,p=c.focusNode;c=c.focusOffset;try{s.nodeType,p.nodeType}catch{s=null;break e}var C=0,R=-1,U=-1,Y=0,ne=0,ce=t,J=null;t:for(;;){for(var ee;ce!==s||h!==0&&ce.nodeType!==3||(R=C+h),ce!==p||c!==0&&ce.nodeType!==3||(U=C+c),ce.nodeType===3&&(C+=ce.nodeValue.length),(ee=ce.firstChild)!==null;)J=ce,ce=ee;for(;;){if(ce===t)break t;if(J===s&&++Y===h&&(R=C),J===p&&++ne===c&&(U=C),(ee=ce.nextSibling)!==null)break;ce=J,J=ce.parentNode}ce=ee}s=R===-1||U===-1?null:{start:R,end:U}}else s=null}s=s||{start:0,end:0}}else s=null;for(Sg={focusedElem:t,selectionRange:s},Pu=!1,gn=a;gn!==null;)if(a=gn,t=a.child,(a.subtreeFlags&1028)!==0&&t!==null)t.return=a,gn=t;else for(;gn!==null;){switch(a=gn,p=a.alternate,t=a.flags,a.tag){case 0:if((t&4)!==0&&(t=a.updateQueue,t=t!==null?t.events:null,t!==null))for(s=0;s title"))),Sn(p,c,s),p[ln]=t,Zt(p),c=p;break e;case"link":var C=SS("link","href",h).get(c+(s.href||""));if(C){for(var R=0;RRt&&(C=Rt,Rt=Ve,Ve=C);var F=_y(R,Ve),G=_y(R,Rt);if(F&&G&&(ee.rangeCount!==1||ee.anchorNode!==F.node||ee.anchorOffset!==F.offset||ee.focusNode!==G.node||ee.focusOffset!==G.offset)){var W=ce.createRange();W.setStart(F.node,F.offset),ee.removeAllRanges(),Ve>Rt?(ee.addRange(W),ee.extend(G.node,G.offset)):(W.setEnd(G.node,G.offset),ee.addRange(W))}}}}for(ce=[],ee=R;ee=ee.parentNode;)ee.nodeType===1&&ce.push({element:ee,left:ee.scrollLeft,top:ee.scrollTop});for(typeof R.focus=="function"&&R.focus(),R=0;Rs?32:s,Q.T=null,s=ig,ig=null;var p=ei,C=Sr;if(un=0,Bs=ei=null,Sr=0,(St&6)!==0)throw Error(r(331));var R=St;if(St|=4,Av(p.current),Cv(p,p.current,C,s),St=R,vl(0,!1),on&&typeof on.onPostCommitFiberRoot=="function")try{on.onPostCommitFiberRoot(_a,p)}catch{}return!0}finally{fe.p=h,Q.T=c,qv(t,a)}}function Vv(t,a,s){a=ia(s,a),a=zh(t.stateNode,a,2),t=Kr(t,a,2),t!==null&&(Ke(t,2),Wa(t))}function _t(t,a,s){if(t.tag===3)Vv(t,t,s);else for(;a!==null;){if(a.tag===3){Vv(a,t,s);break}else if(a.tag===1){var c=a.stateNode;if(typeof a.type.getDerivedStateFromError=="function"||typeof c.componentDidCatch=="function"&&(Zr===null||!Zr.has(c))){t=ia(s,t),s=Fb(2),c=Kr(a,s,2),c!==null&&(Kb(s,c,a,t),Ke(c,2),Wa(c));break}}a=a.return}}function cg(t,a,s){var c=t.pingCache;if(c===null){c=t.pingCache=new vx;var h=new Set;c.set(a,h)}else h=c.get(a),h===void 0&&(h=new Set,c.set(a,h));h.has(s)||(tg=!0,h.add(s),t=_x.bind(null,t,a,s),a.then(t,t))}function _x(t,a,s){var c=t.pingCache;c!==null&&c.delete(a),t.pingedLanes|=t.suspendedLanes&s,t.warmLanes&=~s,Dt===t&&(rt&s)===s&&(Vt===4||Vt===3&&(rt&62914560)===rt&&300>Jt()-_u?(St&2)===0&&Ps(t,0):ng|=s,Us===rt&&(Us=0)),Wa(t)}function Fv(t,a){a===0&&(a=De()),t=Mi(t,a),t!==null&&(Ke(t,a),Wa(t))}function Ax(t){var a=t.memoizedState,s=0;a!==null&&(s=a.retryLane),Fv(t,s)}function xx(t,a){var s=0;switch(t.tag){case 31:case 13:var c=t.stateNode,h=t.memoizedState;h!==null&&(s=h.retryLane);break;case 19:c=t.stateNode;break;case 22:c=t.stateNode._retryCache;break;default:throw Error(r(314))}c!==null&&c.delete(a),Fv(t,s)}function Tx(t,a){return Lt(t,a)}var Ou=null,Gs=null,ug=!1,Lu=!1,dg=!1,ni=0;function Wa(t){t!==Gs&&t.next===null&&(Gs===null?Ou=Gs=t:Gs=Gs.next=t),Lu=!0,ug||(ug=!0,kx())}function vl(t,a){if(!dg&&Lu){dg=!0;do for(var s=!1,c=Ou;c!==null;){if(t!==0){var h=c.pendingLanes;if(h===0)var p=0;else{var C=c.suspendedLanes,R=c.pingedLanes;p=(1<<31-q(42|t)+1)-1,p&=h&~(C&~R),p=p&201326741?p&201326741|1:p?p|2:0}p!==0&&(s=!0,Xv(c,p))}else p=rt,p=je(c,c===Dt?p:0,c.cancelPendingCommit!==null||c.timeoutHandle!==-1),(p&3)===0||ze(c,p)||(s=!0,Xv(c,p));c=c.next}while(s);dg=!1}}function Rx(){Kv()}function Kv(){Lu=ug=!1;var t=0;ni!==0&&jx()&&(t=ni);for(var a=Jt(),s=null,c=Ou;c!==null;){var h=c.next,p=Wv(c,a);p===0?(c.next=null,s===null?Ou=h:s.next=h,h===null&&(Gs=s)):(s=c,(t!==0||(p&3)!==0)&&(Lu=!0)),c=h}un!==0&&un!==5||vl(t),ni!==0&&(ni=0)}function Wv(t,a){for(var s=t.suspendedLanes,c=t.pingedLanes,h=t.expirationTimes,p=t.pendingLanes&-62914561;0R)break;var ne=U.transferSize,ce=U.initiatorType;ne&&rS(ce)&&(U=U.responseEnd,C+=ne*(U"u"?null:document;function mS(t,a,s){var c=Vs;if(c&&typeof a=="string"&&a){var h=Hn(a);h='link[rel="'+t+'"][href="'+h+'"]',typeof s=="string"&&(h+='[crossorigin="'+s+'"]'),pS.has(h)||(pS.add(h),t={rel:t,crossOrigin:s,href:a},c.querySelector(h)===null&&(a=c.createElement("link"),Sn(a,"link",t),Zt(a),c.head.appendChild(a)))}}function Wx(t){wr.D(t),mS("dns-prefetch",t,null)}function Yx(t,a){wr.C(t,a),mS("preconnect",t,a)}function Xx(t,a,s){wr.L(t,a,s);var c=Vs;if(c&&t&&a){var h='link[rel="preload"][as="'+Hn(a)+'"]';a==="image"&&s&&s.imageSrcSet?(h+='[imagesrcset="'+Hn(s.imageSrcSet)+'"]',typeof s.imageSizes=="string"&&(h+='[imagesizes="'+Hn(s.imageSizes)+'"]')):h+='[href="'+Hn(t)+'"]';var p=h;switch(a){case"style":p=Fs(t);break;case"script":p=Ks(t)}da.has(p)||(t=b({rel:"preload",href:a==="image"&&s&&s.imageSrcSet?void 0:t,as:a},s),da.set(p,t),c.querySelector(h)!==null||a==="style"&&c.querySelector(El(p))||a==="script"&&c.querySelector(_l(p))||(a=c.createElement("link"),Sn(a,"link",t),Zt(a),c.head.appendChild(a)))}}function Jx(t,a){wr.m(t,a);var s=Vs;if(s&&t){var c=a&&typeof a.as=="string"?a.as:"script",h='link[rel="modulepreload"][as="'+Hn(c)+'"][href="'+Hn(t)+'"]',p=h;switch(c){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":p=Ks(t)}if(!da.has(p)&&(t=b({rel:"modulepreload",href:t},a),da.set(p,t),s.querySelector(h)===null)){switch(c){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(s.querySelector(_l(p)))return}c=s.createElement("link"),Sn(c,"link",t),Zt(c),s.head.appendChild(c)}}}function Qx(t,a,s){wr.S(t,a,s);var c=Vs;if(c&&t){var h=Mr(c).hoistableStyles,p=Fs(t);a=a||"default";var C=h.get(p);if(!C){var R={loading:0,preload:null};if(C=c.querySelector(El(p)))R.loading=5;else{t=b({rel:"stylesheet",href:t,"data-precedence":a},s),(s=da.get(p))&&Tg(t,s);var U=C=c.createElement("link");Zt(U),Sn(U,"link",t),U._p=new Promise(function(Y,ne){U.onload=Y,U.onerror=ne}),U.addEventListener("load",function(){R.loading|=1}),U.addEventListener("error",function(){R.loading|=2}),R.loading|=4,Hu(C,a,c)}C={type:"stylesheet",instance:C,count:1,state:R},h.set(p,C)}}}function Zx(t,a){wr.X(t,a);var s=Vs;if(s&&t){var c=Mr(s).hoistableScripts,h=Ks(t),p=c.get(h);p||(p=s.querySelector(_l(h)),p||(t=b({src:t,async:!0},a),(a=da.get(h))&&Rg(t,a),p=s.createElement("script"),Zt(p),Sn(p,"link",t),s.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},c.set(h,p))}}function eT(t,a){wr.M(t,a);var s=Vs;if(s&&t){var c=Mr(s).hoistableScripts,h=Ks(t),p=c.get(h);p||(p=s.querySelector(_l(h)),p||(t=b({src:t,async:!0,type:"module"},a),(a=da.get(h))&&Rg(t,a),p=s.createElement("script"),Zt(p),Sn(p,"link",t),s.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},c.set(h,p))}}function yS(t,a,s,c){var h=(h=de.current)?Iu(h):null;if(!h)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof s.precedence=="string"&&typeof s.href=="string"?(a=Fs(s.href),s=Mr(h).hoistableStyles,c=s.get(a),c||(c={type:"style",instance:null,count:0,state:null},s.set(a,c)),c):{type:"void",instance:null,count:0,state:null};case"link":if(s.rel==="stylesheet"&&typeof s.href=="string"&&typeof s.precedence=="string"){t=Fs(s.href);var p=Mr(h).hoistableStyles,C=p.get(t);if(C||(h=h.ownerDocument||h,C={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},p.set(t,C),(p=h.querySelector(El(t)))&&!p._p&&(C.instance=p,C.state.loading=5),da.has(t)||(s={rel:"preload",as:"style",href:s.href,crossOrigin:s.crossOrigin,integrity:s.integrity,media:s.media,hrefLang:s.hrefLang,referrerPolicy:s.referrerPolicy},da.set(t,s),p||tT(h,t,s,C.state))),a&&c===null)throw Error(r(528,""));return C}if(a&&c!==null)throw Error(r(529,""));return null;case"script":return a=s.async,s=s.src,typeof s=="string"&&a&&typeof a!="function"&&typeof a!="symbol"?(a=Ks(s),s=Mr(h).hoistableScripts,c=s.get(a),c||(c={type:"script",instance:null,count:0,state:null},s.set(a,c)),c):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function Fs(t){return'href="'+Hn(t)+'"'}function El(t){return'link[rel="stylesheet"]['+t+"]"}function bS(t){return b({},t,{"data-precedence":t.precedence,precedence:null})}function tT(t,a,s,c){t.querySelector('link[rel="preload"][as="style"]['+a+"]")?c.loading=1:(a=t.createElement("link"),c.preload=a,a.addEventListener("load",function(){return c.loading|=1}),a.addEventListener("error",function(){return c.loading|=2}),Sn(a,"link",s),Zt(a),t.head.appendChild(a))}function Ks(t){return'[src="'+Hn(t)+'"]'}function _l(t){return"script[async]"+t}function vS(t,a,s){if(a.count++,a.instance===null)switch(a.type){case"style":var c=t.querySelector('style[data-href~="'+Hn(s.href)+'"]');if(c)return a.instance=c,Zt(c),c;var h=b({},s,{"data-href":s.href,"data-precedence":s.precedence,href:null,precedence:null});return c=(t.ownerDocument||t).createElement("style"),Zt(c),Sn(c,"style",h),Hu(c,s.precedence,t),a.instance=c;case"stylesheet":h=Fs(s.href);var p=t.querySelector(El(h));if(p)return a.state.loading|=4,a.instance=p,Zt(p),p;c=bS(s),(h=da.get(h))&&Tg(c,h),p=(t.ownerDocument||t).createElement("link"),Zt(p);var C=p;return C._p=new Promise(function(R,U){C.onload=R,C.onerror=U}),Sn(p,"link",c),a.state.loading|=4,Hu(p,s.precedence,t),a.instance=p;case"script":return p=Ks(s.src),(h=t.querySelector(_l(p)))?(a.instance=h,Zt(h),h):(c=s,(h=da.get(p))&&(c=b({},s),Rg(c,h)),t=t.ownerDocument||t,h=t.createElement("script"),Zt(h),Sn(h,"link",c),t.head.appendChild(h),a.instance=h);case"void":return null;default:throw Error(r(443,a.type))}else a.type==="stylesheet"&&(a.state.loading&4)===0&&(c=a.instance,a.state.loading|=4,Hu(c,s.precedence,t));return a.instance}function Hu(t,a,s){for(var c=s.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),h=c.length?c[c.length-1]:null,p=h,C=0;C title"):null)}function nT(t,a,s){if(s===1||a.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof a.precedence!="string"||typeof a.href!="string"||a.href==="")break;return!0;case"link":if(typeof a.rel!="string"||typeof a.href!="string"||a.href===""||a.onLoad||a.onError)break;return a.rel==="stylesheet"?(t=a.disabled,typeof a.precedence=="string"&&t==null):!0;case"script":if(a.async&&typeof a.async!="function"&&typeof a.async!="symbol"&&!a.onLoad&&!a.onError&&a.src&&typeof a.src=="string")return!0}return!1}function CS(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function aT(t,a,s,c){if(s.type==="stylesheet"&&(typeof c.media!="string"||matchMedia(c.media).matches!==!1)&&(s.state.loading&4)===0){if(s.instance===null){var h=Fs(c.href),p=a.querySelector(El(h));if(p){a=p._p,a!==null&&typeof a=="object"&&typeof a.then=="function"&&(t.count++,t=ju.bind(t),a.then(t,t)),s.state.loading|=4,s.instance=p,Zt(p);return}p=a.ownerDocument||a,c=bS(c),(h=da.get(h))&&Tg(c,h),p=p.createElement("link"),Zt(p);var C=p;C._p=new Promise(function(R,U){C.onload=R,C.onerror=U}),Sn(p,"link",c),s.instance=p}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(s,a),(a=s.state.preload)&&(s.state.loading&3)===0&&(t.count++,s=ju.bind(t),a.addEventListener("load",s),a.addEventListener("error",s))}}var kg=0;function rT(t,a){return t.stylesheets&&t.count===0&&Bu(t,t.stylesheets),0kg?50:800)+a);return t.unsuspend=s,function(){t.unsuspend=null,clearTimeout(c),clearTimeout(h)}}:null}function ju(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Bu(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var Uu=null;function Bu(t,a){t.stylesheets=null,t.unsuspend!==null&&(t.count++,Uu=new Map,a.forEach(iT,t),Uu=null,ju.call(t))}function iT(t,a){if(!(a.state.loading&4)){var s=Uu.get(t);if(s)var c=s.get(null);else{s=new Map,Uu.set(t,s);for(var h=t.querySelectorAll("link[data-precedence],style[data-precedence]"),p=0;p"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(e){console.error(e)}}return o(),Ju.exports=PS(),Ju.exports}var td=qS();const nd=Xa(td),Xs=[],GS=50;let ad=!1;function Vg(){try{return window.G7Core?.devTools}catch{return}}function Fi(o,e,n){const r=Vg();if(ad&&r?.isEnabled?.()){r.trackLog?.(o,e,n);return}ad||(o==="error"||o==="warn")&&Xs.length{n.isDebugEnabled()&&console.log(e,...r),Fi("log",o,r)},warn:(...r)=>{n.isDebugEnabled()&&console.warn(e,...r),Fi("warn",o,r)},error:(...r)=>{n.isDebugEnabled()&&console.error(e,...r),Fi("error",o,r)}}}Js.getInstance();const Ki=dt("networkResilience"),Fg=2,Kg=300,Wg=2e3,VS=15e3;function Yg(o){return o?.name==="AbortError"}function FS(o){return Yg(o)?!1:o instanceof TypeError}let kl=!1,Xg=!1;function id(){return kl}function KS(){typeof window>"u"||Xg||(Xg=!0,window.addEventListener("pagehide",()=>{kl=!0}),window.addEventListener("pageshow",o=>{o.persisted&&(kl=!1)}),window.addEventListener("beforeunload",()=>{kl=!0}))}function Jg(o,e,n){const r=Math.min(e*Math.pow(2,o),n),i=r*.25*(Math.random()*2-1);return Math.max(0,Math.round(r+i))}function Qg(o){return new Promise(e=>setTimeout(e,o))}async function Dl(o,e={}){const{retries:n=Fg,baseDelayMs:r=Kg,maxDelayMs:i=Wg,timeoutMs:l=VS,label:u=o,init:d}=e,f=n+1;let g;for(let m=0;m0?new AbortController:null;try{const _={...d};if(S){const k=d?.signal;k&&(k.aborted?S.abort():k.addEventListener("abort",()=>S.abort(),{once:!0})),_.signal=S.signal,v=setTimeout(()=>{b=!0,S.abort()},l)}return await fetch(o,_)}catch(_){if(g=_,Yg(_)&&!b||!(b||FS(_)))throw _;if(id())throw Ki.warn(`Document unloading, aborting retries: ${u}`),_;if(m===f-1)throw Ki.warn(`All ${f} attempts failed: ${u}`,_),_;const O=Jg(m,r,i);Ki.warn(`Network failure (attempt ${m+1}/${f}), retrying in ${O}ms: ${u}`),await Qg(O)}finally{v!==void 0&&clearTimeout(v)}}throw g}async function Zg(o,e={},n={}){const{retries:r=Fg,baseDelayMs:i=Kg,maxDelayMs:l=Wg,label:u=o}=n,d=r+1;for(let f=0;f{const i=document.createElement("script");i.src=o,i.async=!1;for(const[l,u]of Object.entries(e))l==="id"?i.id=u:i.setAttribute(l,u);i.onload=()=>n(),i.onerror=()=>r(new Error(`Failed to load script: ${o}`)),document.head.appendChild(i)})}const ep="extension",Wi="extensionless",YS="file";function XS(){const o=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===Wi)return Wi;const n=o?.assetUrlMode;return n===Wi||n===ep?n:o?.settings?.general?.asset_url_mode===Wi?Wi:ep}function tp(){return XS()===Wi}function Ma(o,e,n,r){const i=o.replace(/\/+$/,""),l=e.replace(/^\.+/,""),u=tp()?i:`${i}.${l}`,d=[];return r&&d.push(r.replace(/^[?&]+/,"")),n!=null&&n!==""&&d.push(`v=${n}`),d.length>0?`${u}?${d.join("&")}`:u}function Cr(o){if(!o||!tp())return o;const e=globalThis?.location?.origin,n=e&&o.startsWith(e)?o.slice(e.length):o;if(!n.startsWith("/api/"))return o;const[r,i]=JS(n),l=r.match(/^\/api\/(modules|plugins)\/bundle\.(js|css)$/);if(l)return np(`/api/${l[1]}/bundle/${l[2]}`,i);const u=r.match(/^\/api\/(templates|modules|plugins)\/assets\/([^/]+)\/(.+)$/);if(u){const[,f,g,m]=u,b=`${YS}=${encodeURIComponent(decodeURIComponent(m))}`;return`/api/${f}/assets/${g}?${b}${i?`&${i}`:""}`}const d=r.match(/^(\/api\/.+)\.(json|js|css)$/);return d?np(d[1],i):o}function JS(o){const e=o.indexOf("?");return e===-1?[o,""]:[o.slice(0,e),o.slice(e+1)]}function np(o,e){return e?`${o}?${e}`:o}const $a=dt("ComponentRegistry");class dn extends Error{constructor(n,r,i){super(n);$(this,"code");$(this,"details");this.code=r,this.details=i,this.name="ComponentRegistryError"}}const Vn=class Vn{constructor(){$(this,"registry",{});$(this,"manifest",null);$(this,"loadingState","idle");$(this,"error",null);$(this,"templateId",null);$(this,"templateType",null)}static getInstance(){return Vn.instance||(Vn.instance=new Vn),Vn.instance}static createIsolatedInstance(){return new Vn}static resetInstance(){Vn.instance=null}async loadComponents(e,n){if(this.loadingState==="loading")throw new dn("Components are already being loaded","LOADING_IN_PROGRESS");if(this.loadingState==="loaded"&&this.templateId===e&&this.templateType===n){$a.log("Components already loaded for template:",e,n);return}this.loadingState="loading",this.templateId=e,this.templateType=n,this.error=null;try{await this.loadManifest(),await this.loadComponentBundle(),this.loadingState="loaded",$a.log("Successfully loaded components:",Object.keys(this.registry).length)}catch(r){throw this.loadingState="error",this.error=r instanceof Error?r:new Error(String(r)),new dn(`Failed to load components: ${this.error.message}`,"LOAD_FAILED",{originalError:this.error})}}async loadManifest(){try{if(!this.templateId)throw new dn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=`${this.templateId}:${this.templateType}`;if(Vn.manifestCache.has(e)){this.manifest=Vn.manifestCache.get(e),$a.log("Manifest loaded from cache:",this.manifest.templateId);return}const n=Ma(`/api/templates/${this.templateId}/components`,"json"),r=await Dl(n,{label:"components.json"});if(!r.ok)throw new dn(`Failed to fetch manifest: ${r.status} ${r.statusText}`,"MANIFEST_FETCH_FAILED",{status:r.status,statusText:r.statusText});const i=await r.json();this.validateManifest(i),this.manifest=i,Vn.manifestCache.set(e,i),$a.log("Manifest loaded and cached:",i.templateId)}catch(e){throw e instanceof dn?e:new dn("Failed to load component manifest","MANIFEST_LOAD_FAILED",{originalError:e})}}validateManifest(e){if(!e.version)throw new dn("Manifest missing required field: version","MANIFEST_INVALID",{field:"version"});if(!e.templateId)throw new dn("Manifest missing required field: templateId","MANIFEST_INVALID",{field:"templateId"});if(!e.components||typeof e.components!="object")throw new dn("Manifest missing required field: components","MANIFEST_INVALID",{field:"components"});const n=["basic","composite","layout"];for(const r of n){if(!Array.isArray(e.components[r]))throw new dn(`Manifest field 'components.${r}' must be an array`,"MANIFEST_INVALID",{field:`components.${r}`,value:e.components[r]});e.components[r].forEach((i,l)=>{if(!i.name||typeof i.name!="string")throw new dn(`Invalid component metadata at ${r}[${l}]: missing or invalid 'name'`,"MANIFEST_INVALID",{type:r,index:l,metadata:i});if(!i.type||typeof i.type!="string")throw new dn(`Invalid component metadata at ${r}[${l}]: missing or invalid 'type'`,"MANIFEST_INVALID",{type:r,index:l,metadata:i})})}$a.log("Manifest validation passed")}async loadComponentBundle(){try{if(!this.templateId)throw new dn("Template ID not set","TEMPLATE_ID_NOT_SET");const e=this.getGlobalVariableName(),n=window[e];if(!n||typeof n!="object")throw new dn(`Component bundle not loaded. Expected global variable: ${e}. Ensure admin.blade.php includes the IIFE bundle script.`,"BUNDLE_NOT_LOADED",{expectedVariable:e});await this.registerComponentsFromManifest(n),$a.log("Component bundle loaded from global variable:",e)}catch(e){throw e instanceof dn?e:new dn("Failed to load component bundle","BUNDLE_LOAD_FAILED",{originalError:e})}}getGlobalVariableName(){if(!this.templateId)throw new dn("Template ID not set","TEMPLATE_ID_NOT_SET");return this.templateId.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}async registerComponentsFromManifest(e){if(!this.manifest)throw new dn("Manifest not loaded","MANIFEST_NOT_LOADED");const n=["basic","composite","layout"];for(const r of n){const i=this.manifest.components[r]||[];for(const l of i){const u=l.name,d=e[u];if(!d){$a.warn(`Component '${u}' not found in bundle`);continue}this.registerComponent(u,d,l)}}}registerComponent(e,n,r){this.registry[e]&&$a.warn(`Component '${e}' already registered, overwriting`);const i=Xe.memo(n);this.registry[e]={component:i,metadata:r},$a.log(`[ComponentRegistry] Registered component: ${e} (${r.type})`)}getComponent(e){const n=this.registry[e];return n?n.component:null}getMetadata(e){const n=this.registry[e];return n?n.metadata:null}hasComponent(e){return e in this.registry}getComponentsByType(e){return Object.entries(this.registry).filter(([n,r])=>r.metadata.type===e).map(([n,r])=>n)}getAllComponents(){return Object.keys(this.registry)}getComponentMap(){const e={};for(const[n,r]of Object.entries(this.registry))e[n]=r.component;return e}getLoadingState(){return this.loadingState}getError(){return this.error}getTemplateId(){return this.templateId}getManifest(){return this.manifest}clear(){this.registry={},this.manifest=null,this.loadingState="idle",this.error=null,this.templateId=null,this.templateType=null,$a.log("Registry cleared")}};$(Vn,"instance",null),$(Vn,"manifestCache",new Map);let fa=Vn;const ap=dt("TranslationEngine");class sd extends Error{constructor(n,r,i){super(n);$(this,"key");$(this,"locale");this.key=r,this.locale=i,this.name="TranslationError"}}const $n=class $n{constructor(e={}){$(this,"cache",new Map);$(this,"translations",new Map);$(this,"options");$(this,"cacheVersion",0);this.options={defaultLocale:e.defaultLocale||"ko",fallbackLocale:e.fallbackLocale||"en",cacheTTL:e.cacheTTL||3e5}}static getInstance(e={}){return $n.instance||($n.instance=new $n(e)),$n.instance}static resetInstance(){$n.instance=null}setCacheVersion(e){this.cacheVersion!==e&&(ap.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.cache.clear())}getCacheVersion(){return this.cacheVersion}async loadTranslations(e,n,r="/api",i=!1){const l=`${e}:${n}`;if(!i){const u=this.getFromCache(l);if(u)return this.translations.set(l,u),u}try{const u=[];i&&u.push(`_=${Date.now()}`);const d=Ma(`${r}/templates/${e}/lang/${n}`,"json",this.cacheVersion>0?this.cacheVersion:null,u.length>0?u.join("&"):void 0),f=await fetch(d);if(!f.ok)throw new sd(`Failed to load translations: ${f.statusText}`,void 0,n);const m=await f.json();return this.saveToCache(l,m),this.translations.set(l,m),m}catch(u){throw new sd(`Failed to fetch translations for ${n}: ${u instanceof Error?u.message:String(u)}`,void 0,n)}}resolveTranslations(e,n,r){let i=e;const l=5;let u=0;for(;u"="+this.translate(m,n,void 0,r)),i===d)break;u++}return i.replace($n.TRANSLATION_PATTERN,(d,f,g)=>{const{cleanedParams:m,trailing:b}=this.separateTrailingText(g);return this.translate(f,n,m,r)+b})}translate(e,n,r,i){const l=this.getTranslation(e,n),u=r&&this.cleanParamsStr(r);if(u){const d=this.parseParams(u,i);return this.replaceParams(l,d)}return l}getTranslation(e,n){const{templateId:r,locale:i}=n,l=`${r}:${i}`,u=this.translations.get(l);if(u){const d=this.getNestedValue(u,e);if(d!==null)return d}if(i!==this.options.fallbackLocale){const d=`${r}:${this.options.fallbackLocale}`,f=this.translations.get(d);if(f){const g=this.getNestedValue(f,e);if(g!==null)return g}}return e}getNestedValue(e,n){const r=n.split(".");let i=e;for(const l of r){if(i==null||typeof i!="object")return null;i=i[l]}return typeof i=="string"?i:null}setTranslationValue(e,n,r,i){const l=`${e}:${n}`,u=this.translations.get(l)??{},d=r.split(".");let f=u;for(let g=0;g(l.push(f),`__PLACEHOLDER_${l.length-1}__`)).matchAll($n.PARAM_PATTERN);for(const f of d){const[,g,m]=f,b=m.replace(/__PLACEHOLDER_(\d+)__/g,(v,S)=>l[parseInt(S,10)]);r[g.trim()]=this.resolveParamValue(b.trim(),n)}return r}resolveParamValue(e,n){if(e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2).trim();if(/[|&()[\]!?:+\-*/%<>=\s]/.test(r)&&n)try{const u=new Function(...Object.keys(n),`return ${r}`)(...Object.values(n));return String(u??"")}catch(l){return ap.error("Expression evaluation failed:",r,l),""}else{const l=this.getNestedDataValue(n,r);return String(l??"")}}return e}separateTrailingText(e){if(!e)return{cleanedParams:void 0,trailing:""};if(e.trimEnd().endsWith("}}"))return{cleanedParams:e,trailing:""};if(!(e.startsWith("|")?e.slice(1):e).includes("="))return{cleanedParams:void 0,trailing:e};const r=e.match(/(\s+[^\p{L}\w=|&\s][^\p{L}\w=]*\s*)$/u);return r?{cleanedParams:e.slice(0,-r[1].length),trailing:r[1]}:{cleanedParams:e,trailing:""}}cleanParamsStr(e){return this.separateTrailingText(e).cleanedParams||""}getNestedDataValue(e,n){if(!e)return;const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}replaceParams(e,n){let r=e;for(const[i,l]of Object.entries(n)){const u=new RegExp(`\\{\\{${i}\\}\\}`,"g");r=r.replace(u,String(l));const d=new RegExp(`\\{${i}\\}`,"g");r=r.replace(d,String(l))}return r}getFromCache(e){const n=this.cache.get(e);return n?Date.now()-n.timestamp>this.options.cacheTTL?(this.cache.delete(e),null):n.data:null}saveToCache(e,n){this.cache.set(e,{data:n,timestamp:Date.now()})}clearCache(){this.cache.clear(),this.translations.clear()}pruneCache(){const e=Date.now(),n=[];for(const[r,i]of this.cache.entries())e-i.timestamp>this.options.cacheTTL&&n.push(r);for(const r of n)this.cache.delete(r)}getCacheStats(){const e=Date.now();let n=0;for(const r of this.cache.values())e-r.timestamp>this.options.cacheTTL&&n++;return{size:this.cache.size,expired:n}}};$($n,"instance",null),$($n,"TRANSLATION_PATTERN",/\$t:(?:defer:)?([a-zA-Z0-9._-]+)(\|(?:(?!\$t:).)+)?/g),$($n,"NESTED_TRANSLATION_PARAM_PATTERN",/=(\$t:([a-zA-Z0-9._-]+))(?=[|&\s]|$)/g),$($n,"PARAM_PATTERN",/([^=|&]+)=([^|&]+)/g);let Er=$n,od=null;function QS(o){return od||(od=new Er(o)),od}const rp=Object.freeze(Object.defineProperty({__proto__:null,TranslationEngine:Er,TranslationError:sd,getTranslationEngine:QS},Symbol.toStringTag,{value:"Module"})),Yi=dt("PipeRegistry");function ip(o,e){const n=o.getFullYear(),r=String(o.getMonth()+1).padStart(2,"0"),i=String(o.getDate()).padStart(2,"0"),l=String(o.getHours()).padStart(2,"0"),u=String(o.getMinutes()).padStart(2,"0"),d=String(o.getSeconds()).padStart(2,"0");return e.replace("YYYY",String(n)).replace("YY",String(n).slice(-2)).replace("MM",r).replace("M",String(o.getMonth()+1)).replace("DD",i).replace("D",String(o.getDate())).replace("HH",l).replace("H",String(o.getHours())).replace("mm",u).replace("m",String(o.getMinutes())).replace("ss",d).replace("s",String(o.getSeconds()))}function ZS(o,e="ko"){const r=new Date().getTime()-o.getTime(),i=Math.floor(r/1e3),l=Math.floor(i/60),u=Math.floor(l/60),d=Math.floor(u/24),f=Math.floor(d/7),g=Math.floor(d/30),m=Math.floor(d/365);return e==="ko"?i<60?"방금 전":l<60?`${l}분 전`:u<24?`${u}시간 전`:d<7?`${d}일 전`:f<4?`${f}주 전`:g<12?`${g}개월 전`:`${m}년 전`:i<60?"just now":l<60?`${l} minute${l===1?"":"s"} ago`:u<24?`${u} hour${u===1?"":"s"} ago`:d<7?`${d} day${d===1?"":"s"} ago`:f<4?`${f} week${f===1?"":"s"} ago`:g<12?`${g} month${g===1?"":"s"} ago`:`${m} year${m===1?"":"s"} ago`}function ld(o){if(o==null)return null;if(o instanceof Date)return o;if(typeof o=="number")return new Date(o);if(typeof o=="string"){const e=new Date(o);return isNaN(e.getTime())?null:e}return null}const Qs={date:{fn:(o,e="YYYY-MM-DD")=>{const n=ld(o);return n?ip(n,e):""},description:"날짜 포맷 (기본: YYYY-MM-DD)"},datetime:{fn:(o,e="YYYY-MM-DD HH:mm")=>{const n=ld(o);return n?ip(n,e):""},description:"날짜+시간 포맷 (기본: YYYY-MM-DD HH:mm)"},relativeTime:{fn:(o,e="ko")=>{const n=ld(o);return n?ZS(n,e):""},description:'상대 시간 표시 (예: "3분 전")'},number:{fn:(o,e)=>{const n=Number(o);if(isNaN(n))return String(o??"");const r={};return e!==void 0&&(r.minimumFractionDigits=e,r.maximumFractionDigits=e),n.toLocaleString(void 0,r)},description:"숫자 포맷 (천단위 구분, 선택적 소수점)"},truncate:{fn:(o,e=100,n="...")=>typeof o!="string"?String(o??""):o.length<=e?o:o.slice(0,e)+n,description:'문자열 자르기 (기본: 100자, 접미사: "...")'},uppercase:{fn:o=>typeof o!="string"?String(o??""):o.toUpperCase(),description:"대문자 변환"},lowercase:{fn:o=>typeof o!="string"?String(o??""):o.toLowerCase(),description:"소문자 변환"},stripHtml:{fn:o=>typeof o!="string"?String(o??""):o.replace(/<[^>]*>/g,""),description:"HTML 태그 제거"},default:{fn:(o,e="")=>o==null||o===""?e:o,description:"기본값 설정 (null, undefined, 빈문자열 시)"},fallback:{fn:(o,e)=>o??e,description:"폴백 값 설정 (null, undefined 시만)"},first:{fn:o=>{if(Array.isArray(o))return o[0]},description:"배열의 첫 번째 요소"},last:{fn:o=>{if(Array.isArray(o))return o[o.length-1]},description:"배열의 마지막 요소"},join:{fn:(o,e=", ")=>Array.isArray(o)?o.join(e):"",description:'배열을 문자열로 결합 (기본 구분자: ", ")'},length:{fn:o=>Array.isArray(o)||typeof o=="string"?o.length:0,description:"배열/문자열 길이"},filterBy:{fn:(o,e,n)=>Array.isArray(o)?Array.isArray(e)?o.filter(r=>{const i=n?r?.[n]:r;return e.includes(i)}):o:[],description:"배열 필터링 (allowList에 포함된 항목만 반환)"},keys:{fn:o=>o==null||typeof o!="object"?[]:Object.keys(o),description:"객체의 키 배열"},values:{fn:o=>o==null||typeof o!="object"?[]:Object.values(o),description:"객체의 값 배열"},json:{fn:(o,e)=>{try{return JSON.stringify(o,null,e)}catch{return""}},description:"JSON 문자열로 변환"},localized:{fn:(o,e)=>o==null?"":typeof o=="string"?o:typeof o!="object"?String(o):o[e||"ko"]||o.ko||o.en||Object.values(o)[0]||"",description:"다국어 객체에서 로케일에 맞는 값 추출"}},Xi=new Map,yi=class yi{static getInstance(){return yi.instance||(yi.instance=new yi),yi.instance}register(e,n,r){if(Qs[e]){Yi.warn(`[PipeRegistry] 내장 파이프를 덮어쓸 수 없습니다: ${e}`);return}Xi.set(e,{fn:n,description:r}),Yi.log(`[PipeRegistry] 커스텀 파이프 등록됨: ${e}`)}unregister(e){if(Qs[e])return Yi.warn(`[PipeRegistry] 내장 파이프는 해제할 수 없습니다: ${e}`),!1;const n=Xi.delete(e);return n&&Yi.log(`[PipeRegistry] 커스텀 파이프 해제됨: ${e}`),n}get(e){const n=Qs[e];return n?n.fn:Xi.get(e)?.fn}has(e){return!!Qs[e]||Xi.has(e)}execute(e,n,r=[]){const i=this.get(e);if(!i)return Yi.warn(`[PipeRegistry] 알 수 없는 파이프: ${e}`),n;try{return i(n,...r)}catch(l){return Yi.error(`[PipeRegistry] 파이프 실행 오류 (${e}):`,l),n}}list(){const e=[];for(const[n,r]of Object.entries(Qs))e.push({name:n,description:r.description,type:"built-in"});for(const[n,r]of Xi.entries())e.push({name:n,description:r.description,type:"custom"});return e.sort((n,r)=>n.name.localeCompare(r.name))}clearCustomPipes(){Xi.clear()}};$(yi,"instance",null);let Ol=yi;Ol.getInstance();function ew(o){const e=o.trim(),n=e.indexOf("(");if(n===-1)return{name:e,args:[]};const r=e.slice(0,n).trim(),i=e.slice(n+1,-1);if(!i.trim())return{name:r,args:[]};const l=[];let u="",d=null,f=0;for(let g=0;g0?i[g-1]:"")==="\\"){u+=m;continue}if((m==='"'||m==="'")&&!d){d=m;continue}if(m===d){d=null;continue}if(d){u+=m;continue}if(m==="("||m==="["||m==="{"){f++,u+=m;continue}if(m===")"||m==="]"||m==="}"){f--,u+=m;continue}if(m===","&&f===0){l.push(sp(u.trim())),u="";continue}u+=m}return u.trim()&&l.push(sp(u.trim())),{name:r,args:l}}function sp(o){if(o==="null")return null;if(o==="undefined")return;if(o==="true")return!0;if(o==="false")return!1;const e=Number(o);return!isNaN(e)&&o!==""?e:o}function tw(o){const e=[];let n="",r=null,i=0;for(let l=0;l0?o[l-1]:"",f=l0?o[r-1]:"",u=r=2&&o.charCodeAt(0)===64976&&o.charCodeAt(o.length-1)===64977}function lp(o){return o.includes(Ll)}function cp(o){return o.slice(1,-1)}function Ji(o){if(typeof o=="string")return eo(o);if(Array.isArray(o))return o.map(Ji);if(o&&typeof o=="object"){const e={};for(const[n,r]of Object.entries(o))e[n]=Ji(r);return e}return o}const to=dt("DataBindingEngine");function Ml(){try{return window.G7Core?.devTools}catch{return}}class cd extends Error{constructor(n,r,i){super(n);$(this,"path");$(this,"context");this.path=r,this.context=i,this.name="DataBindingError"}}const wa=class wa{constructor(e){$(this,"cache",new Map);$(this,"expressionFnCache",new Map);$(this,"renderCycleCache",new Map);$(this,"currentRenderCycleId",0);$(this,"defaultOptions",{defaultValue:void 0,nullSafe:!0,detectCircular:!0,maxDepth:10});e&&(this.defaultOptions={...this.defaultOptions,...e})}startRenderCycle(){this.currentRenderCycleId++,this.renderCycleCache.clear()}isRenderCycleCacheActive(){return this.currentRenderCycleId>0}getFromRenderCycleCache(e){if(this.isRenderCycleCacheActive())return this.renderCycleCache.get(e)}saveToRenderCycleCache(e,n){this.isRenderCycleCacheActive()&&this.renderCycleCache.set(e,n)}resolveBindings(e,n,r,i){const l={...this.defaultOptions,...r},u=Ml(),d=n._computed&&!n.$computed?{...n,$computed:n._computed}:n;return e.replace(wa.BINDING_PATTERN,(f,g)=>{const m=g.trim();let b=!1,v=m;if(m.startsWith(Na)&&(b=!0,v=m.slice(Na.length)),ci(v)){const T=u?.isEnabled()?performance.now():0;try{const[D,j]=tw(v);let H,P=!1;const X=/[?:|&!+\-*/<>=()]/.test(D)||/\[['"]/.test(D),pe=D.startsWith("_global")||D.startsWith("_local")||D.startsWith("_isolated")||D.startsWith("$parent");if(l.skipCache)X?H=this.evaluateExpression(D,d):H=this.resolvePath(D,d,l);else if(X){const Me=this.getFromRenderCycleCache(`expr:${D}`);Me!==void 0?(H=Me,P=!0):(H=this.evaluateExpression(D,d),this.saveToRenderCycleCache(`expr:${D}`,H))}else if(pe){const Me=this.getFromRenderCycleCache(D);Me!==void 0?(H=Me,P=!0):(H=this.resolvePath(D,d,l),this.saveToRenderCycleCache(D,H))}else{const Me=this.getFromCache(D);Me!==void 0?(H=Me,P=!0):(H=this.resolvePath(D,d,l),this.saveToCache(D,H))}const _e=nw(H,j);if(u?.isEnabled()){const Me=performance.now()-T;u.trackExpressionEval({expression:`{{${m}}}`,result:this.sanitizeResultForTracking(_e),resultType:this.getResultType(_e),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:P,duration:Me,method:"resolveBindings",skipCache:l.skipCache})}const we=this.formatValue(_e);return b?eo(we):we}catch(D){return to.error("Pipe expression evaluation failed:",v,D),f}}if(/[?:&!+\-*/<>=()]/.test(v)||/\|\|/.test(v)||/\[['"]/.test(v)){const T=u?.isEnabled()?performance.now():0;try{let D,j=!1;if(l.skipCache)D=this.evaluateExpression(v,d);else{const P=`expr:${v}`,X=this.getFromRenderCycleCache(P);X!==void 0?(D=X,j=!0):(D=this.evaluateExpression(v,d),this.saveToRenderCycleCache(P,D))}if(u?.isEnabled()){const P=performance.now()-T;u.trackExpressionEval({expression:`{{${m}}}`,result:this.sanitizeResultForTracking(D),resultType:this.getResultType(D),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:j,duration:P,method:"resolveBindings",skipCache:l.skipCache})}const H=this.formatValue(D);return b?eo(H):H}catch(D){return to.error("Expression evaluation failed:",v,D),f}}if(l.skipCache){const T=u?.isEnabled()?performance.now():0,D=this.resolvePath(v,d,l);if(u?.isEnabled()){const H=performance.now()-T;u.trackExpressionEval({expression:`{{${m}}}`,result:this.sanitizeResultForTracking(D),resultType:this.getResultType(D),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:H,method:"resolveBindings",skipCache:!0})}const j=this.formatValue(D);return b?eo(j):j}const _=v.startsWith("_global")||v.startsWith("_local")||v.startsWith("_isolated")||v.startsWith("$parent"),x=u?.isEnabled()?performance.now():0;let k,O=!1;if(_){const T=this.getFromRenderCycleCache(v);T!==void 0?(k=T,O=!0):(k=this.resolvePath(v,d,l),this.saveToRenderCycleCache(v,k))}else{const T=this.getFromCache(v);T!==void 0?(k=T,O=!0):(k=this.resolvePath(v,d,l),this.saveToCache(v,k))}if(u?.isEnabled()){const T=performance.now()-x;u.trackExpressionEval({expression:`{{${m}}}`,result:this.sanitizeResultForTracking(k),resultType:this.getResultType(k),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:O,duration:T,method:"resolveBindings",skipCache:l.skipCache})}const M=this.formatValue(k);return b?eo(M):M})}resolve(e,n,r,i){const l={...this.defaultOptions,...r},u=Ml(),d=u?.isEnabled()?performance.now():0;if(l.skipCache){const b=this.resolvePath(e,n,l);return u?.isEnabled()&&u.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:performance.now()-d,method:"resolve",skipCache:!0}),b}const f=e.startsWith("_global")||e.startsWith("_local")||e.startsWith("_isolated")||e.startsWith("$parent");let g=!1;if(f){const b=this.getFromRenderCycleCache(e);if(b!==void 0)return g=!0,u?.isEnabled()&&u.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),b}else{const b=this.getFromCache(e);if(b!==void 0)return g=!0,u?.isEnabled()&&u.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),b}const m=this.resolvePath(e,n,l);return f?this.saveToRenderCycleCache(e,m):this.saveToCache(e,m),u?.isEnabled()&&u.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(m),resultType:this.getResultType(m),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:g,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),m}resolvePath(e,n,r,i=0,l=new WeakSet){if(r.maxDepth&&i>r.maxDepth){if(r.detectCircular)throw new cd(`Maximum depth exceeded (${r.maxDepth}). Possible circular reference.`,e,n);return r.defaultValue}const u=this.parsePath(e);let d=n;for(let f=0;fwa.CACHE_EXPIRY){this.cache.delete(e),r?.isEnabled()&&r.recordCacheMiss();return}return r?.isEnabled()&&r.recordCacheHit(),n.value}saveToCache(e,n){this.cache.set(e,{value:n,timestamp:Date.now()})}isActionDefinition(e){return typeof e.handler!="string"?!1:e.params!==void 0||Array.isArray(e.actions)||typeof e.target=="string"||e.onSuccess!==void 0||e.onError!==void 0}extractSingleBinding(e){if(!e.startsWith("{{")||!e.endsWith("}}")||e.length<5)return null;const n=e.slice(2,-2);let r=0;for(let i=0;i0)r--;else if(n[i+1]==="}")return null}}return n}resolveObject(e,n,r){if(this.isSwitchExpression(e)){const d=e;return this.resolveSwitch(d,n,r)}if(this.isActionDefinition(e))return{...e};if("iteration"in e)return{...e};const i={},u=[...["cellChildren","expandChildren","expandContext","render"],...r?.skipBindingKeys||[]];for(const[d,f]of Object.entries(e)){if(u.includes(d)){i[d]=f;continue}if(typeof f=="string"){const g=this.extractSingleBinding(f);if(g!==null){const m=g.trim();let b=!1,v=m;m.startsWith(Na)&&(b=!0,v=m.slice(Na.length));const S=/[?:|&!+\-*/<>=()]/.test(v)||/\[['"]/.test(v),_=ci(v)?this.resolveBindings(`{{${v}}}`,n,r):S?this.evaluateExpression(v,n,r):this.resolve(v,n,r);i[d]=b&&_!=null?Ji(_):_}else i[d]=this.resolveBindings(f,n,r)}else Array.isArray(f)?i[d]=f.map(g=>typeof g=="string"?this.resolveBindings(g,n,r):typeof g=="object"&&g!==null?this.resolveObject(g,n,r):g):f&&typeof f=="object"?i[d]=this.resolveObject(f,n,r):i[d]=f}return i}clearCache(){this.cache.clear(),this.expressionFnCache.clear()}invalidateCacheByKeys(e){for(const n of this.cache.keys())for(const r of e)if(n===r||n.startsWith(`${r}.`)||n.startsWith(`${r}[`)){this.cache.delete(n);break}}pruneCache(){const e=Date.now();for(const[n,r]of this.cache.entries())e-r.timestamp>wa.CACHE_EXPIRY&&this.cache.delete(n)}getCacheStats(){const e=Date.now();let n=0;for(const r of this.cache.values())e-r.timestamp>wa.CACHE_EXPIRY&&n++;return{size:this.cache.size,expired:n}}evaluateExpression(e,n,r){const i=Ml(),l=i?.isEnabled()?performance.now():0;i?.isEnabled()&&i.trackBindingEval();try{let u=this.preprocessOptionalChaining(e);u=this.preprocessTranslationTokens(u);const d=this.extractVariablesFromExpression(u),f={...n};for(const D of d)D in f||(f[D]=void 0);n._computed&&!f.$computed&&(f.$computed=n._computed);let g=n.$templateId,m=n.$locale;if(typeof window<"u"&&(!g||!m)){const D=window.__templateApp?.getConfig?.();g??(g=D?.templateId),m??(m=D?.locale)}const b=m||"ko";f.$localized=(D,j)=>{if(D==null&&!j)return"";if(typeof D=="string")return D;if(D&&typeof D=="object"&&D[b])return D[b];if(j&&typeof j=="string"){const H=f.$t?f.$t(j):j;if(H&&H!==j)return H}return D&&typeof D=="object"?D.ko||D.en||Object.values(D)[0]||"":D==null?"":String(D)};const v=typeof window<"u"?window.G7Core:void 0;f.$uuid=()=>v?.uuid?v.uuid():typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,D=>{const j=Math.random()*16|0;return(D==="x"?j:j&3|8).toString(16)});const S={templateId:g||"",locale:b};f.$t=D=>{if(!D||typeof D!="string")return"";try{return Er.getInstance().translate(D,S)}catch(j){return to.warn("$t() translation failed for key:",D,j),D}},f.$get=(D,j,H=void 0)=>{if(D==null)return H;const P=Array.isArray(j)?j:[j];if(P.length===0)return D;let X=D;for(const pe of P){if(X==null||pe==null)return H;X=X[pe]}return X??H};const _=Object.keys(f),x=Object.values(f),k=`${u}|${_.join(",")}`;let O=this.expressionFnCache.get(k);const M=!!O;O||(O=new Function(..._,`return (${u});`),this.expressionFnCache.set(k,O));const T=O(...x);if(i?.isEnabled()){const D=performance.now()-l;i.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(T),resultType:this.getResultType(T),componentId:r?.componentId,componentName:r?.componentName,propName:r?.propName,fromCache:M,duration:D,method:"evaluateExpression",skipCache:r?.skipCache})}return T}catch(u){throw new Error(`Failed to evaluate expression "${e}": ${u instanceof Error?u.message:String(u)}`)}}getResultType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}sanitizeResultForTracking(e){if(e==null||typeof e!="object")return e;try{if(Array.isArray(e))return e.length>10?`[Array(${e.length})]`:e.slice(0,10);const n=Object.keys(e);return n.length>20?`{Object(${n.length} keys)}`:(JSON.stringify(e),e)}catch{return"[Complex Object]"}}preprocessTranslationTokens(e){return e=e.replace(/(['"])(\$t:([a-zA-Z_][a-zA-Z0-9_.\-]*))\1/g,(n,r,i,l)=>`$t('${l}')`),e.replace(/(?(n.push(l),`__STRING_LITERAL_${n.length-1}__`));const i=[];return r=r.replace(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*(?:\|[^'"\s,)]+)?/g,l=>(i.push(l),`__TRANSLATION_TOKEN_${i.length-1}__`)),r=r.replace(/([a-zA-Z_$][a-zA-Z0-9_$]*)\.(?!\?)/g,"$1?."),r=r.replace(/__TRANSLATION_TOKEN_(\d+)__/g,(l,u)=>i[parseInt(u,10)]),r=r.replace(/__STRING_LITERAL_(\d+)__/g,(l,u)=>n[parseInt(u,10)]),r}extractVariablesFromExpression(e){const n=new Set(["true","false","null","undefined","NaN","Infinity","if","else","for","while","do","switch","case","break","continue","return","function","class","const","let","var","new","delete","typeof","instanceof","this","super","import","export","default","try","catch","finally","throw","Math","Date","JSON","Array","Object","String","Number","Boolean","RegExp","Set","Map","WeakSet","WeakMap","Symbol","Promise","BigInt","Error","parseInt","parseFloat","isNaN","isFinite","encodeURI","decodeURI","encodeURIComponent","decodeURIComponent"]),r=/(?{n!==null&&clearTimeout(n),n=setTimeout(()=>{o(...r)},e)})}class iw{constructor(){$(this,"subscribers",new Set);$(this,"currentWidth",1024);$(this,"debouncedHandler",null);$(this,"isInitialized",!1);this.initialize()}initialize(){typeof window>"u"||(this.currentWidth=window.innerWidth,this.isInitialized=!0,this.debouncedHandler=rw(()=>{this.currentWidth=window.innerWidth,this.notifySubscribers()},150),window.addEventListener("resize",this.debouncedHandler))}notifySubscribers(){this.subscribers.forEach(e=>{e(this.currentWidth)})}subscribe(e){return this.subscribers.add(e),e(this.currentWidth),()=>{this.subscribers.delete(e)}}getWidth(){return this.currentWidth}parseRange(e){if(Nl.has(e))return Nl.get(e)??null;let n=null;if($l[e])n={...$l[e]};else{const r=e.match(/^(-?\d*)-(-?\d*)$/);if(r){const[,i,l]=r,u=i===""?0:parseInt(i,10),d=l===""?1/0:parseInt(l,10);!isNaN(u)&&!isNaN(d)&&u<=d&&(n={min:u,max:d})}}return Nl.set(e,n),n}getMatchingKey(e,n){const r=[];for(const i of Object.keys(e)){const l=this.parseRange(i);l&&n>=l.min&&n<=l.max&&r.push({key:i,range:l,isPreset:!!$l[i]})}return r.length===0?null:(r.sort((i,l)=>{if(i.isPreset!==l.isPreset)return i.isPreset?1:-1;const u=i.range.max-i.range.min,d=l.range.max-l.range.min;return u-d}),r[0].key)}matches(e){const n=this.parseRange(e);return n?this.currentWidth>=n.min&&this.currentWidth<=n.max:!1}clearSubscribers(){this.subscribers.clear()}destroy(){this.debouncedHandler&&typeof window<"u"&&window.removeEventListener("resize",this.debouncedHandler),this.clearSubscribers(),Nl.clear()}_setWidthForTesting(e){this.currentWidth=e,this.notifySubscribers()}}const ui=new iw,Il=dt("ConditionEvaluator");function sw(o){switch(o){case"true":return{value:!0};case"false":return{value:!1};case"null":return{value:null};case"undefined":return{value:void 0};default:return}}function ow(o){return/[?:|&!+\-*/<>=()]/.test(o)}function lw(o){if(!o.startsWith("{{")||!o.endsWith("}}"))return null;const e=o.slice(2,-2);let n=null,r=0;for(let i=0;i0?e[i-1]:"")!=="\\"&&((l==='"'||l==="'")&&!n?n=l:l===n&&(n=null),!n&&(l==="{"&&r++,l==="}"&&r--,r<0)))return null}return r===0?e.trim():null}function cw(o,e,n){if(!o)return!0;try{const r=lw(o);let i;if(r!==null){const l=sw(r);l!==void 0?i=l.value:ci(r)?i=n.resolveBindings(`{{${r}}}`,e,{skipCache:!0}):ow(r)?i=n.evaluateExpression(r,e):i=n.resolve(r,e,{skipCache:!0})}else i=n.resolveBindings(o,e,{skipCache:!0});if(typeof i=="string"){const l=i.toLowerCase().trim();if(l==="false"||l==="0"||l===""||l==="null"||l==="undefined")return!1}return!!i}catch(r){return Il.warn(`evaluateStringCondition: 조건 평가 실패: ${o}`,r),!1}}function Hl(o,e,n){if(typeof o=="string")return cw(o,e,n);if("and"in o){if(!Array.isArray(o.and)||o.and.length===0)return Il.warn("evaluateConditionExpression: AND 그룹이 비어있습니다"),!0;for(const r of o.and)if(!Hl(r,e,n))return!1;return!0}if("or"in o){if(!Array.isArray(o.or)||o.or.length===0)return Il.warn("evaluateConditionExpression: OR 그룹이 비어있습니다"),!1;for(const r of o.or)if(Hl(r,e,n))return!0;return!1}return Il.warn("evaluateConditionExpression: 알 수 없는 조건 형식",o),!1}function up(o,e,n){if(!Array.isArray(o)||o.length===0)return{matched:!1,branchIndex:-1};for(let r=0;r=()[\]{}]/.test(o)}function hp(o){if(!o.startsWith("{{")||!o.endsWith("}}"))return null;const e=o.slice(2,-2);let n=null,r=0;for(let i=0;i0?e[i-1]:"")!=="\\"&&((l==='"'||l==="'")&&!n?n=l:l===n&&(n=null),!n&&(l==="{"&&r++,l==="}"&&r--,r<0)))return null}return r===0?e.trim():null}function dd(o,e,n){let r=o;if(o.startsWith("{{")&&o.endsWith("}}")&&(r=o.slice(2,-2).trim()),ud(r))try{return n.evaluateExpression(r,e)}catch(i){Ja.warn(`resolveIterationSource: 표현식 평가 실패: ${o}`,i);return}return n.resolve(r,e,{skipCache:!0})}function hw(o,e){const n={...o};if(e?.parentDataContext)for(const[r,i]of Object.entries(e.parentDataContext))r in n||(n[r]=i);if(e?.state){const r=n._local||{};n._local={...r,...e.state}}return e?.isolatedContext?.state&&(n._isolated=e.isolatedContext.state),n}function fd(o,e,n,r){if(!e||!Array.isArray(e)||e.length===0)return o;const i=r?.actionDispatcher??lm(),l={...o,actions:e},u=i.bindActionsToProps(l,n,r?.componentContext);return delete u.actions,u}function gp(o,e,n,r){if(typeof o=="boolean")return o;if(!o)return!0;try{const i=hp(o);let l;if(i!==null){const u=fw.get(i);u!==void 0?l=u:ci(i)?l=n.resolveBindings(`{{${i}}}`,e,{skipCache:!0}):ud(i)?l=n.evaluateExpression(i,e):l=n.resolve(i,e,{skipCache:!0})}else l=n.resolveBindings(o,e,{skipCache:!0});if(typeof l=="string"){const u=l.toLowerCase().trim();if(u==="false"||u==="0"||u===""||u==="null"||u==="undefined")return!1}return!!l}catch(i){return Ja.warn(`evaluateIfCondition: 조건 평가 실패 (컴포넌트: ${r||"unknown"}):`,i),!1}}function Qi(o,e,n,r){if(o.if!==void 0)return gp(o.if,e,n,r);if(o.condition!==void 0)return gp(o.condition,e,n,r);if(o.conditions===void 0)return!0;const i=o.conditions;try{return uw(i)?Hl(i,e,n):dw(i)?up(i,e,n).matched:(Ja.warn(`evaluateRenderCondition: 알 수 없는 conditions 형식 (컴포넌트: ${r||"unknown"})`),!0)}catch(l){return Ja.warn(`evaluateRenderCondition: conditions 평가 실패 (컴포넌트: ${r||"unknown"}):`,l),!1}}function pp(o){if(!o.responsive)return o;const e=ui.getWidth(),n=ui.getMatchingKey(o.responsive,e);if(!n)return o;const r=o.responsive[n];return{...o,props:{...o.props,...r.props},children:r.children??o.children,text:r.text??o.text,if:r.if??o.if,iteration:r.iteration??o.iteration}}function no(o,e,n,r,i){if(!o||o.length===0)return[];const l=i?.bindingEngine??new Rn,u=i?.translationEngine??Er.getInstance(),d=i?.translationContext??{templateId:"",locale:"ko"},f=hw(e,i?.componentContext),g=(v,S)=>{if(typeof v=="string"){if(op(v))return cp(v);if(lp(v)){const x=[],k=v.replace(new RegExp(`${Ll}([^${Zs}]*)${Zs}`,"g"),(M,T)=>(x.push(T),`${_r}${x.length-1}${_r}`));let O=k;return/\$t:[a-zA-Z0-9._-]+/.test(k)&&(O=u.resolveTranslations(k,d,S)),O.replace(new RegExp(`${_r}(\\d+)${_r}`,"g"),(M,T)=>x[parseInt(T)])}if(v.startsWith("$t:defer:")){const x="$t:"+v.slice(9);return u.resolveTranslations(x,d,S)}if(v.startsWith("$t:"))return u.resolveTranslations(v,d,S);const _=hp(v);if(_!==null){let x=!1,k=_;_.startsWith(Na)&&(x=!0,k=_.slice(Na.length));let O;if(ci(k))O=l.resolveBindings(`{{${k}}}`,S,{skipCache:!0});else if(ud(k))try{O=l.evaluateExpression(k,S)}catch(M){Ja.warn("renderItemChildren: 표현식 평가 실패:",M);return}else O=l.resolve(k,S,{skipCache:!0});return!x&&typeof O=="string"&&/\$t:[a-zA-Z0-9._-]+/.test(O)&&(O=u.resolveTranslations(O,d,S)),x&&O!=null?Ji(O):O}if(v.includes("{{")){const x=l.resolveBindings(v,S,{skipCache:!0});return typeof x=="string"&&/\$t:[a-zA-Z0-9._-]+/.test(x)?u.resolveTranslations(x,d,S):x}return v}if(Array.isArray(v))return v.map(_=>g(_,S));if(v&&typeof v=="object"){const _={};for(const[x,k]of Object.entries(v))_[x]=g(k,S);return _}return v},m=(v,S)=>{if(!v)return{};const _={};for(const[x,k]of Object.entries(v))_[x]=g(k,S);return _},b=(v,S,_)=>{const x=pp(v),k=x.iteration;if(!k)return[];const O=dd(k.source,_,l);if(!Array.isArray(O))return Ja.warn(`renderItemChildren: iteration.source가 배열이 아닙니다: ${k.source}`),[];const M=dp();if(M?.isEnabled()){const D=`${S}-iteration`;M.trackIteration(D,k.source,k.item_var,k.index_var,O.length)}const T=n[x.name];return T?O.flatMap((D,j)=>{const H={..._,[k.item_var]:D,[`${k.item_var}_index`]:j,...k.index_var?{[k.index_var]:j}:{}},P=Qi({if:x.if,condition:x.condition,conditions:x.conditions},H,l,x.id);if(x.if&&M?.isEnabled()){const Je=`${S}-iter-${j}-if`;M.trackIfCondition(Je,x.if,P,x.name)}if(!P)return[];const X=x.id?String(g(x.id,H)):void 0,pe=`${S}-iter-${j}`,_e=i?.getRemountKey?i.getRemountKey(X,pe):pe,we=m(x.props,H),Me=fd(we,x.actions,H,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let qe=null;return x.text!==void 0?qe=g(x.text,H):x.children&&x.children.length>0&&(qe=no(x.children,H,n,_e,i)),M?.isEnabled()&&M.trackRender(x.name),[Xe.createElement(T,{key:_e,...Me},qe)]}):(Ja.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${x.name}`),[])};return o.flatMap((v,S)=>{const _=pp(v),x=_.id?String(g(_.id,f)):void 0,k=r?`${r}-${x||S}`:x||`child-${S}`,O=i?.getRemountKey?i.getRemountKey(x,k):k;if(_.iteration)return b(_,O,f);const M=Qi({if:_.if,condition:_.condition,conditions:_.conditions},f,l,_.id),T=dp();if(_.if&&T?.isEnabled()){const X=`${O}-if`;T.trackIfCondition(X,_.if,M,_.name)}if(!M)return[];const D=n[_.name];if(!D)return Ja.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${_.name}`),[];const j=m(_.props,f),H=fd(j,_.actions,f,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let P=null;return _.text!==void 0?P=g(_.text,f):_.children&&_.children.length>0&&(P=no(_.children,f,n,O,i)),T?.isEnabled()&&T.trackRender(_.name),[Xe.createElement(D,{key:O,...H},P)]})}function Zi(o,e,n,r){let i,l;return n instanceof Rn?(i=n,l=r):(i=fp,l=n),i.resolveBindings(o,e,l)}function gw(o,e,n,r){const i=n??fp,l={skipCache:!0,...r};let u;try{u=i.resolveBindings(o.key,e,{skipCache:l.skipCache})?.toString()?.trim()??""}catch(g){Ja.warn("resolveClassMap: key 평가 실패:",o.key,g),u=""}let d="";u&&o.variants&&u in o.variants?d=o.variants[u]:o.default&&(d=o.default);const f=[];return o.base?.trim()&&f.push(o.base.trim()),d?.trim()&&f.push(d.trim()),f.join(" ")}function mp(o,e){return function(){return o.apply(e,arguments)}}const{toString:pw}=Object.prototype,{getPrototypeOf:es}=Object,{iterator:ao,toStringTag:yp}=Symbol,zl=(({hasOwnProperty:o})=>(e,n)=>o.call(e,n))(Object.prototype),ro=(o,e)=>{let n=o;const r=[];for(;n!=null&&n!==Object.prototype;){if(r.indexOf(n)!==-1)return!1;if(r.push(n),zl(n,e))return!0;n=es(n)}return!1},mw=(o,e)=>o!=null&&ro(o,e)?o[e]:void 0,hd=(o=>e=>{const n=pw.call(e);return o[n]||(o[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),ha=o=>(o=o.toLowerCase(),e=>hd(e)===o),jl=o=>e=>typeof e===o,{isArray:di}=Array,ts=jl("undefined");function ns(o){return o!==null&&!ts(o)&&o.constructor!==null&&!ts(o.constructor)&&Ln(o.constructor.isBuffer)&&o.constructor.isBuffer(o)}const bp=ha("ArrayBuffer");function yw(o){let e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e=ArrayBuffer.isView(o):e=o&&o.buffer&&bp(o.buffer),e}const bw=jl("string"),Ln=jl("function"),vp=jl("number"),as=o=>o!==null&&typeof o=="object",vw=o=>o===!0||o===!1,Ul=o=>{if(!as(o))return!1;const e=es(o);return(e===null||e===Object.prototype||es(e)===null)&&!ro(o,yp)&&!ro(o,ao)},Sw=o=>{if(!as(o)||ns(o))return!1;try{return Object.keys(o).length===0&&Object.getPrototypeOf(o)===Object.prototype}catch{return!1}},ww=ha("Date"),Cw=ha("File"),Ew=o=>!!(o&&typeof o.uri<"u"),_w=o=>o&&typeof o.getParts<"u",Aw=ha("Blob"),xw=ha("FileList"),Tw=o=>as(o)&&Ln(o.pipe);function Rw(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}}const Sp=Rw(),wp=typeof Sp.FormData<"u"?Sp.FormData:void 0,kw=o=>{if(!o)return!1;if(wp&&o instanceof wp)return!0;const e=es(o);if(!e||e===Object.prototype||!Ln(o.append))return!1;const n=hd(o);return n==="formdata"||n==="object"&&Ln(o.toString)&&o.toString()==="[object FormData]"},Dw=ha("URLSearchParams"),[Ow,Lw,Mw,$w]=["ReadableStream","Request","Response","Headers"].map(ha),Nw=o=>o.trim?o.trim():o.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function io(o,e,{allOwnKeys:n=!1}={}){if(o===null||typeof o>"u")return;let r,i;if(typeof o!="object"&&(o=[o]),di(o))for(r=0,i=o.length;r0;)if(i=n[r],e===i.toLowerCase())return i;return null}const fi=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,Ep=o=>!ts(o)&&o!==fi;function gd(...o){const{caseless:e,skipUndefined:n}=Ep(this)&&this||{},r={},i=(l,u)=>{if(u==="__proto__"||u==="constructor"||u==="prototype")return;const d=e&&typeof u=="string"&&Cp(r,u)||u,f=zl(r,d)?r[d]:void 0;Ul(f)&&Ul(l)?r[d]=gd(f,l):Ul(l)?r[d]=gd({},l):di(l)?r[d]=l.slice():(!n||!ts(l))&&(r[d]=l)};for(let l=0,u=o.length;l(io(e,(i,l)=>{n&&Ln(i)?Object.defineProperty(o,l,{__proto__:null,value:mp(i,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(o,l,{__proto__:null,value:i,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:r}),o),Hw=o=>(o.charCodeAt(0)===65279&&(o=o.slice(1)),o),zw=(o,e,n,r)=>{o.prototype=Object.create(e.prototype,r),Object.defineProperty(o.prototype,"constructor",{__proto__:null,value:o,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(o,"super",{__proto__:null,value:e.prototype}),n&&Object.assign(o.prototype,n)},jw=(o,e,n,r)=>{let i,l,u;const d={};if(e=e||{},o==null)return e;do{for(i=Object.getOwnPropertyNames(o),l=i.length;l-- >0;)u=i[l],(!r||r(u,o,e))&&!d[u]&&(e[u]=o[u],d[u]=!0);o=n!==!1&&es(o)}while(o&&(!n||n(o,e))&&o!==Object.prototype);return e},Uw=(o,e,n)=>{o=String(o),(n===void 0||n>o.length)&&(n=o.length),n-=e.length;const r=o.indexOf(e,n);return r!==-1&&r===n},Bw=o=>{if(!o)return null;if(di(o))return o;let e=o.length;if(!vp(e))return null;const n=new Array(e);for(;e-- >0;)n[e]=o[e];return n},Pw=(o=>e=>o&&e instanceof o)(typeof Uint8Array<"u"&&es(Uint8Array)),qw=(o,e)=>{const r=(o&&o[ao]).call(o);let i;for(;(i=r.next())&&!i.done;){const l=i.value;e.call(o,l[0],l[1])}},Gw=(o,e)=>{let n;const r=[];for(;(n=o.exec(e))!==null;)r.push(n);return r},Vw=ha("HTMLFormElement"),Fw=o=>o.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(n,r,i){return r.toUpperCase()+i}),{propertyIsEnumerable:Kw}=Object.prototype,Ww=ha("RegExp"),_p=(o,e)=>{const n=Object.getOwnPropertyDescriptors(o),r={};io(n,(i,l)=>{let u;(u=e(i,l,o))!==!1&&(r[l]=u||i)}),Object.defineProperties(o,r)},Yw=o=>{_p(o,(e,n)=>{if(Ln(o)&&["arguments","caller","callee"].includes(n))return!1;const r=o[n];if(Ln(r)){if(e.enumerable=!1,"writable"in e){e.writable=!1;return}e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+n+"'")})}})},Xw=(o,e)=>{const n={},r=i=>{i.forEach(l=>{n[l]=!0})};return di(o)?r(o):r(String(o).split(e)),n},Jw=()=>{},Qw=(o,e)=>o!=null&&Number.isFinite(o=+o)?o:e;function Zw(o){return!!(o&&Ln(o.append)&&o[yp]==="FormData"&&o[ao])}const e0=o=>{const e=new WeakSet,n=r=>{if(as(r)){if(e.has(r))return;if(ns(r))return r;if(!("toJSON"in r)){e.add(r);const i=di(r)?[]:{};return io(r,(l,u)=>{const d=n(l);!ts(d)&&(i[u]=d)}),e.delete(r),i}}return r};return n(o)},t0=ha("AsyncFunction"),n0=o=>o&&(as(o)||Ln(o))&&Ln(o.then)&&Ln(o.catch),Ap=((o,e)=>o?setImmediate:e?((n,r)=>(fi.addEventListener("message",({source:i,data:l})=>{i===fi&&l===n&&r.length&&r.shift()()},!1),i=>{r.push(i),fi.postMessage(n,"*")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate=="function",Ln(fi.postMessage)),a0=typeof queueMicrotask<"u"?queueMicrotask.bind(fi):typeof process<"u"&&process.nextTick||Ap,xp=o=>o!=null&&Ln(o[ao]),V={isArray:di,isArrayBuffer:bp,isBuffer:ns,isFormData:kw,isArrayBufferView:yw,isString:bw,isNumber:vp,isBoolean:vw,isObject:as,isPlainObject:Ul,isEmptyObject:Sw,isReadableStream:Ow,isRequest:Lw,isResponse:Mw,isHeaders:$w,isUndefined:ts,isDate:ww,isFile:Cw,isReactNativeBlob:Ew,isReactNative:_w,isBlob:Aw,isRegExp:Ww,isFunction:Ln,isStream:Tw,isURLSearchParams:Dw,isTypedArray:Pw,isFileList:xw,forEach:io,merge:gd,extend:Iw,trim:Nw,stripBOM:Hw,inherits:zw,toFlatObject:jw,kindOf:hd,kindOfTest:ha,endsWith:Uw,toArray:Bw,forEachEntry:qw,matchAll:Gw,isHTMLForm:Vw,hasOwnProperty:zl,hasOwnProp:zl,hasOwnInPrototypeChain:ro,getSafeProp:mw,reduceDescriptors:_p,freezeMethods:Yw,toObjectSet:Xw,toCamelCase:Fw,noop:Jw,toFiniteNumber:Qw,findKey:Cp,global:fi,isContextDefined:Ep,isSpecCompliantForm:Zw,toJSONObject:e0,isAsyncFn:t0,isThenable:n0,setImmediate:Ap,asap:a0,isIterable:xp,isSafeIterable:o=>o!=null&&ro(o,ao)&&xp(o)},r0=V.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),i0=o=>{const e={};let n,r,i;return o&&o.split(` -`).forEach(function(u){i=u.indexOf(":"),n=u.substring(0,i).trim().toLowerCase(),r=u.substring(i+1).trim(),!(!n||e[n]&&r0[n])&&(n==="set-cookie"?e[n]?e[n].push(r):e[n]=[r]:e[n]=e[n]?e[n]+", "+r:r)}),e};function s0(o){let e=0,n=o.length;for(;ee;){const r=o.charCodeAt(n-1);if(r!==9&&r!==32)break;n-=1}return e===0&&n===o.length?o:o.slice(e,n)}const o0=new RegExp("[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+","g"),l0=new RegExp("[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+","g");function pd(o,e){return V.isArray(o)?o.map(n=>pd(n,e)):s0(String(o).replace(e,""))}const c0=o=>pd(o,o0),u0=o=>pd(o,l0);function Tp(o){const e=Object.create(null);return V.forEach(o.toJSON(),(n,r)=>{e[r]=u0(n)}),e}const Rp=Symbol("internals");function so(o){return o&&String(o).trim().toLowerCase()}function Bl(o){return o===!1||o==null?o:V.isArray(o)?o.map(Bl):c0(String(o))}function d0(o){const e=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let r;for(;r=n.exec(o);)e[r[1]]=r[2];return e}const f0=o=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(o.trim());function md(o,e,n,r,i){if(V.isFunction(r))return r.call(this,e,n);if(i&&(e=n),!!V.isString(e)){if(V.isString(r))return e.indexOf(r)!==-1;if(V.isRegExp(r))return r.test(e)}}function h0(o){return o.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(e,n,r)=>n.toUpperCase()+r)}function g0(o,e){const n=V.toCamelCase(" "+e);["get","set","has"].forEach(r=>{Object.defineProperty(o,r+n,{__proto__:null,value:function(i,l,u){return this[r].call(this,e,i,l,u)},configurable:!0})})}let Cn=class{constructor(e){e&&this.set(e)}set(e,n,r){const i=this;function l(d,f,g){const m=so(f);if(!m)return;const b=V.findKey(i,m);(!b||i[b]===void 0||g===!0||g===void 0&&i[b]!==!1)&&(i[b||f]=Bl(d))}const u=(d,f)=>V.forEach(d,(g,m)=>l(g,m,f));if(V.isPlainObject(e)||e instanceof this.constructor)u(e,n);else if(V.isString(e)&&(e=e.trim())&&!f0(e))u(i0(e),n);else if(V.isObject(e)&&V.isSafeIterable(e)){let d=Object.create(null),f,g;for(const m of e){if(!V.isArray(m))throw new TypeError("Object iterator must return a key-value pair");g=m[0],V.hasOwnProp(d,g)?(f=d[g],d[g]=V.isArray(f)?[...f,m[1]]:[f,m[1]]):d[g]=m[1]}u(d,n)}else e!=null&&l(n,e,r);return this}get(e,n){if(e=so(e),e){const r=V.findKey(this,e);if(r){const i=this[r];if(!n)return i;if(n===!0)return d0(i);if(V.isFunction(n))return n.call(this,i,r);if(V.isRegExp(n))return n.exec(i);throw new TypeError("parser must be boolean|regexp|function")}}}has(e,n){if(e=so(e),e){const r=V.findKey(this,e);return!!(r&&this[r]!==void 0&&(!n||md(this,this[r],r,n)))}return!1}delete(e,n){const r=this;let i=!1;function l(u){if(u=so(u),u){const d=V.findKey(r,u);d&&(!n||md(r,r[d],d,n))&&(delete r[d],i=!0)}}return V.isArray(e)?e.forEach(l):l(e),i}clear(e){const n=Object.keys(this);let r=n.length,i=!1;for(;r--;){const l=n[r];(!e||md(this,this[l],l,e,!0))&&(delete this[l],i=!0)}return i}normalize(e){const n=this,r={};return V.forEach(this,(i,l)=>{const u=V.findKey(r,l);if(u){n[u]=Bl(i),delete n[l];return}const d=e?h0(l):String(l).trim();d!==l&&delete n[l],n[d]=Bl(i),r[d]=!0}),this}concat(...e){return this.constructor.concat(this,...e)}toJSON(e){const n=Object.create(null);return V.forEach(this,(r,i)=>{r!=null&&r!==!1&&(n[i]=e&&V.isArray(r)?r.join(", "):r)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([e,n])=>e+": "+n).join(` -`)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(e){return e instanceof this?e:new this(e)}static concat(e,...n){const r=new this(e);return n.forEach(i=>r.set(i)),r}static accessor(e){const r=(this[Rp]=this[Rp]={accessors:{}}).accessors,i=this.prototype;function l(u){const d=so(u);r[d]||(g0(i,u),r[d]=!0)}return V.isArray(e)?e.forEach(l):l(e),this}};Cn.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]),V.reduceDescriptors(Cn.prototype,({value:o},e)=>{let n=e[0].toUpperCase()+e.slice(1);return{get:()=>o,set(r){this[n]=r}}}),V.freezeMethods(Cn);const p0="[REDACTED ****]";function m0(o){if(V.hasOwnProp(o,"toJSON"))return!0;let e=Object.getPrototypeOf(o);for(;e&&e!==Object.prototype;){if(V.hasOwnProp(e,"toJSON"))return!0;e=Object.getPrototypeOf(e)}return!1}function y0(o,e){const n=new Set(e.map(l=>String(l).toLowerCase())),r=[],i=l=>{if(l===null||typeof l!="object"||V.isBuffer(l))return l;if(r.indexOf(l)!==-1)return;l instanceof Cn&&(l=l.toJSON()),r.push(l);let u;if(V.isArray(l))u=[],l.forEach((d,f)=>{const g=i(d);V.isUndefined(g)||(u[f]=g)});else{if(!V.isPlainObject(l)&&m0(l))return r.pop(),l;u=Object.create(null);for(const[d,f]of Object.entries(l)){const g=n.has(d.toLowerCase())?p0:i(f);V.isUndefined(g)||(u[d]=g)}}return r.pop(),u};return i(o)}let Ce=class IS extends Error{static from(e,n,r,i,l,u){const d=new IS(e.message,n||e.code,r,i,l);return Object.defineProperty(d,"cause",{__proto__:null,value:e,writable:!0,enumerable:!1,configurable:!0}),d.name=e.name,e.status!=null&&d.status==null&&(d.status=e.status),u&&Object.assign(d,u),d}constructor(e,n,r,i,l){super(e),Object.defineProperty(this,"message",{__proto__:null,value:e,enumerable:!0,writable:!0,configurable:!0}),this.name="AxiosError",this.isAxiosError=!0,n&&(this.code=n),r&&(this.config=r),i&&(this.request=i),l&&(this.response=l,this.status=l.status)}toJSON(){const e=this.config,n=e&&V.hasOwnProp(e,"redact")?e.redact:void 0,r=V.isArray(n)&&n.length>0?y0(e,n):V.toJSONObject(e);return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:r,code:this.code,status:this.status}}};Ce.ERR_BAD_OPTION_VALUE="ERR_BAD_OPTION_VALUE",Ce.ERR_BAD_OPTION="ERR_BAD_OPTION",Ce.ECONNABORTED="ECONNABORTED",Ce.ETIMEDOUT="ETIMEDOUT",Ce.ECONNREFUSED="ECONNREFUSED",Ce.ERR_NETWORK="ERR_NETWORK",Ce.ERR_FR_TOO_MANY_REDIRECTS="ERR_FR_TOO_MANY_REDIRECTS",Ce.ERR_DEPRECATED="ERR_DEPRECATED",Ce.ERR_BAD_RESPONSE="ERR_BAD_RESPONSE",Ce.ERR_BAD_REQUEST="ERR_BAD_REQUEST",Ce.ERR_CANCELED="ERR_CANCELED",Ce.ERR_NOT_SUPPORT="ERR_NOT_SUPPORT",Ce.ERR_INVALID_URL="ERR_INVALID_URL",Ce.ERR_FORM_DATA_DEPTH_EXCEEDED="ERR_FORM_DATA_DEPTH_EXCEEDED";const b0=null,kp=100;function yd(o){return V.isPlainObject(o)||V.isArray(o)}function Dp(o){return V.endsWith(o,"[]")?o.slice(0,-2):o}function bd(o,e,n){return o?o.concat(e).map(function(i,l){return i=Dp(i),!n&&l?"["+i+"]":i}).join(n?".":""):e}function v0(o){return V.isArray(o)&&!o.some(yd)}const S0=V.toFlatObject(V,{},null,function(e){return/^is[A-Z]/.test(e)});function Pl(o,e,n){if(!V.isObject(o))throw new TypeError("target must be an object");e=e||new FormData,n=V.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(M,T){return!V.isUndefined(T[M])});const r=n.metaTokens,i=n.visitor||_,l=n.dots,u=n.indexes,d=n.Blob||typeof Blob<"u"&&Blob,f=n.maxDepth===void 0?kp:n.maxDepth,g=d&&V.isSpecCompliantForm(e),m=[];if(!V.isFunction(i))throw new TypeError("visitor must be a function");function b(O){if(O===null)return"";if(V.isDate(O))return O.toISOString();if(V.isBoolean(O))return O.toString();if(!g&&V.isBlob(O))throw new Ce("Blob is not supported. Use a Buffer instead.");if(V.isArrayBuffer(O)||V.isTypedArray(O)){if(g&&typeof d=="function")return new d([O]);if(typeof Buffer<"u")return Buffer.from(O);throw new Ce("Blob is not supported. Use a Buffer instead.",Ce.ERR_NOT_SUPPORT)}return O}function v(O){if(O>f)throw new Ce("Object is too deeply nested ("+O+" levels). Max depth: "+f,Ce.ERR_FORM_DATA_DEPTH_EXCEEDED)}function S(O,M){if(f===1/0)return JSON.stringify(O);const T=[];return JSON.stringify(O,function(j,H){if(!V.isObject(H))return H;for(;T.length&&T[T.length-1]!==this;)T.pop();return T.push(H),v(M+T.length-1),H})}function _(O,M,T){let D=O;if(V.isReactNative(e)&&V.isReactNativeBlob(O))return e.append(bd(T,M,l),b(O)),!1;if(O&&!T&&typeof O=="object"){if(V.endsWith(M,"{}"))M=r?M:M.slice(0,-2),O=S(O,1);else if(V.isArray(O)&&v0(O)||(V.isFileList(O)||V.endsWith(M,"[]"))&&(D=V.toArray(O)))return M=Dp(M),D.forEach(function(H,P){!(V.isUndefined(H)||H===null)&&e.append(u===!0?bd([M],P,l):u===null?M:M+"[]",b(H))}),!1}return yd(O)?!0:(e.append(bd(T,M,l),b(O)),!1)}const x=Object.assign(S0,{defaultVisitor:_,convertValue:b,isVisitable:yd});function k(O,M,T=0){if(!V.isUndefined(O)){if(v(T),m.indexOf(O)!==-1)throw new Error("Circular reference detected in "+M.join("."));m.push(O),V.forEach(O,function(j,H){(!(V.isUndefined(j)||j===null)&&i.call(e,j,V.isString(H)?H.trim():H,M,x))===!0&&k(j,M?M.concat(H):[H],T+1)}),m.pop()}}if(!V.isObject(o))throw new TypeError("data must be an object");return k(o),e}function Op(o){const e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"};return encodeURIComponent(o).replace(/[!'()~]|%20/g,function(r){return e[r]})}function vd(o,e){this._pairs=[],o&&Pl(o,this,e)}const Lp=vd.prototype;Lp.append=function(e,n){this._pairs.push([e,n])},Lp.toString=function(e){const n=e?r=>e.call(this,r,Op):Op;return this._pairs.map(function(i){return n(i[0])+"="+n(i[1])},"").join("&")};function w0(o){return encodeURIComponent(o).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function Mp(o,e,n){if(!e)return o;o=o||"";const r=V.isFunction(n)?{serialize:n}:n,i=V.getSafeProp(r,"encode")||w0,l=V.getSafeProp(r,"serialize");let u;if(l?u=l(e,r):u=V.isURLSearchParams(e)?e.toString():new vd(e,r).toString(i),u){const d=o.indexOf("#");d!==-1&&(o=o.slice(0,d)),o+=(o.indexOf("?")===-1?"?":"&")+u}return o}class $p{constructor(){this.handlers=[]}use(e,n,r){return this.handlers.push({fulfilled:e,rejected:n,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null}),this.handlers.length-1}eject(e){this.handlers[e]&&(this.handlers[e]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(e){V.forEach(this.handlers,function(r){r!==null&&e(r)})}}const Sd={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0,advertiseZstdAcceptEncoding:!1,validateStatusUndefinedResolves:!0},C0={isBrowser:!0,classes:{URLSearchParams:typeof URLSearchParams<"u"?URLSearchParams:vd,FormData:typeof FormData<"u"?FormData:null,Blob:typeof Blob<"u"?Blob:null},protocols:["http","https","file","blob","url","data"]},wd=typeof window<"u"&&typeof document<"u",Cd=typeof navigator=="object"&&navigator||void 0,E0=wd&&(!Cd||["ReactNative","NativeScript","NS"].indexOf(Cd.product)<0),_0=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",A0=wd&&window.location.href||"http://localhost",pn={...Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:wd,hasStandardBrowserEnv:E0,hasStandardBrowserWebWorkerEnv:_0,navigator:Cd,origin:A0},Symbol.toStringTag,{value:"Module"})),...C0};function x0(o,e){return Pl(o,new pn.classes.URLSearchParams,{visitor:function(n,r,i,l){return pn.isNode&&V.isBuffer(n)?(this.append(r,n.toString("base64")),!1):l.defaultVisitor.apply(this,arguments)},...e})}const Np=kp;function Ip(o){if(o>Np)throw new Ce("FormData field is too deeply nested ("+o+" levels). Max depth: "+Np,Ce.ERR_FORM_DATA_DEPTH_EXCEEDED)}function T0(o){const e=[],n=/\w+|\[(\w*)]/g;let r;for(;(r=n.exec(o))!==null;)Ip(e.length),e.push(r[0]==="[]"?"":r[1]||r[0]);return e}function R0(o){const e={},n=Object.keys(o);let r;const i=n.length;let l;for(r=0;r=n.length;return u=!u&&V.isArray(i)?i.length:u,f?(V.hasOwnProp(i,u)?i[u]=V.isArray(i[u])?i[u].concat(r):[i[u],r]:i[u]=r,!d):((!V.hasOwnProp(i,u)||!V.isObject(i[u]))&&(i[u]=[]),e(n,r,i[u],l)&&V.isArray(i[u])&&(i[u]=R0(i[u])),!d)}if(V.isFormData(o)&&V.isFunction(o.entries)){const n={};return V.forEachEntry(o,(r,i)=>{e(T0(r),i,n,0)}),n}return null}const rs=(o,e)=>o!=null&&V.hasOwnProp(o,e)?o[e]:void 0;function k0(o,e,n){if(V.isString(o))try{return(e||JSON.parse)(o),V.trim(o)}catch(r){if(r.name!=="SyntaxError")throw r}return(n||JSON.stringify)(o)}const oo={transitional:Sd,adapter:["xhr","http","fetch"],transformRequest:[function(e,n){const r=n.getContentType()||"",i=r.indexOf("application/json")>-1,l=V.isObject(e);if(l&&V.isHTMLForm(e)&&(e=new FormData(e)),V.isFormData(e))return i?JSON.stringify(Hp(e)):e;if(V.isArrayBuffer(e)||V.isBuffer(e)||V.isStream(e)||V.isFile(e)||V.isBlob(e)||V.isReadableStream(e))return e;if(V.isArrayBufferView(e))return e.buffer;if(V.isURLSearchParams(e))return n.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();let d;if(l){const f=rs(this,"formSerializer");if(r.indexOf("application/x-www-form-urlencoded")>-1)return x0(e,f).toString();if((d=V.isFileList(e))||r.indexOf("multipart/form-data")>-1){const g=rs(this,"env"),m=g&&g.FormData;return Pl(d?{"files[]":e}:e,m&&new m,f)}}return l||i?(n.setContentType("application/json",!1),k0(e)):e}],transformResponse:[function(e){const n=rs(this,"transitional")||oo.transitional,r=n&&n.forcedJSONParsing,i=rs(this,"responseType"),l=i==="json";if(V.isResponse(e)||V.isReadableStream(e))return e;if(e&&V.isString(e)&&(r&&!i||l)){const d=!(n&&n.silentJSONParsing)&&l;try{return JSON.parse(e,rs(this,"parseReviver"))}catch(f){if(d)throw f.name==="SyntaxError"?Ce.from(f,Ce.ERR_BAD_RESPONSE,this,null,rs(this,"response")):f}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:pn.classes.FormData,Blob:pn.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};V.forEach(["delete","get","head","post","put","patch","query"],o=>{oo.headers[o]={}});function Ed(o,e){const n=this||oo,r=e||n,i=Cn.from(r.headers);let l=r.data;return V.forEach(o,function(d){l=d.call(n,l,i.normalize(),e?e.status:void 0)}),i.normalize(),l}function zp(o){return!!(o&&o.__CANCEL__)}let lo=class extends Ce{constructor(e,n,r){super(e??"canceled",Ce.ERR_CANCELED,n,r),this.name="CanceledError",this.__CANCEL__=!0}};function jp(o,e,n){const r=n.config.validateStatus;!n.status||!r||r(n.status)?o(n):e(new Ce("Request failed with status code "+n.status,n.status>=400&&n.status<500?Ce.ERR_BAD_REQUEST:Ce.ERR_BAD_RESPONSE,n.config,n.request,n))}function D0(o){const e=/^([-+\w]{1,25}):(?:\/\/)?/.exec(o);return e&&e[1]||""}function O0(o,e){o=o||10;const n=new Array(o),r=new Array(o);let i=0,l=0,u;return e=e!==void 0?e:1e3,function(f){const g=Date.now(),m=r[l];u||(u=g),n[i]=f,r[i]=g;let b=l,v=0;for(;b!==i;)v+=n[b++],b=b%o;if(i=(i+1)%o,i===l&&(l=(l+1)%o),g-u{n=m,i=null,l&&(clearTimeout(l),l=null),o(...g)};return[(...g)=>{const m=Date.now(),b=m-n;b>=r?u(g,m):(i=g,l||(l=setTimeout(()=>{l=null,u(i)},r-b)))},()=>i&&u(i)]}const ql=(o,e,n=3)=>{let r=0;const i=O0(50,250);return L0(l=>{if(!l||typeof l.loaded!="number")return;const u=l.loaded,d=l.lengthComputable?l.total:void 0,f=d!=null?Math.min(u,d):u,g=Math.max(0,f-r),m=i(g);r=Math.max(r,f);const b={loaded:f,total:d,progress:d?f/d:void 0,bytes:g,rate:m||void 0,estimated:m&&d?(d-f)/m:void 0,event:l,lengthComputable:d!=null,[e?"download":"upload"]:!0};o(b)},n)},Up=(o,e)=>{const n=o!=null;return[r=>e[0]({lengthComputable:n,total:o,loaded:r}),e[1]]},Bp=o=>(...e)=>V.asap(()=>o(...e)),M0=pn.hasStandardBrowserEnv?((o,e)=>n=>(n=new URL(n,pn.origin),o.protocol===n.protocol&&o.host===n.host&&(e||o.port===n.port)))(new URL(pn.origin),pn.navigator&&/(msie|trident)/i.test(pn.navigator.userAgent)):()=>!0,$0=pn.hasStandardBrowserEnv?{write(o,e,n,r,i,l,u){if(typeof document>"u")return;const d=[`${o}=${encodeURIComponent(e)}`];V.isNumber(n)&&d.push(`expires=${new Date(n).toUTCString()}`),V.isString(r)&&d.push(`path=${r}`),V.isString(i)&&d.push(`domain=${i}`),l===!0&&d.push("secure"),V.isString(u)&&d.push(`SameSite=${u}`),document.cookie=d.join("; ")},read(o){if(typeof document>"u")return null;const e=document.cookie.split(";");for(let n=0;no instanceof Cn?{...o}:o;function hi(o,e){o=o||{},e=e||{};const n=Object.create(null);Object.defineProperty(n,"hasOwnProperty",{__proto__:null,value:Object.prototype.hasOwnProperty,enumerable:!1,writable:!0,configurable:!0});function r(m,b,v,S){return V.isPlainObject(m)&&V.isPlainObject(b)?V.merge.call({caseless:S},m,b):V.isPlainObject(b)?V.merge({},b):V.isArray(b)?b.slice():b}function i(m,b,v,S){if(V.isUndefined(b)){if(!V.isUndefined(m))return r(void 0,m,v,S)}else return r(m,b,v,S)}function l(m,b){if(!V.isUndefined(b))return r(void 0,b)}function u(m,b){if(V.isUndefined(b)){if(!V.isUndefined(m))return r(void 0,m)}else return r(void 0,b)}function d(m){const b=V.hasOwnProp(e,"transitional")?e.transitional:void 0;if(!V.isUndefined(b))if(V.isPlainObject(b)){if(V.hasOwnProp(b,m))return b[m]}else return;const v=V.hasOwnProp(o,"transitional")?o.transitional:void 0;if(V.isPlainObject(v)&&V.hasOwnProp(v,m))return v[m]}function f(m,b,v){if(V.hasOwnProp(e,v))return r(m,b);if(V.hasOwnProp(o,v))return r(void 0,m)}const g={url:l,method:l,data:l,baseURL:u,transformRequest:u,transformResponse:u,paramsSerializer:u,timeout:u,timeoutMessage:u,withCredentials:u,withXSRFToken:u,adapter:u,responseType:u,xsrfCookieName:u,xsrfHeaderName:u,onUploadProgress:u,onDownloadProgress:u,decompress:u,maxContentLength:u,maxBodyLength:u,beforeRedirect:u,transport:u,httpAgent:u,httpsAgent:u,cancelToken:u,socketPath:u,allowedSocketPaths:u,responseEncoding:u,validateStatus:f,headers:(m,b,v)=>i(Gp(m),Gp(b),v,!0)};return V.forEach(Object.keys({...o,...e}),function(b){if(b==="__proto__"||b==="constructor"||b==="prototype")return;const v=V.hasOwnProp(g,b)?g[b]:i,S=V.hasOwnProp(o,b)?o[b]:void 0,_=V.hasOwnProp(e,b)?e[b]:void 0,x=v(S,_,b);V.isUndefined(x)&&v!==f||(n[b]=x)}),V.hasOwnProp(e,"validateStatus")&&V.isUndefined(e.validateStatus)&&d("validateStatusUndefinedResolves")===!1&&(V.hasOwnProp(o,"validateStatus")?n.validateStatus=r(void 0,o.validateStatus):delete n.validateStatus),n}const B0=["content-type","content-length"];function P0(o,e,n){if(n!=="content-only"){o.set(e);return}Object.entries(e||{}).forEach(([r,i])=>{B0.includes(r.toLowerCase())&&o.set(r,i)})}const q0=o=>encodeURIComponent(o).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16)));function Vp(o){const e=hi({},o),n=v=>V.hasOwnProp(e,v)?e[v]:void 0,r=n("data");let i=n("withXSRFToken");const l=n("xsrfHeaderName"),u=n("xsrfCookieName");let d=n("headers");const f=n("auth"),g=n("baseURL"),m=n("allowAbsoluteUrls"),b=n("url");if(e.headers=d=Cn.from(d),e.url=Mp(qp(g,b,m,e),n("params"),n("paramsSerializer")),f){const v=V.getSafeProp(f,"username")||"",S=V.getSafeProp(f,"password")||"";try{d.set("Authorization","Basic "+btoa(v+":"+(S?q0(S):"")))}catch(_){throw Ce.from(_,Ce.ERR_BAD_OPTION_VALUE,o)}}if(V.isFormData(r)&&(pn.hasStandardBrowserEnv||pn.hasStandardBrowserWebWorkerEnv||V.isReactNative(r)?d.setContentType(void 0):V.isFunction(r.getHeaders)&&P0(d,r.getHeaders(),n("formDataHeaderPolicy"))),pn.hasStandardBrowserEnv&&(V.isFunction(i)&&(i=i(e)),i===!0||i==null&&M0(e.url))){const S=l&&u&&$0.read(u);S&&d.set(l,S)}return e}const G0=typeof XMLHttpRequest<"u"&&function(o){return new Promise(function(n,r){const i=Vp(o);let l=i.data;const u=Cn.from(i.headers).normalize();let{responseType:d,onUploadProgress:f,onDownloadProgress:g}=i,m,b,v,S,_;function x(){S&&S(),_&&_(),i.cancelToken&&i.cancelToken.unsubscribe(m),i.signal&&i.signal.removeEventListener("abort",m)}let k=new XMLHttpRequest;k.open(i.method.toUpperCase(),i.url,!0),k.timeout=i.timeout;function O(){if(!k)return;const T=Cn.from("getAllResponseHeaders"in k&&k.getAllResponseHeaders()),j={data:!d||d==="text"||d==="json"?k.responseText:k.response,status:k.status,statusText:k.statusText,headers:T,config:o,request:k};jp(function(P){n(P),x()},function(P){r(P),x()},j),k=null}"onloadend"in k?k.onloadend=O:k.onreadystatechange=function(){!k||k.readyState!==4||k.status===0&&!(k.responseURL&&k.responseURL.startsWith("file:"))||setTimeout(O)},k.onabort=function(){k&&(r(new Ce("Request aborted",Ce.ECONNABORTED,o,k)),x(),k=null)},k.onerror=function(D){const j=D&&D.message?D.message:"Network Error",H=new Ce(j,Ce.ERR_NETWORK,o,k);H.event=D||null,r(H),x(),k=null},k.ontimeout=function(){let D=i.timeout?"timeout of "+i.timeout+"ms exceeded":"timeout exceeded";const j=i.transitional||Sd;i.timeoutErrorMessage&&(D=i.timeoutErrorMessage),r(new Ce(D,j.clarifyTimeoutError?Ce.ETIMEDOUT:Ce.ECONNABORTED,o,k)),x(),k=null},l===void 0&&u.setContentType(null),"setRequestHeader"in k&&V.forEach(Tp(u),function(D,j){k.setRequestHeader(j,D)}),V.isUndefined(i.withCredentials)||(k.withCredentials=!!i.withCredentials),d&&d!=="json"&&(k.responseType=i.responseType),g&&([v,_]=ql(g,!0),k.addEventListener("progress",v)),f&&k.upload&&([b,S]=ql(f),k.upload.addEventListener("progress",b),k.upload.addEventListener("loadend",S)),(i.cancelToken||i.signal)&&(m=T=>{k&&(r(!T||T.type?new lo(null,o,k):T),k.abort(),x(),k=null)},i.cancelToken&&i.cancelToken.subscribe(m),i.signal&&(i.signal.aborted?m():i.signal.addEventListener("abort",m)));const M=D0(i.url);if(M&&!pn.protocols.includes(M)){r(new Ce("Unsupported protocol "+M+":",Ce.ERR_BAD_REQUEST,o)),x();return}k.send(l||null)})},V0=(o,e)=>{if(o=o?o.filter(Boolean):[],!e&&!o.length)return;const n=new AbortController;let r=!1;const i=function(f){if(!r){r=!0,u();const g=f instanceof Error?f:this.reason;n.abort(g instanceof Ce?g:new lo(g instanceof Error?g.message:g))}};let l=e&&setTimeout(()=>{l=null,i(new Ce(`timeout of ${e}ms exceeded`,Ce.ETIMEDOUT))},e);const u=()=>{o&&(l&&clearTimeout(l),l=null,o.forEach(f=>{f.unsubscribe?f.unsubscribe(i):f.removeEventListener("abort",i)}),o=null)};o.forEach(f=>f.addEventListener("abort",i,{once:!0}));const{signal:d}=n;return d.unsubscribe=()=>V.asap(u),d},F0=function*(o,e){let n=o.byteLength;if(n{const i=K0(o,e);let l=0,u,d=f=>{u||(u=!0,r&&r(f))};return new ReadableStream({async pull(f){try{const{done:g,value:m}=await i.next();if(g){d(),f.close();return}let b=m.byteLength;if(n){let v=l+=b;n(v)}f.enqueue(new Uint8Array(m))}catch(g){throw d(g),g}},cancel(f){return d(f),i.return()}},{highWaterMark:2})},Gl=o=>o>=48&&o<=57||o>=65&&o<=70||o>=97&&o<=102,Y0=(o,e,n)=>e+2S>=2&&r.charCodeAt(S-2)===37&&r.charCodeAt(S-1)===51&&(r.charCodeAt(S)===68||r.charCodeAt(S)===100);g>=0&&(r.charCodeAt(g)===61?(f++,g--):m(g)&&(f++,g-=3)),f===1&&g>=0&&(r.charCodeAt(g)===61||m(g))&&f++;const v=Math.floor(u/4)*3-(f||0);return v>0?v:0}let l=0;for(let u=0,d=r.length;u=55296&&f<=56319&&u+1=56320&&g<=57343?(l+=4,u++):l+=3}else l+=3}return l}const _d="1.18.1",Kp=64*1024,{isFunction:Vl}=V,J0=o=>encodeURIComponent(o).replace(/%([0-9A-F]{2})/gi,(e,n)=>String.fromCharCode(parseInt(n,16))),Wp=o=>{if(!V.isString(o))return o;try{return decodeURIComponent(o)}catch{return o}},Yp=(o,...e)=>{try{return!!o(...e)}catch{return!1}},Q0=o=>{const e=o.indexOf("://");let n=o;return e!==-1&&(n=n.slice(e+3)),n.includes("@")||n.includes(":")},Z0=o=>{const e=V.global!==void 0&&V.global!==null?V.global:globalThis,{ReadableStream:n,TextEncoder:r}=e;o=V.merge.call({skipUndefined:!0},{Request:e.Request,Response:e.Response},o);const{fetch:i,Request:l,Response:u}=o,d=i?Vl(i):typeof fetch=="function",f=Vl(l),g=Vl(u);if(!d)return!1;const m=d&&Vl(n),b=d&&(typeof r=="function"?(O=>M=>O.encode(M))(new r):async O=>new Uint8Array(await new l(O).arrayBuffer())),v=f&&m&&Yp(()=>{let O=!1;const M=new l(pn.origin,{body:new n,method:"POST",get duplex(){return O=!0,"half"}}),T=M.headers.has("Content-Type");return M.body!=null&&M.body.cancel(),O&&!T}),S=g&&m&&Yp(()=>V.isReadableStream(new u("").body)),_={stream:S&&(O=>O.body)};d&&["text","arrayBuffer","blob","formData","stream"].forEach(O=>{!_[O]&&(_[O]=(M,T)=>{let D=M&&M[O];if(D)return D.call(M);throw new Ce(`Response type '${O}' is not supported`,Ce.ERR_NOT_SUPPORT,T)})});const x=async O=>{if(O==null)return 0;if(V.isBlob(O))return O.size;if(V.isSpecCompliantForm(O))return(await new l(pn.origin,{method:"POST",body:O}).arrayBuffer()).byteLength;if(V.isArrayBufferView(O)||V.isArrayBuffer(O))return O.byteLength;if(V.isURLSearchParams(O)&&(O=O+""),V.isString(O))return(await b(O)).byteLength},k=async(O,M)=>{const T=V.toFiniteNumber(O.getContentLength());return T??x(M)};return async O=>{let{url:M,method:T,data:D,signal:j,cancelToken:H,timeout:P,onDownloadProgress:X,onUploadProgress:pe,responseType:_e,headers:we,withCredentials:Me="same-origin",fetchOptions:qe,maxContentLength:Je,maxBodyLength:gt}=Vp(O);const Q=V.isNumber(Je)&&Je>-1,fe=V.isNumber(gt)&>>-1,ve=de=>V.hasOwnProp(O,de)?O[de]:void 0;let ae=i||fetch;_e=_e?(_e+"").toLowerCase():"text";let ge=V0([j,H&&H.toAbortSignal()],P),A=null;const K=ge&&ge.unsubscribe&&(()=>{ge.unsubscribe()});let ue,he=null;const le=()=>new Ce("Request body larger than maxBodyLength limit",Ce.ERR_BAD_REQUEST,O,A);try{let de;const Oe=ve("auth");if(Oe){const Ee=V.getSafeProp(Oe,"username")||"",Nt=V.getSafeProp(Oe,"password")||"";de={username:Ee,password:Nt}}if(Q0(M)){const Ee=new URL(M,pn.origin);if(!de&&(Ee.username||Ee.password)){const Nt=Wp(Ee.username),zt=Wp(Ee.password);de={username:Nt,password:zt}}(Ee.username||Ee.password)&&(Ee.username="",Ee.password="",M=Ee.href)}if(de&&(we.delete("authorization"),we.set("Authorization","Basic "+btoa(J0((de.username||"")+":"+(de.password||""))))),Q&&typeof M=="string"&&M.startsWith("data:")&&X0(M)>Je)throw new Ce("maxContentLength size of "+Je+" exceeded",Ce.ERR_BAD_RESPONSE,O,A);if(fe&&T!=="get"&&T!=="head"){const Ee=await x(D);if(typeof Ee=="number"&&isFinite(Ee)&&(ue=Ee,Ee>gt))throw le()}const He=fe&&(V.isReadableStream(D)||V.isStream(D)),Pe=(Ee,Nt,zt)=>Fp(Ee,Kp,qt=>{if(fe&&qt>gt)throw he=le();Nt&&Nt(qt)},zt);if(v&&T!=="get"&&T!=="head"&&(pe||He)){if(ue=ue??await k(we,D),ue!==0||He){let Ee=new l(M,{method:"POST",body:D,duplex:"half"}),Nt;if(V.isFormData(D)&&(Nt=Ee.headers.get("content-type"))&&we.setContentType(Nt),Ee.body){const[zt,qt]=pe&&Up(ue,ql(Bp(pe)))||[];D=Pe(Ee.body,zt,qt)}}}else if(He&&!f&&m&&T!=="get"&&T!=="head")D=Pe(D);else if(He&&f&&!v&&T!=="get"&&T!=="head")throw new Ce("Stream request bodies are not supported by the current fetch implementation",Ce.ERR_NOT_SUPPORT,O,A);V.isString(Me)||(Me=Me?"include":"omit");const ke=f&&"credentials"in l.prototype;if(V.isFormData(D)){const Ee=we.getContentType();Ee&&/^multipart\/form-data/i.test(Ee)&&!/boundary=/i.test(Ee)&&we.delete("content-type")}we.set("User-Agent","axios/"+_d,!1);const Ot={...qe,signal:ge,method:T.toUpperCase(),headers:Tp(we.normalize()),body:D,duplex:"half",credentials:ke?Me:void 0};A=f&&new l(M,Ot);let ut=await(f?ae(A,qe):ae(M,Ot));const Ct=Cn.from(ut.headers);if(Q){const Ee=V.toFiniteNumber(Ct.getContentLength());if(Ee!=null&&Ee>Je)throw new Ce("maxContentLength size of "+Je+" exceeded",Ce.ERR_BAD_RESPONSE,O,A)}const vt=S&&(_e==="stream"||_e==="response");if(S&&ut.body&&(X||Q||vt&&K)){const Ee={};["status","statusText","headers"].forEach(hn=>{Ee[hn]=ut[hn]});const Nt=V.toFiniteNumber(Ct.getContentLength()),[zt,qt]=X&&Up(Nt,ql(Bp(X),!0))||[];let Lt=0;const _n=hn=>{if(Q&&(Lt=hn,Lt>Je))throw new Ce("maxContentLength size of "+Je+" exceeded",Ce.ERR_BAD_RESPONSE,O,A);zt&&zt(hn)};ut=new u(Fp(ut.body,Kp,_n,()=>{qt&&qt(),K&&K()}),Ee)}_e=_e||"text";let kt=await _[V.findKey(_,_e)||"text"](ut,O);if(Q&&!S&&!vt){let Ee;if(kt!=null&&(typeof kt.byteLength=="number"?Ee=kt.byteLength:typeof kt.size=="number"?Ee=kt.size:typeof kt=="string"&&(Ee=typeof r=="function"?new r().encode(kt).byteLength:kt.length)),typeof Ee=="number"&&Ee>Je)throw new Ce("maxContentLength size of "+Je+" exceeded",Ce.ERR_BAD_RESPONSE,O,A)}return!vt&&K&&K(),await new Promise((Ee,Nt)=>{jp(Ee,Nt,{data:kt,headers:Cn.from(ut.headers),status:ut.status,statusText:ut.statusText,config:O,request:A})})}catch(de){if(K&&K(),ge&&ge.aborted&&ge.reason instanceof Ce){const Oe=ge.reason;throw Oe.config=O,A&&(Oe.request=A),de!==Oe&&Object.defineProperty(Oe,"cause",{__proto__:null,value:de,writable:!0,enumerable:!1,configurable:!0}),Oe}if(he)throw A&&!he.request&&(he.request=A),he;if(de instanceof Ce)throw A&&!de.request&&(de.request=A),de;if(de&&de.name==="TypeError"&&/Load failed|fetch/i.test(de.message)){const Oe=new Ce("Network Error",Ce.ERR_NETWORK,O,A,de&&de.response);throw Object.defineProperty(Oe,"cause",{__proto__:null,value:de.cause||de,writable:!0,enumerable:!1,configurable:!0}),Oe}throw Ce.from(de,de&&de.code,O,A,de&&de.response)}}},eC=new Map,Xp=o=>{let e=o&&o.env||{};const{fetch:n,Request:r,Response:i}=e,l=[r,i,n];let u=l.length,d=u,f,g,m=eC;for(;d--;)f=l[d],g=m.get(f),g===void 0&&m.set(f,g=d?new Map:Z0(e)),m=g;return g};Xp();const Ad={http:b0,xhr:G0,fetch:{get:Xp}};V.forEach(Ad,(o,e)=>{if(o){try{Object.defineProperty(o,"name",{__proto__:null,value:e})}catch{}Object.defineProperty(o,"adapterName",{__proto__:null,value:e})}});const Jp=o=>`- ${o}`,tC=o=>V.isFunction(o)||o===null||o===!1;function nC(o,e){o=V.isArray(o)?o:[o];const{length:n}=o;let r,i;const l={};for(let u=0;u`adapter ${f} `+(g===!1?"is not supported by the environment":"is not available in the build"));let d=n?u.length>1?`since : -`+u.map(Jp).join(` -`):" "+Jp(u[0]):"as no adapter specified";throw new Ce("There is no suitable adapter to dispatch the request "+d,Ce.ERR_NOT_SUPPORT)}return i}const Qp={getAdapter:nC,adapters:Ad};function xd(o){if(o.cancelToken&&o.cancelToken.throwIfRequested(),o.signal&&o.signal.aborted)throw new lo(null,o)}function Zp(o){return xd(o),o.headers=Cn.from(o.headers),o.data=Ed.call(o,o.transformRequest),["post","put","patch"].indexOf(o.method)!==-1&&o.headers.setContentType("application/x-www-form-urlencoded",!1),Qp.getAdapter(o.adapter||oo.adapter,o)(o).then(function(r){xd(o),o.response=r;try{r.data=Ed.call(o,o.transformResponse,r)}finally{delete o.response}return r.headers=Cn.from(r.headers),r},function(r){if(!zp(r)&&(xd(o),r&&r.response)){o.response=r.response;try{r.response.data=Ed.call(o,o.transformResponse,r.response)}finally{delete o.response}r.response.headers=Cn.from(r.response.headers)}return Promise.reject(r)})}const Fl={};["object","boolean","number","function","string","symbol"].forEach((o,e)=>{Fl[o]=function(r){return typeof r===o||"a"+(e<1?"n ":" ")+o}});const em={};Fl.transitional=function(e,n,r){function i(l,u){return"[Axios v"+_d+"] Transitional option '"+l+"'"+u+(r?". "+r:"")}return(l,u,d)=>{if(e===!1)throw new Ce(i(u," has been removed"+(n?" in "+n:"")),Ce.ERR_DEPRECATED);return n&&!em[u]&&(em[u]=!0,console.warn(i(u," has been deprecated since v"+n+" and will be removed in the near future"))),e?e(l,u,d):!0}},Fl.spelling=function(e){return(n,r)=>(console.warn(`${r} is likely a misspelling of ${e}`),!0)};function aC(o,e,n){if(typeof o!="object"||o===null)throw new Ce("options must be an object",Ce.ERR_BAD_OPTION_VALUE);const r=Object.keys(o);let i=r.length;for(;i-- >0;){const l=r[i],u=Object.prototype.hasOwnProperty.call(e,l)?e[l]:void 0;if(u){const d=o[l],f=d===void 0||u(d,l,o);if(f!==!0)throw new Ce("option "+l+" must be "+f,Ce.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new Ce("Unknown option "+l,Ce.ERR_BAD_OPTION)}}const Kl={assertOptions:aC,validators:Fl},En=Kl.validators;let gi=class{constructor(e){this.defaults=e||{},this.interceptors={request:new $p,response:new $p}}async request(e,n){try{return await this._request(e,n)}catch(r){if(r instanceof Error){let i={};Error.captureStackTrace?Error.captureStackTrace(i):i=new Error;const l=(()=>{if(!i.stack)return"";const u=i.stack.indexOf(` +`+c.stack}}var Dn=Object.prototype.hasOwnProperty,jt=o.unstable_scheduleCallback,On=o.unstable_cancelCallback,ea=o.unstable_shouldYield,Ba=o.unstable_requestPaint,Kt=o.unstable_now,Dr=o.unstable_getCurrentPriorityLevel,tr=o.unstable_ImmediatePriority,bn=o.unstable_UserBlockingPriority,tn=o.unstable_NormalPriority,et=o.unstable_LowPriority,ta=o.unstable_IdlePriority,En=o.log,nr=o.unstable_setDisableYieldValue,Ea=null,nn=null;function _n(t){if(typeof En=="function"&&nr(t),nn&&typeof nn.setStrictMode=="function")try{nn.setStrictMode(Ea,t)}catch{}}var q=Math.clz32?Math.clz32:Z,oe=Math.log,ce=Math.LN2;function Z(t){return t>>>=0,t===0?32:31-(oe(t)/ce|0)|0}var be=256,pe=262144,Te=4194304;function xe(t){var n=t&42;if(n!==0)return n;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function je(t,n,s){var c=t.pendingLanes;if(c===0)return 0;var h=0,p=t.suspendedLanes,w=t.pingedLanes;t=t.warmLanes;var k=c&134217727;return k!==0?(c=k&~p,c!==0?h=xe(c):(w&=k,w!==0?h=xe(w):s||(s=k&~t,s!==0&&(h=xe(s))))):(k=c&~p,k!==0?h=xe(k):w!==0?h=xe(w):s||(s=c&~t,s!==0&&(h=xe(s)))),h===0?0:n!==0&&n!==h&&(n&p)===0&&(p=h&-h,s=n&-n,p>=s||p===32&&(s&4194048)!==0)?n:h}function ze(t,n){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&n)===0}function Le(t,n){switch(t){case 1:case 2:case 4:case 8:case 64:return n+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return n+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function ke(){var t=Te;return Te<<=1,(Te&62914560)===0&&(Te=4194304),t}function Je(t){for(var n=[],s=0;31>s;s++)n.push(t);return n}function Ve(t,n){t.pendingLanes|=n,n!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function _e(t,n,s,c,h,p){var w=t.pendingLanes;t.pendingLanes=s,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=s,t.entangledLanes&=s,t.errorRecoveryDisabledLanes&=s,t.shellSuspendCounter=0;var k=t.entanglements,z=t.expirationTimes,Y=t.hiddenUpdates;for(s=w&~s;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var Sf=/[\n"\\]/g;function $n(t){return t.replace(Sf,function(n){return"\\"+n.charCodeAt(0).toString(16)+" "})}function fs(t,n,s,c,h,p,w,k){t.name="",w!=null&&typeof w!="function"&&typeof w!="symbol"&&typeof w!="boolean"?t.type=w:t.removeAttribute("type"),n!=null?w==="number"?(n===0&&t.value===""||t.value!=n)&&(t.value=""+Sn(n)):t.value!==""+Sn(n)&&(t.value=""+Sn(n)):w!=="submit"&&w!=="reset"||t.removeAttribute("value"),n!=null?rr(t,w,Sn(n)):s!=null?rr(t,w,Sn(s)):c!=null&&t.removeAttribute("value"),h==null&&p!=null&&(t.defaultChecked=!!p),h!=null&&(t.checked=h&&typeof h!="function"&&typeof h!="symbol"),k!=null&&typeof k!="function"&&typeof k!="symbol"&&typeof k!="boolean"?t.name=""+Sn(k):t.removeAttribute("name")}function Si(t,n,s,c,h,p,w,k){if(p!=null&&typeof p!="function"&&typeof p!="symbol"&&typeof p!="boolean"&&(t.type=p),n!=null||s!=null){if(!(p!=="submit"&&p!=="reset"||n!=null)){Do(t);return}s=s!=null?""+Sn(s):"",n=n!=null?""+Sn(n):s,k||n===t.value||(t.value=n),t.defaultValue=n}c=c??h,c=typeof c!="function"&&typeof c!="symbol"&&!!c,t.checked=k?t.checked:!!c,t.defaultChecked=!!c,w!=null&&typeof w!="function"&&typeof w!="symbol"&&typeof w!="boolean"&&(t.name=w),Do(t)}function rr(t,n,s){n==="number"&&Nr(t.ownerDocument)===t||t.defaultValue===""+s||(t.defaultValue=""+s)}function Ir(t,n,s,c){if(t=t.options,n){n={};for(var h=0;h"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),No=!1;if(xa)try{var Ci={};Object.defineProperty(Ci,"passive",{get:function(){No=!0}}),window.addEventListener("test",Ci,Ci),window.removeEventListener("test",Ci,Ci)}catch{No=!1}var Ga=null,Io=null,gs=null;function Oc(){if(gs)return gs;var t,n=Io,s=n.length,c,h="value"in Ga?Ga.value:Ga.textContent,p=h.length;for(t=0;t=ne),Fe=" ",ot=!1;function pt(t,n){switch(t){case"keyup":return O.indexOf(n.keyCode)!==-1;case"keydown":return n.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Nt(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Ra=!1;function zf(t,n){switch(t){case"compositionend":return Nt(n);case"keypress":return n.which!==32?null:(ot=!0,Fe);case"textInput":return t=n.data,t===Fe&&ot?null:t;default:return null}}function zc(t,n){if(Ra)return t==="compositionend"||!I&&pt(t,n)?(t=Oc(),gs=Io=Ga=null,Ra=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(n.ctrlKey||n.altKey||n.metaKey)||n.ctrlKey&&n.altKey){if(n.char&&1=n)return{node:s,offset:n-t};t=c}e:{for(;s;){if(s.nextSibling){s=s.nextSibling;break e}s=s.parentNode}s=void 0}s=my(s)}}function by(t,n){return t&&n?t===n?!0:t&&t.nodeType===3?!1:n&&n.nodeType===3?by(t,n.parentNode):"contains"in t?t.contains(n):t.compareDocumentPosition?!!(t.compareDocumentPosition(n)&16):!1:!1}function vy(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var n=Nr(t.document);n instanceof t.HTMLIFrameElement;){try{var s=typeof n.contentWindow.location.href=="string"}catch{s=!1}if(s)t=n.contentWindow;else break;n=Nr(t.document)}return n}function qf(t){var n=t&&t.nodeName&&t.nodeName.toLowerCase();return n&&(n==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||n==="textarea"||t.contentEditable==="true")}var LA=xa&&"documentMode"in document&&11>=document.documentMode,bs=null,Gf=null,Fo=null,Vf=!1;function Sy(t,n,s){var c=s.window===s?s.document:s.nodeType===9?s:s.ownerDocument;Vf||bs==null||bs!==Nr(c)||(c=bs,"selectionStart"in c&&qf(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}),Fo&&Vo(Fo,c)||(Fo=c,c=Du(Gf,"onSelect"),0>=w,h-=w,Va=1<<32-q(n)+h|s<Qe?(rt=Ie,Ie=null):rt=Ie.sibling;var yt=J(V,Ie,W[Qe],le);if(yt===null){Ie===null&&(Ie=rt);break}t&&Ie&&yt.alternate===null&&n(V,Ie),G=p(yt,G,Qe),mt===null?qe=yt:mt.sibling=yt,mt=yt,Ie=rt}if(Qe===W.length)return s(V,Ie),lt&&lr(V,Qe),qe;if(Ie===null){for(;QeQe?(rt=Ie,Ie=null):rt=Ie.sibling;var li=J(V,Ie,yt.value,le);if(li===null){Ie===null&&(Ie=rt);break}t&&Ie&&li.alternate===null&&n(V,Ie),G=p(li,G,Qe),mt===null?qe=li:mt.sibling=li,mt=li,Ie=rt}if(yt.done)return s(V,Ie),lt&&lr(V,Qe),qe;if(Ie===null){for(;!yt.done;Qe++,yt=W.next())yt=ue(V,yt.value,le),yt!==null&&(G=p(yt,G,Qe),mt===null?qe=yt:mt.sibling=yt,mt=yt);return lt&&lr(V,Qe),qe}for(Ie=c(Ie);!yt.done;Qe++,yt=W.next())yt=ee(Ie,V,Qe,yt.value,le),yt!==null&&(t&&yt.alternate!==null&&Ie.delete(yt.key===null?Qe:yt.key),G=p(yt,G,Qe),mt===null?qe=yt:mt.sibling=yt,mt=yt);return t&&Ie.forEach(function(Qx){return n(V,Qx)}),lt&&lr(V,Qe),qe}function Rt(V,G,W,le){if(typeof W=="object"&&W!==null&&W.type===A&&W.key===null&&(W=W.props.children),typeof W=="object"&&W!==null){switch(W.$$typeof){case S:e:{for(var qe=W.key;G!==null;){if(G.key===qe){if(qe=W.type,qe===A){if(G.tag===7){s(V,G.sibling),le=h(G,W.props.children),le.return=V,V=le;break e}}else if(G.elementType===qe||typeof qe=="object"&&qe!==null&&qe.$$typeof===X&&zi(qe)===G.type){s(V,G.sibling),le=h(G,W.props),Qo(le,W),le.return=V,V=le;break e}s(V,G);break}else n(V,G);G=G.sibling}W.type===A?(le=Mi(W.props.children,V.mode,le,W.key),le.return=V,V=le):(le=Kc(W.type,W.key,W.props,null,V.mode,le),Qo(le,W),le.return=V,V=le)}return w(V);case E:e:{for(qe=W.key;G!==null;){if(G.key===qe)if(G.tag===4&&G.stateNode.containerInfo===W.containerInfo&&G.stateNode.implementation===W.implementation){s(V,G.sibling),le=h(G,W.children||[]),le.return=V,V=le;break e}else{s(V,G);break}else n(V,G);G=G.sibling}le=Qf(W,V.mode,le),le.return=V,V=le}return w(V);case X:return W=zi(W),Rt(V,G,W,le)}if(ht(W))return De(V,G,W,le);if($e(W)){if(qe=$e(W),typeof qe!="function")throw Error(r(150));return W=qe.call(W),Ge(V,G,W,le)}if(typeof W.then=="function")return Rt(V,G,eu(W),le);if(W.$$typeof===R)return Rt(V,G,Xc(V,W),le);tu(V,W)}return typeof W=="string"&&W!==""||typeof W=="number"||typeof W=="bigint"?(W=""+W,G!==null&&G.tag===6?(s(V,G.sibling),le=h(G,W),le.return=V,V=le):(s(V,G),le=Jf(W,V.mode,le),le.return=V,V=le),w(V)):s(V,G)}return function(V,G,W,le){try{Jo=0;var qe=Rt(V,G,W,le);return ks=null,qe}catch(Ie){if(Ie===Rs||Ie===Qc)throw Ie;var mt=Vn(29,Ie,null,V.mode);return mt.lanes=le,mt.return=V,mt}}}var Ui=qy(!0),Gy=qy(!1),Vr=!1;function uh(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function dh(t,n){t=t.updateQueue,n.updateQueue===t&&(n.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function Fr(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function Kr(t,n,s){var c=t.updateQueue;if(c===null)return null;if(c=c.shared,(vt&2)!==0){var h=c.pending;return h===null?n.next=n:(n.next=h.next,h.next=n),c.pending=n,n=Fc(t),Ty(t,null,s),n}return Vc(t,c,n,s),Fc(t)}function Zo(t,n,s){if(n=n.updateQueue,n!==null&&(n=n.shared,(s&4194048)!==0)){var c=n.lanes;c&=t.pendingLanes,s|=c,n.lanes=s,Ue(t,s)}}function fh(t,n){var s=t.updateQueue,c=t.alternate;if(c!==null&&(c=c.updateQueue,s===c)){var h=null,p=null;if(s=s.firstBaseUpdate,s!==null){do{var w={lane:s.lane,tag:s.tag,payload:s.payload,callback:null,next:null};p===null?h=p=w:p=p.next=w,s=s.next}while(s!==null);p===null?h=p=n:p=p.next=n}else h=p=n;s={baseState:c.baseState,firstBaseUpdate:h,lastBaseUpdate:p,shared:c.shared,callbacks:c.callbacks},t.updateQueue=s;return}t=s.lastBaseUpdate,t===null?s.firstBaseUpdate=n:t.next=n,s.lastBaseUpdate=n}var hh=!1;function el(){if(hh){var t=Ts;if(t!==null)throw t}}function tl(t,n,s,c){hh=!1;var h=t.updateQueue;Vr=!1;var p=h.firstBaseUpdate,w=h.lastBaseUpdate,k=h.shared.pending;if(k!==null){h.shared.pending=null;var z=k,Y=z.next;z.next=null,w===null?p=Y:w.next=Y,w=z;var ae=t.alternate;ae!==null&&(ae=ae.updateQueue,k=ae.lastBaseUpdate,k!==w&&(k===null?ae.firstBaseUpdate=Y:k.next=Y,ae.lastBaseUpdate=z))}if(p!==null){var ue=h.baseState;w=0,ae=Y=z=null,k=p;do{var J=k.lane&-536870913,ee=J!==k.lane;if(ee?(at&J)===J:(c&J)===J){J!==0&&J===xs&&(hh=!0),ae!==null&&(ae=ae.next={lane:0,tag:k.tag,payload:k.payload,callback:null,next:null});e:{var De=t,Ge=k;J=n;var Rt=s;switch(Ge.tag){case 1:if(De=Ge.payload,typeof De=="function"){ue=De.call(Rt,ue,J);break e}ue=De;break e;case 3:De.flags=De.flags&-65537|128;case 0:if(De=Ge.payload,J=typeof De=="function"?De.call(Rt,ue,J):De,J==null)break e;ue=b({},ue,J);break e;case 2:Vr=!0}}J=k.callback,J!==null&&(t.flags|=64,ee&&(t.flags|=8192),ee=h.callbacks,ee===null?h.callbacks=[J]:ee.push(J))}else ee={lane:J,tag:k.tag,payload:k.payload,callback:k.callback,next:null},ae===null?(Y=ae=ee,z=ue):ae=ae.next=ee,w|=J;if(k=k.next,k===null){if(k=h.shared.pending,k===null)break;ee=k,k=ee.next,ee.next=null,h.lastBaseUpdate=ee,h.shared.pending=null}}while(!0);ae===null&&(z=ue),h.baseState=z,h.firstBaseUpdate=Y,h.lastBaseUpdate=ae,p===null&&(h.shared.lanes=0),Qr|=w,t.lanes=w,t.memoizedState=ue}}function Vy(t,n){if(typeof t!="function")throw Error(r(191,t));t.call(n)}function Fy(t,n){var s=t.callbacks;if(s!==null)for(t.callbacks=null,t=0;tp?p:8;var w=Q.T,k={};Q.T=k,Lh(t,!1,n,s);try{var z=h(),Y=Q.S;if(Y!==null&&Y(k,z),z!==null&&typeof z=="object"&&typeof z.then=="function"){var ae=BA(z,c);rl(t,n,ae,Xn(t))}else rl(t,n,c,Xn(t))}catch(ue){rl(t,n,{then:function(){},status:"rejected",reason:ue},Xn())}finally{fe.p=p,w!==null&&k.types!==null&&(w.types=k.types),Q.T=w}}function KA(){}function Dh(t,n,s,c){if(t.tag!==5)throw Error(r(476));var h=_b(t).queue;Eb(t,h,n,ve,s===null?KA:function(){return Ab(t),s(c)})}function _b(t){var n=t.memoizedState;if(n!==null)return n;n={memoizedState:ve,baseState:ve,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:fr,lastRenderedState:ve},next:null};var s={};return n.next={memoizedState:s,baseState:s,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:fr,lastRenderedState:s},next:null},t.memoizedState=n,t=t.alternate,t!==null&&(t.memoizedState=n),n}function Ab(t){var n=_b(t);n.next===null&&(n=t.alternate.memoizedState),rl(t,n.next.queue,{},Xn())}function Oh(){return hn(wl)}function xb(){return Vt().memoizedState}function Tb(){return Vt().memoizedState}function WA(t){for(var n=t.return;n!==null;){switch(n.tag){case 24:case 3:var s=Xn();t=Fr(s);var c=Kr(n,t,s);c!==null&&(jn(c,n,s),Zo(c,n,s)),n={cache:sh()},t.payload=n;return}n=n.return}}function YA(t,n,s){var c=Xn();s={lane:c,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null},du(t)?kb(n,s):(s=Yf(t,n,s,c),s!==null&&(jn(s,t,c),Db(s,n,c)))}function Rb(t,n,s){var c=Xn();rl(t,n,s,c)}function rl(t,n,s,c){var h={lane:c,revertLane:0,gesture:null,action:s,hasEagerState:!1,eagerState:null,next:null};if(du(t))kb(n,h);else{var p=t.alternate;if(t.lanes===0&&(p===null||p.lanes===0)&&(p=n.lastRenderedReducer,p!==null))try{var w=n.lastRenderedState,k=p(w,s);if(h.hasEagerState=!0,h.eagerState=k,Gn(k,w))return Vc(t,n,h,0),Dt===null&&Gc(),!1}catch{}if(s=Yf(t,n,h,c),s!==null)return jn(s,t,c),Db(s,n,c),!0}return!1}function Lh(t,n,s,c){if(c={lane:2,revertLane:ug(),gesture:null,action:c,hasEagerState:!1,eagerState:null,next:null},du(t)){if(n)throw Error(r(479))}else n=Yf(t,s,c,2),n!==null&&jn(n,t,2)}function du(t){var n=t.alternate;return t===Xe||n!==null&&n===Xe}function kb(t,n){Os=ru=!0;var s=t.pending;s===null?n.next=n:(n.next=s.next,s.next=n),t.pending=n}function Db(t,n,s){if((s&4194048)!==0){var c=n.lanes;c&=t.pendingLanes,s|=c,n.lanes=s,Ue(t,s)}}var il={readContext:hn,use:ou,useCallback:Ut,useContext:Ut,useEffect:Ut,useImperativeHandle:Ut,useLayoutEffect:Ut,useInsertionEffect:Ut,useMemo:Ut,useReducer:Ut,useRef:Ut,useState:Ut,useDebugValue:Ut,useDeferredValue:Ut,useTransition:Ut,useSyncExternalStore:Ut,useId:Ut,useHostTransitionStatus:Ut,useFormState:Ut,useActionState:Ut,useOptimistic:Ut,useMemoCache:Ut,useCacheRefresh:Ut};il.useEffectEvent=Ut;var Ob={readContext:hn,use:ou,useCallback:function(t,n){return An().memoizedState=[t,n===void 0?null:n],t},useContext:hn,useEffect:gb,useImperativeHandle:function(t,n,s){s=s!=null?s.concat([t]):null,cu(4194308,4,bb.bind(null,n,t),s)},useLayoutEffect:function(t,n){return cu(4194308,4,t,n)},useInsertionEffect:function(t,n){cu(4,2,t,n)},useMemo:function(t,n){var s=An();n=n===void 0?null:n;var c=t();if(Bi){_n(!0);try{t()}finally{_n(!1)}}return s.memoizedState=[c,n],c},useReducer:function(t,n,s){var c=An();if(s!==void 0){var h=s(n);if(Bi){_n(!0);try{s(n)}finally{_n(!1)}}}else h=n;return c.memoizedState=c.baseState=h,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:h},c.queue=t,t=t.dispatch=YA.bind(null,Xe,t),[c.memoizedState,t]},useRef:function(t){var n=An();return t={current:t},n.memoizedState=t},useState:function(t){t=Ah(t);var n=t.queue,s=Rb.bind(null,Xe,n);return n.dispatch=s,[t.memoizedState,s]},useDebugValue:Rh,useDeferredValue:function(t,n){var s=An();return kh(s,t,n)},useTransition:function(){var t=Ah(!1);return t=Eb.bind(null,Xe,t.queue,!0,!1),An().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,n,s){var c=Xe,h=An();if(lt){if(s===void 0)throw Error(r(407));s=s()}else{if(s=n(),Dt===null)throw Error(r(349));(at&127)!==0||Qy(c,n,s)}h.memoizedState=s;var p={value:s,getSnapshot:n};return h.queue=p,gb(eb.bind(null,c,p,t),[t]),c.flags|=2048,Ms(9,{destroy:void 0},Zy.bind(null,c,p,s,n),null),s},useId:function(){var t=An(),n=Dt.identifierPrefix;if(lt){var s=Fa,c=Va;s=(c&~(1<<32-q(c)-1)).toString(32)+s,n="_"+n+"R_"+s,s=iu++,0<\/script>",p=p.removeChild(p.firstChild);break;case"select":p=typeof c.is=="string"?w.createElement("select",{is:c.is}):w.createElement("select"),c.multiple?p.multiple=!0:c.size&&(p.size=c.size);break;default:p=typeof c.is=="string"?w.createElement(h,{is:c.is}):w.createElement(h)}}p[an]=n,p[vn]=c;e:for(w=n.child;w!==null;){if(w.tag===5||w.tag===6)p.appendChild(w.stateNode);else if(w.tag!==4&&w.tag!==27&&w.child!==null){w.child.return=w,w=w.child;continue}if(w===n)break e;for(;w.sibling===null;){if(w.return===null||w.return===n)break e;w=w.return}w.sibling.return=w.return,w=w.sibling}n.stateNode=p;e:switch(pn(p,h,c),h){case"button":case"input":case"select":case"textarea":c=!!c.autoFocus;break e;case"img":c=!0;break e;default:c=!1}c&&gr(n)}}return Lt(n),Fh(n,n.type,t===null?null:t.memoizedProps,n.pendingProps,s),null;case 6:if(t&&n.stateNode!=null)t.memoizedProps!==c&&gr(n);else{if(typeof c!="string"&&n.stateNode===null)throw Error(r(166));if(t=ye.current,_s(n)){if(t=n.stateNode,s=n.memoizedProps,c=null,h=fn,h!==null)switch(h.tag){case 27:case 5:c=h.memoizedProps}t[an]=n,t=!!(t.nodeValue===s||c!==null&&c.suppressHydrationWarning===!0||Xv(t.nodeValue,s)),t||qr(n,!0)}else t=Ou(t).createTextNode(c),t[an]=n,n.stateNode=t}return Lt(n),null;case 31:if(s=n.memoizedState,t===null||t.memoizedState!==null){if(c=_s(n),s!==null){if(t===null){if(!c)throw Error(r(318));if(t=n.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(r(557));t[an]=n}else $i(),(n.flags&128)===0&&(n.memoizedState=null),n.flags|=4;Lt(n),t=!1}else s=nh(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=s),t=!0;if(!t)return n.flags&256?(Kn(n),n):(Kn(n),null);if((n.flags&128)!==0)throw Error(r(558))}return Lt(n),null;case 13:if(c=n.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(h=_s(n),c!==null&&c.dehydrated!==null){if(t===null){if(!h)throw Error(r(318));if(h=n.memoizedState,h=h!==null?h.dehydrated:null,!h)throw Error(r(317));h[an]=n}else $i(),(n.flags&128)===0&&(n.memoizedState=null),n.flags|=4;Lt(n),h=!1}else h=nh(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=h),h=!0;if(!h)return n.flags&256?(Kn(n),n):(Kn(n),null)}return Kn(n),(n.flags&128)!==0?(n.lanes=s,n):(s=c!==null,t=t!==null&&t.memoizedState!==null,s&&(c=n.child,h=null,c.alternate!==null&&c.alternate.memoizedState!==null&&c.alternate.memoizedState.cachePool!==null&&(h=c.alternate.memoizedState.cachePool.pool),p=null,c.memoizedState!==null&&c.memoizedState.cachePool!==null&&(p=c.memoizedState.cachePool.pool),p!==h&&(c.flags|=2048)),s!==t&&s&&(n.child.flags|=8192),mu(n,n.updateQueue),Lt(n),null);case 4:return He(),t===null&&gg(n.stateNode.containerInfo),Lt(n),null;case 10:return ur(n.type),Lt(n),null;case 19:if(K(Gt),c=n.memoizedState,c===null)return Lt(n),null;if(h=(n.flags&128)!==0,p=c.rendering,p===null)if(h)ol(c,!1);else{if(Bt!==0||t!==null&&(t.flags&128)!==0)for(t=n.child;t!==null;){if(p=au(t),p!==null){for(n.flags|=128,ol(c,!1),t=p.updateQueue,n.updateQueue=t,mu(n,t),n.subtreeFlags=0,t=s,s=n.child;s!==null;)Ry(s,t),s=s.sibling;return re(Gt,Gt.current&1|2),lt&&lr(n,c.treeForkCount),n.child}t=t.sibling}c.tail!==null&&Kt()>wu&&(n.flags|=128,h=!0,ol(c,!1),n.lanes=4194304)}else{if(!h)if(t=au(p),t!==null){if(n.flags|=128,h=!0,t=t.updateQueue,n.updateQueue=t,mu(n,t),ol(c,!0),c.tail===null&&c.tailMode==="hidden"&&!p.alternate&&!lt)return Lt(n),null}else 2*Kt()-c.renderingStartTime>wu&&s!==536870912&&(n.flags|=128,h=!0,ol(c,!1),n.lanes=4194304);c.isBackwards?(p.sibling=n.child,n.child=p):(t=c.last,t!==null?t.sibling=p:n.child=p,c.last=p)}return c.tail!==null?(t=c.tail,c.rendering=t,c.tail=t.sibling,c.renderingStartTime=Kt(),t.sibling=null,s=Gt.current,re(Gt,h?s&1|2:s&1),lt&&lr(n,c.treeForkCount),t):(Lt(n),null);case 22:case 23:return Kn(n),ph(),c=n.memoizedState!==null,t!==null?t.memoizedState!==null!==c&&(n.flags|=8192):c&&(n.flags|=8192),c?(s&536870912)!==0&&(n.flags&128)===0&&(Lt(n),n.subtreeFlags&6&&(n.flags|=8192)):Lt(n),s=n.updateQueue,s!==null&&mu(n,s.retryQueue),s=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(s=t.memoizedState.cachePool.pool),c=null,n.memoizedState!==null&&n.memoizedState.cachePool!==null&&(c=n.memoizedState.cachePool.pool),c!==s&&(n.flags|=2048),t!==null&&K(Hi),null;case 24:return s=null,t!==null&&(s=t.memoizedState.cache),n.memoizedState.cache!==s&&(n.flags|=2048),ur(Xt),Lt(n),null;case 25:return null;case 30:return null}throw Error(r(156,n.tag))}function ex(t,n){switch(eh(n),n.tag){case 1:return t=n.flags,t&65536?(n.flags=t&-65537|128,n):null;case 3:return ur(Xt),He(),t=n.flags,(t&65536)!==0&&(t&128)===0?(n.flags=t&-65537|128,n):null;case 26:case 27:case 5:return Et(n),null;case 31:if(n.memoizedState!==null){if(Kn(n),n.alternate===null)throw Error(r(340));$i()}return t=n.flags,t&65536?(n.flags=t&-65537|128,n):null;case 13:if(Kn(n),t=n.memoizedState,t!==null&&t.dehydrated!==null){if(n.alternate===null)throw Error(r(340));$i()}return t=n.flags,t&65536?(n.flags=t&-65537|128,n):null;case 19:return K(Gt),null;case 4:return He(),null;case 10:return ur(n.type),null;case 22:case 23:return Kn(n),ph(),t!==null&&K(Hi),t=n.flags,t&65536?(n.flags=t&-65537|128,n):null;case 24:return ur(Xt),null;case 25:return null;default:return null}}function tv(t,n){switch(eh(n),n.tag){case 3:ur(Xt),He();break;case 26:case 27:case 5:Et(n);break;case 4:He();break;case 31:n.memoizedState!==null&&Kn(n);break;case 13:Kn(n);break;case 19:K(Gt);break;case 10:ur(n.type);break;case 22:case 23:Kn(n),ph(),t!==null&&K(Hi);break;case 24:ur(Xt)}}function ll(t,n){try{var s=n.updateQueue,c=s!==null?s.lastEffect:null;if(c!==null){var h=c.next;s=h;do{if((s.tag&t)===t){c=void 0;var p=s.create,w=s.inst;c=p(),w.destroy=c}s=s.next}while(s!==h)}}catch(k){Ct(n,n.return,k)}}function Xr(t,n,s){try{var c=n.updateQueue,h=c!==null?c.lastEffect:null;if(h!==null){var p=h.next;c=p;do{if((c.tag&t)===t){var w=c.inst,k=w.destroy;if(k!==void 0){w.destroy=void 0,h=n;var z=s,Y=k;try{Y()}catch(ae){Ct(h,z,ae)}}}c=c.next}while(c!==p)}}catch(ae){Ct(n,n.return,ae)}}function nv(t){var n=t.updateQueue;if(n!==null){var s=t.stateNode;try{Fy(n,s)}catch(c){Ct(t,t.return,c)}}}function av(t,n,s){s.props=Pi(t.type,t.memoizedProps),s.state=t.memoizedState;try{s.componentWillUnmount()}catch(c){Ct(t,n,c)}}function cl(t,n){try{var s=t.ref;if(s!==null){switch(t.tag){case 26:case 27:case 5:var c=t.stateNode;break;case 30:c=t.stateNode;break;default:c=t.stateNode}typeof s=="function"?t.refCleanup=s(c):s.current=c}}catch(h){Ct(t,n,h)}}function Ka(t,n){var s=t.ref,c=t.refCleanup;if(s!==null)if(typeof c=="function")try{c()}catch(h){Ct(t,n,h)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof s=="function")try{s(null)}catch(h){Ct(t,n,h)}else s.current=null}function rv(t){var n=t.type,s=t.memoizedProps,c=t.stateNode;try{e:switch(n){case"button":case"input":case"select":case"textarea":s.autoFocus&&c.focus();break e;case"img":s.src?c.src=s.src:s.srcSet&&(c.srcset=s.srcSet)}}catch(h){Ct(t,t.return,h)}}function Kh(t,n,s){try{var c=t.stateNode;Cx(c,t.type,s,n),c[vn]=n}catch(h){Ct(t,t.return,h)}}function iv(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&ai(t.type)||t.tag===4}function Wh(t){e:for(;;){for(;t.sibling===null;){if(t.return===null||iv(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&ai(t.type)||t.flags&2||t.child===null||t.tag===4)continue e;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function Yh(t,n,s){var c=t.tag;if(c===5||c===6)t=t.stateNode,n?(s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s).insertBefore(t,n):(n=s.nodeType===9?s.body:s.nodeName==="HTML"?s.ownerDocument.body:s,n.appendChild(t),s=s._reactRootContainer,s!=null||n.onclick!==null||(n.onclick=Aa));else if(c!==4&&(c===27&&ai(t.type)&&(s=t.stateNode,n=null),t=t.child,t!==null))for(Yh(t,n,s),t=t.sibling;t!==null;)Yh(t,n,s),t=t.sibling}function yu(t,n,s){var c=t.tag;if(c===5||c===6)t=t.stateNode,n?s.insertBefore(t,n):s.appendChild(t);else if(c!==4&&(c===27&&ai(t.type)&&(s=t.stateNode),t=t.child,t!==null))for(yu(t,n,s),t=t.sibling;t!==null;)yu(t,n,s),t=t.sibling}function sv(t){var n=t.stateNode,s=t.memoizedProps;try{for(var c=t.type,h=n.attributes;h.length;)n.removeAttributeNode(h[0]);pn(n,c,s),n[an]=t,n[vn]=s}catch(p){Ct(t,t.return,p)}}var pr=!1,Zt=!1,Xh=!1,ov=typeof WeakSet=="function"?WeakSet:Set,cn=null;function tx(t,n){if(t=t.containerInfo,yg=zu,t=vy(t),qf(t)){if("selectionStart"in t)var s={start:t.selectionStart,end:t.selectionEnd};else e:{s=(s=t.ownerDocument)&&s.defaultView||window;var c=s.getSelection&&s.getSelection();if(c&&c.rangeCount!==0){s=c.anchorNode;var h=c.anchorOffset,p=c.focusNode;c=c.focusOffset;try{s.nodeType,p.nodeType}catch{s=null;break e}var w=0,k=-1,z=-1,Y=0,ae=0,ue=t,J=null;t:for(;;){for(var ee;ue!==s||h!==0&&ue.nodeType!==3||(k=w+h),ue!==p||c!==0&&ue.nodeType!==3||(z=w+c),ue.nodeType===3&&(w+=ue.nodeValue.length),(ee=ue.firstChild)!==null;)J=ue,ue=ee;for(;;){if(ue===t)break t;if(J===s&&++Y===h&&(k=w),J===p&&++ae===c&&(z=w),(ee=ue.nextSibling)!==null)break;ue=J,J=ue.parentNode}ue=ee}s=k===-1||z===-1?null:{start:k,end:z}}else s=null}s=s||{start:0,end:0}}else s=null;for(bg={focusedElem:t,selectionRange:s},zu=!1,cn=n;cn!==null;)if(n=cn,t=n.child,(n.subtreeFlags&1028)!==0&&t!==null)t.return=n,cn=t;else for(;cn!==null;){switch(n=cn,p=n.alternate,t=n.flags,n.tag){case 0:if((t&4)!==0&&(t=n.updateQueue,t=t!==null?t.events:null,t!==null))for(s=0;s title"))),pn(p,c,s),p[an]=t,Yt(p),c=p;break e;case"link":var w=hS("link","href",h).get(c+(s.href||""));if(w){for(var k=0;kRt&&(w=Rt,Rt=Ge,Ge=w);var V=yy(k,Ge),G=yy(k,Rt);if(V&&G&&(ee.rangeCount!==1||ee.anchorNode!==V.node||ee.anchorOffset!==V.offset||ee.focusNode!==G.node||ee.focusOffset!==G.offset)){var W=ue.createRange();W.setStart(V.node,V.offset),ee.removeAllRanges(),Ge>Rt?(ee.addRange(W),ee.extend(G.node,G.offset)):(W.setEnd(G.node,G.offset),ee.addRange(W))}}}}for(ue=[],ee=k;ee=ee.parentNode;)ee.nodeType===1&&ue.push({element:ee,left:ee.scrollLeft,top:ee.scrollTop});for(typeof k.focus=="function"&&k.focus(),k=0;ks?32:s,Q.T=null,s=ag,ag=null;var p=ei,w=Sr;if(sn=0,zs=ei=null,Sr=0,(vt&6)!==0)throw Error(r(331));var k=vt;if(vt|=4,bv(p.current),pv(p,p.current,w,s),vt=k,pl(0,!1),nn&&typeof nn.onPostCommitFiberRoot=="function")try{nn.onPostCommitFiberRoot(Ea,p)}catch{}return!0}finally{fe.p=h,Q.T=c,Iv(t,n)}}function zv(t,n,s){n=ra(s,n),n=Ih(t.stateNode,n,2),t=Kr(t,n,2),t!==null&&(Ve(t,2),Wa(t))}function Ct(t,n,s){if(t.tag===3)zv(t,t,s);else for(;n!==null;){if(n.tag===3){zv(n,t,s);break}else if(n.tag===1){var c=n.stateNode;if(typeof n.type.getDerivedStateFromError=="function"||typeof c.componentDidCatch=="function"&&(Zr===null||!Zr.has(c))){t=ra(s,t),s=jb(2),c=Kr(n,s,2),c!==null&&(Ub(s,c,n,t),Ve(c,2),Wa(c));break}}n=n.return}}function og(t,n,s){var c=t.pingCache;if(c===null){c=t.pingCache=new rx;var h=new Set;c.set(n,h)}else h=c.get(n),h===void 0&&(h=new Set,c.set(n,h));h.has(s)||(Zh=!0,h.add(s),t=cx.bind(null,t,n,s),n.then(t,t))}function cx(t,n,s){var c=t.pingCache;c!==null&&c.delete(n),t.pingedLanes|=t.suspendedLanes&s,t.warmLanes&=~s,Dt===t&&(at&s)===s&&(Bt===4||Bt===3&&(at&62914560)===at&&300>Kt()-Su?(vt&2)===0&&js(t,0):eg|=s,Hs===at&&(Hs=0)),Wa(t)}function jv(t,n){n===0&&(n=ke()),t=Li(t,n),t!==null&&(Ve(t,n),Wa(t))}function ux(t){var n=t.memoizedState,s=0;n!==null&&(s=n.retryLane),jv(t,s)}function dx(t,n){var s=0;switch(t.tag){case 31:case 13:var c=t.stateNode,h=t.memoizedState;h!==null&&(s=h.retryLane);break;case 19:c=t.stateNode;break;case 22:c=t.stateNode._retryCache;break;default:throw Error(r(314))}c!==null&&c.delete(n),jv(t,s)}function fx(t,n){return jt(t,n)}var Tu=null,Bs=null,lg=!1,Ru=!1,cg=!1,ni=0;function Wa(t){t!==Bs&&t.next===null&&(Bs===null?Tu=Bs=t:Bs=Bs.next=t),Ru=!0,lg||(lg=!0,gx())}function pl(t,n){if(!cg&&Ru){cg=!0;do for(var s=!1,c=Tu;c!==null;){if(t!==0){var h=c.pendingLanes;if(h===0)var p=0;else{var w=c.suspendedLanes,k=c.pingedLanes;p=(1<<31-q(42|t)+1)-1,p&=h&~(w&~k),p=p&201326741?p&201326741|1:p?p|2:0}p!==0&&(s=!0,qv(c,p))}else p=at,p=je(c,c===Dt?p:0,c.cancelPendingCommit!==null||c.timeoutHandle!==-1),(p&3)===0||ze(c,p)||(s=!0,qv(c,p));c=c.next}while(s);cg=!1}}function hx(){Uv()}function Uv(){Ru=lg=!1;var t=0;ni!==0&&_x()&&(t=ni);for(var n=Kt(),s=null,c=Tu;c!==null;){var h=c.next,p=Bv(c,n);p===0?(c.next=null,s===null?Tu=h:s.next=h,h===null&&(Bs=s)):(s=c,(t!==0||(p&3)!==0)&&(Ru=!0)),c=h}sn!==0&&sn!==5||pl(t),ni!==0&&(ni=0)}function Bv(t,n){for(var s=t.suspendedLanes,c=t.pingedLanes,h=t.expirationTimes,p=t.pendingLanes&-62914561;0k)break;var ae=z.transferSize,ue=z.initiatorType;ae&&Jv(ue)&&(z=z.responseEnd,w+=ae*(z"u"?null:document;function cS(t,n,s){var c=Ps;if(c&&typeof n=="string"&&n){var h=$n(n);h='link[rel="'+t+'"][href="'+h+'"]',typeof s=="string"&&(h+='[crossorigin="'+s+'"]'),lS.has(h)||(lS.add(h),t={rel:t,crossOrigin:s,href:n},c.querySelector(h)===null&&(n=c.createElement("link"),pn(n,"link",t),Yt(n),c.head.appendChild(n)))}}function Mx(t){wr.D(t),cS("dns-prefetch",t,null)}function $x(t,n){wr.C(t,n),cS("preconnect",t,n)}function Nx(t,n,s){wr.L(t,n,s);var c=Ps;if(c&&t&&n){var h='link[rel="preload"][as="'+$n(n)+'"]';n==="image"&&s&&s.imageSrcSet?(h+='[imagesrcset="'+$n(s.imageSrcSet)+'"]',typeof s.imageSizes=="string"&&(h+='[imagesizes="'+$n(s.imageSizes)+'"]')):h+='[href="'+$n(t)+'"]';var p=h;switch(n){case"style":p=qs(t);break;case"script":p=Gs(t)}ua.has(p)||(t=b({rel:"preload",href:n==="image"&&s&&s.imageSrcSet?void 0:t,as:n},s),ua.set(p,t),c.querySelector(h)!==null||n==="style"&&c.querySelector(vl(p))||n==="script"&&c.querySelector(Sl(p))||(n=c.createElement("link"),pn(n,"link",t),Yt(n),c.head.appendChild(n)))}}function Ix(t,n){wr.m(t,n);var s=Ps;if(s&&t){var c=n&&typeof n.as=="string"?n.as:"script",h='link[rel="modulepreload"][as="'+$n(c)+'"][href="'+$n(t)+'"]',p=h;switch(c){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":p=Gs(t)}if(!ua.has(p)&&(t=b({rel:"modulepreload",href:t},n),ua.set(p,t),s.querySelector(h)===null)){switch(c){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(s.querySelector(Sl(p)))return}c=s.createElement("link"),pn(c,"link",t),Yt(c),s.head.appendChild(c)}}}function Hx(t,n,s){wr.S(t,n,s);var c=Ps;if(c&&t){var h=Mr(c).hoistableStyles,p=qs(t);n=n||"default";var w=h.get(p);if(!w){var k={loading:0,preload:null};if(w=c.querySelector(vl(p)))k.loading=5;else{t=b({rel:"stylesheet",href:t,"data-precedence":n},s),(s=ua.get(p))&&Ag(t,s);var z=w=c.createElement("link");Yt(z),pn(z,"link",t),z._p=new Promise(function(Y,ae){z.onload=Y,z.onerror=ae}),z.addEventListener("load",function(){k.loading|=1}),z.addEventListener("error",function(){k.loading|=2}),k.loading|=4,Mu(w,n,c)}w={type:"stylesheet",instance:w,count:1,state:k},h.set(p,w)}}}function zx(t,n){wr.X(t,n);var s=Ps;if(s&&t){var c=Mr(s).hoistableScripts,h=Gs(t),p=c.get(h);p||(p=s.querySelector(Sl(h)),p||(t=b({src:t,async:!0},n),(n=ua.get(h))&&xg(t,n),p=s.createElement("script"),Yt(p),pn(p,"link",t),s.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},c.set(h,p))}}function jx(t,n){wr.M(t,n);var s=Ps;if(s&&t){var c=Mr(s).hoistableScripts,h=Gs(t),p=c.get(h);p||(p=s.querySelector(Sl(h)),p||(t=b({src:t,async:!0,type:"module"},n),(n=ua.get(h))&&xg(t,n),p=s.createElement("script"),Yt(p),pn(p,"link",t),s.head.appendChild(p)),p={type:"script",instance:p,count:1,state:null},c.set(h,p))}}function uS(t,n,s,c){var h=(h=ye.current)?Lu(h):null;if(!h)throw Error(r(446));switch(t){case"meta":case"title":return null;case"style":return typeof s.precedence=="string"&&typeof s.href=="string"?(n=qs(s.href),s=Mr(h).hoistableStyles,c=s.get(n),c||(c={type:"style",instance:null,count:0,state:null},s.set(n,c)),c):{type:"void",instance:null,count:0,state:null};case"link":if(s.rel==="stylesheet"&&typeof s.href=="string"&&typeof s.precedence=="string"){t=qs(s.href);var p=Mr(h).hoistableStyles,w=p.get(t);if(w||(h=h.ownerDocument||h,w={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},p.set(t,w),(p=h.querySelector(vl(t)))&&!p._p&&(w.instance=p,w.state.loading=5),ua.has(t)||(s={rel:"preload",as:"style",href:s.href,crossOrigin:s.crossOrigin,integrity:s.integrity,media:s.media,hrefLang:s.hrefLang,referrerPolicy:s.referrerPolicy},ua.set(t,s),p||Ux(h,t,s,w.state))),n&&c===null)throw Error(r(528,""));return w}if(n&&c!==null)throw Error(r(529,""));return null;case"script":return n=s.async,s=s.src,typeof s=="string"&&n&&typeof n!="function"&&typeof n!="symbol"?(n=Gs(s),s=Mr(h).hoistableScripts,c=s.get(n),c||(c={type:"script",instance:null,count:0,state:null},s.set(n,c)),c):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,t))}}function qs(t){return'href="'+$n(t)+'"'}function vl(t){return'link[rel="stylesheet"]['+t+"]"}function dS(t){return b({},t,{"data-precedence":t.precedence,precedence:null})}function Ux(t,n,s,c){t.querySelector('link[rel="preload"][as="style"]['+n+"]")?c.loading=1:(n=t.createElement("link"),c.preload=n,n.addEventListener("load",function(){return c.loading|=1}),n.addEventListener("error",function(){return c.loading|=2}),pn(n,"link",s),Yt(n),t.head.appendChild(n))}function Gs(t){return'[src="'+$n(t)+'"]'}function Sl(t){return"script[async]"+t}function fS(t,n,s){if(n.count++,n.instance===null)switch(n.type){case"style":var c=t.querySelector('style[data-href~="'+$n(s.href)+'"]');if(c)return n.instance=c,Yt(c),c;var h=b({},s,{"data-href":s.href,"data-precedence":s.precedence,href:null,precedence:null});return c=(t.ownerDocument||t).createElement("style"),Yt(c),pn(c,"style",h),Mu(c,s.precedence,t),n.instance=c;case"stylesheet":h=qs(s.href);var p=t.querySelector(vl(h));if(p)return n.state.loading|=4,n.instance=p,Yt(p),p;c=dS(s),(h=ua.get(h))&&Ag(c,h),p=(t.ownerDocument||t).createElement("link"),Yt(p);var w=p;return w._p=new Promise(function(k,z){w.onload=k,w.onerror=z}),pn(p,"link",c),n.state.loading|=4,Mu(p,s.precedence,t),n.instance=p;case"script":return p=Gs(s.src),(h=t.querySelector(Sl(p)))?(n.instance=h,Yt(h),h):(c=s,(h=ua.get(p))&&(c=b({},s),xg(c,h)),t=t.ownerDocument||t,h=t.createElement("script"),Yt(h),pn(h,"link",c),t.head.appendChild(h),n.instance=h);case"void":return null;default:throw Error(r(443,n.type))}else n.type==="stylesheet"&&(n.state.loading&4)===0&&(c=n.instance,n.state.loading|=4,Mu(c,s.precedence,t));return n.instance}function Mu(t,n,s){for(var c=s.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),h=c.length?c[c.length-1]:null,p=h,w=0;w title"):null)}function Bx(t,n,s){if(s===1||n.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof n.precedence!="string"||typeof n.href!="string"||n.href==="")break;return!0;case"link":if(typeof n.rel!="string"||typeof n.href!="string"||n.href===""||n.onLoad||n.onError)break;return n.rel==="stylesheet"?(t=n.disabled,typeof n.precedence=="string"&&t==null):!0;case"script":if(n.async&&typeof n.async!="function"&&typeof n.async!="symbol"&&!n.onLoad&&!n.onError&&n.src&&typeof n.src=="string")return!0}return!1}function pS(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function Px(t,n,s,c){if(s.type==="stylesheet"&&(typeof c.media!="string"||matchMedia(c.media).matches!==!1)&&(s.state.loading&4)===0){if(s.instance===null){var h=qs(c.href),p=n.querySelector(vl(h));if(p){n=p._p,n!==null&&typeof n=="object"&&typeof n.then=="function"&&(t.count++,t=Nu.bind(t),n.then(t,t)),s.state.loading|=4,s.instance=p,Yt(p);return}p=n.ownerDocument||n,c=dS(c),(h=ua.get(h))&&Ag(c,h),p=p.createElement("link"),Yt(p);var w=p;w._p=new Promise(function(k,z){w.onload=k,w.onerror=z}),pn(p,"link",c),s.instance=p}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(s,n),(n=s.state.preload)&&(s.state.loading&3)===0&&(t.count++,s=Nu.bind(t),n.addEventListener("load",s),n.addEventListener("error",s))}}var Tg=0;function qx(t,n){return t.stylesheets&&t.count===0&&Hu(t,t.stylesheets),0Tg?50:800)+n);return t.unsuspend=s,function(){t.unsuspend=null,clearTimeout(c),clearTimeout(h)}}:null}function Nu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Hu(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var Iu=null;function Hu(t,n){t.stylesheets=null,t.unsuspend!==null&&(t.count++,Iu=new Map,n.forEach(Gx,t),Iu=null,Nu.call(t))}function Gx(t,n){if(!(n.state.loading&4)){var s=Iu.get(t);if(s)var c=s.get(null);else{s=new Map,Iu.set(t,s);for(var h=t.querySelectorAll("link[data-precedence],style[data-precedence]"),p=0;p"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(e){console.error(e)}}return o(),Ku.exports=IS(),Ku.exports}var Ju=HS();const Qu=Xa(Ju),Ks=[],zS=50;let Zu=!1;function qg(){try{return window.G7Core?.devTools}catch{return}}function Vi(o,e,a){const r=qg();if(Zu&&r?.isEnabled?.()){r.trackLog?.(o,e,a);return}Zu||(o==="error"||o==="warn")&&Ks.length{a.isDebugEnabled()&&console.log(e,...r),Vi("log",o,r)},warn:(...r)=>{a.isDebugEnabled()&&console.warn(e,...r),Vi("warn",o,r)},error:(...r)=>{a.isDebugEnabled()&&console.error(e,...r),Vi("error",o,r)}}}Ws.getInstance();const Fi=ct("networkResilience"),Gg=2,Vg=300,Fg=2e3,jS=15e3;function td(o){return o?.name==="AbortError"}function Kg(o){return td(o)?!1:o instanceof TypeError}let Al=!1,Wg=!1;function nd(){return Al}function US(){typeof window>"u"||Wg||(Wg=!0,window.addEventListener("pagehide",()=>{Al=!0}),window.addEventListener("pageshow",o=>{o.persisted&&(Al=!1)}),window.addEventListener("beforeunload",()=>{Al=!0}))}function Yg(o,e,a){const r=Math.min(e*Math.pow(2,o),a),i=r*.25*(Math.random()*2-1);return Math.max(0,Math.round(r+i))}function Xg(o){return new Promise(e=>setTimeout(e,o))}async function xl(o,e={}){const{retries:a=Gg,baseDelayMs:r=Vg,maxDelayMs:i=Fg,timeoutMs:l=jS,label:u=o,init:d}=e,f=a+1;let g;for(let m=0;m0?new AbortController:null;try{const E={...d};if(S){const T=d?.signal;T&&(T.aborted?S.abort():T.addEventListener("abort",()=>S.abort(),{once:!0})),E.signal=S.signal,v=setTimeout(()=>{b=!0,S.abort()},l)}return await fetch(o,E)}catch(E){if(g=E,td(E)&&!b||!(b||Kg(E)))throw E;if(nd())throw Fi.warn(`Document unloading, aborting retries: ${u}`),E;if(m===f-1)throw Fi.warn(`All ${f} attempts failed: ${u}`,E),E;const L=Yg(m,r,i);Fi.warn(`Network failure (attempt ${m+1}/${f}), retrying in ${L}ms: ${u}`),await Xg(L)}finally{v!==void 0&&clearTimeout(v)}}throw g}async function Jg(o,e={},a={}){const{retries:r=Gg,baseDelayMs:i=Vg,maxDelayMs:l=Fg,label:u=o}=a,d=r+1;for(let f=0;f{const i=document.createElement("script");i.src=o,i.async=!1;for(const[l,u]of Object.entries(e))l==="id"?i.id=u:i.setAttribute(l,u);i.onload=()=>a(),i.onerror=()=>r(new Error(`Failed to load script: ${o}`)),document.head.appendChild(i)})}const Qg="extension",Ki="extensionless",PS="file";function qS(){const o=globalThis?.G7Config;if(globalThis?.__g7AssetUrlMode===Ki)return Ki;const a=o?.assetUrlMode;return a===Ki||a===Qg?a:o?.settings?.general?.asset_url_mode===Ki?Ki:Qg}function Zg(){return qS()===Ki}function La(o,e,a,r){const i=o.replace(/\/+$/,""),l=e.replace(/^\.+/,""),u=Zg()?i:`${i}.${l}`,d=[];return r&&d.push(r.replace(/^[?&]+/,"")),a!=null&&a!==""&&d.push(`v=${a}`),d.length>0?`${u}?${d.join("&")}`:u}function Cr(o){if(!o||!Zg())return o;const e=globalThis?.location?.origin,a=e&&o.startsWith(e)?o.slice(e.length):o;if(!a.startsWith("/api/"))return o;const[r,i]=GS(a),l=r.match(/^\/api\/(modules|plugins)\/bundle\.(js|css)$/);if(l)return ep(`/api/${l[1]}/bundle/${l[2]}`,i);const u=r.match(/^\/api\/(templates|modules|plugins)\/assets\/([^/]+)\/(.+)$/);if(u){const[,f,g,m]=u,b=`${PS}=${encodeURIComponent(decodeURIComponent(m))}`;return`/api/${f}/assets/${g}?${b}${i?`&${i}`:""}`}const d=r.match(/^(\/api\/.+)\.(json|js|css)$/);return d?ep(d[1],i):o}function GS(o){const e=o.indexOf("?");return e===-1?[o,""]:[o.slice(0,e),o.slice(e+1)]}function ep(o,e){return e?`${o}?${e}`:o}const Ma=ct("ComponentRegistry");class on extends Error{constructor(a,r,i){super(a);$(this,"code");$(this,"details");this.code=r,this.details=i,this.name="ComponentRegistryError"}}const Pn=class Pn{constructor(){$(this,"registry",{});$(this,"manifest",null);$(this,"loadingState","idle");$(this,"error",null);$(this,"templateId",null);$(this,"templateType",null)}static getInstance(){return Pn.instance||(Pn.instance=new Pn),Pn.instance}static createIsolatedInstance(){return new Pn}static resetInstance(){Pn.instance=null}async loadComponents(e,a){if(this.loadingState==="loading")throw new on("Components are already being loaded","LOADING_IN_PROGRESS");if(this.loadingState==="loaded"&&this.templateId===e&&this.templateType===a){Ma.log("Components already loaded for template:",e,a);return}this.loadingState="loading",this.templateId=e,this.templateType=a,this.error=null;try{await this.loadManifest(),await this.loadComponentBundle(),this.loadingState="loaded",Ma.log("Successfully loaded components:",Object.keys(this.registry).length)}catch(r){throw this.loadingState="error",this.error=r instanceof Error?r:new Error(String(r)),new on(`Failed to load components: ${this.error.message}`,"LOAD_FAILED",{originalError:this.error})}}async loadManifest(){try{if(!this.templateId)throw new on("Template ID not set","TEMPLATE_ID_NOT_SET");const e=`${this.templateId}:${this.templateType}`;if(Pn.manifestCache.has(e)){this.manifest=Pn.manifestCache.get(e),Ma.log("Manifest loaded from cache:",this.manifest.templateId);return}const a=La(`/api/templates/${this.templateId}/components`,"json"),r=await xl(a,{label:"components.json"});if(!r.ok)throw new on(`Failed to fetch manifest: ${r.status} ${r.statusText}`,"MANIFEST_FETCH_FAILED",{status:r.status,statusText:r.statusText});const i=await r.json();this.validateManifest(i),this.manifest=i,Pn.manifestCache.set(e,i),Ma.log("Manifest loaded and cached:",i.templateId)}catch(e){throw e instanceof on?e:new on("Failed to load component manifest","MANIFEST_LOAD_FAILED",{originalError:e})}}validateManifest(e){if(!e.version)throw new on("Manifest missing required field: version","MANIFEST_INVALID",{field:"version"});if(!e.templateId)throw new on("Manifest missing required field: templateId","MANIFEST_INVALID",{field:"templateId"});if(!e.components||typeof e.components!="object")throw new on("Manifest missing required field: components","MANIFEST_INVALID",{field:"components"});const a=["basic","composite","layout"];for(const r of a){if(!Array.isArray(e.components[r]))throw new on(`Manifest field 'components.${r}' must be an array`,"MANIFEST_INVALID",{field:`components.${r}`,value:e.components[r]});e.components[r].forEach((i,l)=>{if(!i.name||typeof i.name!="string")throw new on(`Invalid component metadata at ${r}[${l}]: missing or invalid 'name'`,"MANIFEST_INVALID",{type:r,index:l,metadata:i});if(!i.type||typeof i.type!="string")throw new on(`Invalid component metadata at ${r}[${l}]: missing or invalid 'type'`,"MANIFEST_INVALID",{type:r,index:l,metadata:i})})}Ma.log("Manifest validation passed")}async loadComponentBundle(){try{if(!this.templateId)throw new on("Template ID not set","TEMPLATE_ID_NOT_SET");const e=this.getGlobalVariableName(),a=window[e];if(!a||typeof a!="object")throw new on(`Component bundle not loaded. Expected global variable: ${e}. Ensure admin.blade.php includes the IIFE bundle script.`,"BUNDLE_NOT_LOADED",{expectedVariable:e});await this.registerComponentsFromManifest(a),Ma.log("Component bundle loaded from global variable:",e)}catch(e){throw e instanceof on?e:new on("Failed to load component bundle","BUNDLE_LOAD_FAILED",{originalError:e})}}getGlobalVariableName(){if(!this.templateId)throw new on("Template ID not set","TEMPLATE_ID_NOT_SET");return this.templateId.split(/[-_]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join("")}async registerComponentsFromManifest(e){if(!this.manifest)throw new on("Manifest not loaded","MANIFEST_NOT_LOADED");const a=["basic","composite","layout"];for(const r of a){const i=this.manifest.components[r]||[];for(const l of i){const u=l.name,d=e[u];if(!d){Ma.warn(`Component '${u}' not found in bundle`);continue}this.registerComponent(u,d,l)}}}registerComponent(e,a,r){this.registry[e]&&Ma.warn(`Component '${e}' already registered, overwriting`);const i=We.memo(a);this.registry[e]={component:i,metadata:r},Ma.log(`[ComponentRegistry] Registered component: ${e} (${r.type})`)}getComponent(e){const a=this.registry[e];return a?a.component:null}getMetadata(e){const a=this.registry[e];return a?a.metadata:null}hasComponent(e){return e in this.registry}getComponentsByType(e){return Object.entries(this.registry).filter(([a,r])=>r.metadata.type===e).map(([a,r])=>a)}getAllComponents(){return Object.keys(this.registry)}getComponentMap(){const e={};for(const[a,r]of Object.entries(this.registry))e[a]=r.component;return e}getLoadingState(){return this.loadingState}getError(){return this.error}getTemplateId(){return this.templateId}getManifest(){return this.manifest}clear(){this.registry={},this.manifest=null,this.loadingState="idle",this.error=null,this.templateId=null,this.templateType=null,Ma.log("Registry cleared")}};$(Pn,"instance",null),$(Pn,"manifestCache",new Map);let da=Pn;const tp=ct("TranslationEngine");class ad extends Error{constructor(a,r,i){super(a);$(this,"key");$(this,"locale");this.key=r,this.locale=i,this.name="TranslationError"}}const Rn=class Rn{constructor(e={}){$(this,"cache",new Map);$(this,"translations",new Map);$(this,"options");$(this,"cacheVersion",0);this.options={defaultLocale:e.defaultLocale||"ko",fallbackLocale:e.fallbackLocale||"en",cacheTTL:e.cacheTTL||3e5}}static getInstance(e={}){return Rn.instance||(Rn.instance=new Rn(e)),Rn.instance}static resetInstance(){Rn.instance=null}setCacheVersion(e){this.cacheVersion!==e&&(tp.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.cache.clear())}getCacheVersion(){return this.cacheVersion}async loadTranslations(e,a,r="/api",i=!1){const l=`${e}:${a}`;if(!i){const u=this.getFromCache(l);if(u)return this.translations.set(l,u),u}try{const u=[];i&&u.push(`_=${Date.now()}`);const d=La(`${r}/templates/${e}/lang/${a}`,"json",this.cacheVersion>0?this.cacheVersion:null,u.length>0?u.join("&"):void 0),f=await fetch(d);if(!f.ok)throw new ad(`Failed to load translations: ${f.statusText}`,void 0,a);const m=await f.json();return this.saveToCache(l,m),this.translations.set(l,m),m}catch(u){throw new ad(`Failed to fetch translations for ${a}: ${u instanceof Error?u.message:String(u)}`,void 0,a)}}resolveTranslations(e,a,r){let i=e;const l=5;let u=0;for(;u"="+this.translate(m,a,void 0,r)),i===d)break;u++}return i.replace(Rn.TRANSLATION_PATTERN,(d,f,g)=>{const{cleanedParams:m,trailing:b}=this.separateTrailingText(g);return this.translate(f,a,m,r)+b})}translate(e,a,r,i){const l=this.getTranslation(e,a),u=r&&this.cleanParamsStr(r);if(u){const d=this.parseParams(u,i);return this.replaceParams(l,d)}return l}getTranslation(e,a){const{templateId:r,locale:i}=a,l=`${r}:${i}`,u=this.translations.get(l);if(u){const d=this.getNestedValue(u,e);if(d!==null)return d}if(i!==this.options.fallbackLocale){const d=`${r}:${this.options.fallbackLocale}`,f=this.translations.get(d);if(f){const g=this.getNestedValue(f,e);if(g!==null)return g}}return e}getNestedValue(e,a){const r=a.split(".");let i=e;for(const l of r){if(i==null||typeof i!="object")return null;i=i[l]}return typeof i=="string"?i:null}setTranslationValue(e,a,r,i){const l=`${e}:${a}`,u=this.translations.get(l)??{},d=r.split(".");let f=u;for(let g=0;g(l.push(f),`__PLACEHOLDER_${l.length-1}__`)).matchAll(Rn.PARAM_PATTERN);for(const f of d){const[,g,m]=f,b=m.replace(/__PLACEHOLDER_(\d+)__/g,(v,S)=>l[parseInt(S,10)]);r[g.trim()]=this.resolveParamValue(b.trim(),a)}return r}resolveParamValue(e,a){if(e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2).trim();if(/[|&()[\]!?:+\-*/%<>=\s]/.test(r)&&a)try{const u=new Function(...Object.keys(a),`return ${r}`)(...Object.values(a));return String(u??"")}catch(l){return tp.error("Expression evaluation failed:",r,l),""}else{const l=this.getNestedDataValue(a,r);return String(l??"")}}return e}separateTrailingText(e){if(!e)return{cleanedParams:void 0,trailing:""};if(e.trimEnd().endsWith("}}"))return{cleanedParams:e,trailing:""};if(!(e.startsWith("|")?e.slice(1):e).includes("="))return{cleanedParams:void 0,trailing:e};const r=e.match(/(\s+[^\p{L}\w=|&\s][^\p{L}\w=]*\s*)$/u);return r?{cleanedParams:e.slice(0,-r[1].length),trailing:r[1]}:{cleanedParams:e,trailing:""}}cleanParamsStr(e){return this.separateTrailingText(e).cleanedParams||""}getNestedDataValue(e,a){if(!e)return;const r=a.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}replaceParams(e,a){let r=e;for(const[i,l]of Object.entries(a)){const u=new RegExp(`\\{\\{${i}\\}\\}`,"g");r=r.replace(u,String(l));const d=new RegExp(`\\{${i}\\}`,"g");r=r.replace(d,String(l))}return r}getFromCache(e){const a=this.cache.get(e);return a?Date.now()-a.timestamp>this.options.cacheTTL?(this.cache.delete(e),null):a.data:null}saveToCache(e,a){this.cache.set(e,{data:a,timestamp:Date.now()})}clearCache(){this.cache.clear(),this.translations.clear()}pruneCache(){const e=Date.now(),a=[];for(const[r,i]of this.cache.entries())e-i.timestamp>this.options.cacheTTL&&a.push(r);for(const r of a)this.cache.delete(r)}getCacheStats(){const e=Date.now();let a=0;for(const r of this.cache.values())e-r.timestamp>this.options.cacheTTL&&a++;return{size:this.cache.size,expired:a}}};$(Rn,"instance",null),$(Rn,"TRANSLATION_PATTERN",/\$t:(?:defer:)?([a-zA-Z0-9._-]+)(\|(?:(?!\$t:).)+)?/g),$(Rn,"NESTED_TRANSLATION_PARAM_PATTERN",/=(\$t:([a-zA-Z0-9._-]+))(?=[|&\s]|$)/g),$(Rn,"PARAM_PATTERN",/([^=|&]+)=([^|&]+)/g);let Er=Rn,rd=null;function VS(o){return rd||(rd=new Er(o)),rd}const np=Object.freeze(Object.defineProperty({__proto__:null,TranslationEngine:Er,TranslationError:ad,getTranslationEngine:VS},Symbol.toStringTag,{value:"Module"})),Wi=ct("PipeRegistry");function ap(o,e){const a=o.getFullYear(),r=String(o.getMonth()+1).padStart(2,"0"),i=String(o.getDate()).padStart(2,"0"),l=String(o.getHours()).padStart(2,"0"),u=String(o.getMinutes()).padStart(2,"0"),d=String(o.getSeconds()).padStart(2,"0");return e.replace("YYYY",String(a)).replace("YY",String(a).slice(-2)).replace("MM",r).replace("M",String(o.getMonth()+1)).replace("DD",i).replace("D",String(o.getDate())).replace("HH",l).replace("H",String(o.getHours())).replace("mm",u).replace("m",String(o.getMinutes())).replace("ss",d).replace("s",String(o.getSeconds()))}function FS(o,e="ko"){const r=new Date().getTime()-o.getTime(),i=Math.floor(r/1e3),l=Math.floor(i/60),u=Math.floor(l/60),d=Math.floor(u/24),f=Math.floor(d/7),g=Math.floor(d/30),m=Math.floor(d/365);return e==="ko"?i<60?"방금 전":l<60?`${l}분 전`:u<24?`${u}시간 전`:d<7?`${d}일 전`:f<4?`${f}주 전`:g<12?`${g}개월 전`:`${m}년 전`:i<60?"just now":l<60?`${l} minute${l===1?"":"s"} ago`:u<24?`${u} hour${u===1?"":"s"} ago`:d<7?`${d} day${d===1?"":"s"} ago`:f<4?`${f} week${f===1?"":"s"} ago`:g<12?`${g} month${g===1?"":"s"} ago`:`${m} year${m===1?"":"s"} ago`}function id(o){if(o==null)return null;if(o instanceof Date)return o;if(typeof o=="number")return new Date(o);if(typeof o=="string"){const e=new Date(o);return isNaN(e.getTime())?null:e}return null}const Ys={date:{fn:(o,e="YYYY-MM-DD")=>{const a=id(o);return a?ap(a,e):""},description:"날짜 포맷 (기본: YYYY-MM-DD)"},datetime:{fn:(o,e="YYYY-MM-DD HH:mm")=>{const a=id(o);return a?ap(a,e):""},description:"날짜+시간 포맷 (기본: YYYY-MM-DD HH:mm)"},relativeTime:{fn:(o,e="ko")=>{const a=id(o);return a?FS(a,e):""},description:'상대 시간 표시 (예: "3분 전")'},number:{fn:(o,e)=>{const a=Number(o);if(isNaN(a))return String(o??"");const r={};return e!==void 0&&(r.minimumFractionDigits=e,r.maximumFractionDigits=e),a.toLocaleString(void 0,r)},description:"숫자 포맷 (천단위 구분, 선택적 소수점)"},truncate:{fn:(o,e=100,a="...")=>typeof o!="string"?String(o??""):o.length<=e?o:o.slice(0,e)+a,description:'문자열 자르기 (기본: 100자, 접미사: "...")'},uppercase:{fn:o=>typeof o!="string"?String(o??""):o.toUpperCase(),description:"대문자 변환"},lowercase:{fn:o=>typeof o!="string"?String(o??""):o.toLowerCase(),description:"소문자 변환"},stripHtml:{fn:o=>typeof o!="string"?String(o??""):o.replace(/<[^>]*>/g,""),description:"HTML 태그 제거"},default:{fn:(o,e="")=>o==null||o===""?e:o,description:"기본값 설정 (null, undefined, 빈문자열 시)"},fallback:{fn:(o,e)=>o??e,description:"폴백 값 설정 (null, undefined 시만)"},first:{fn:o=>{if(Array.isArray(o))return o[0]},description:"배열의 첫 번째 요소"},last:{fn:o=>{if(Array.isArray(o))return o[o.length-1]},description:"배열의 마지막 요소"},join:{fn:(o,e=", ")=>Array.isArray(o)?o.join(e):"",description:'배열을 문자열로 결합 (기본 구분자: ", ")'},length:{fn:o=>Array.isArray(o)||typeof o=="string"?o.length:0,description:"배열/문자열 길이"},filterBy:{fn:(o,e,a)=>Array.isArray(o)?Array.isArray(e)?o.filter(r=>{const i=a?r?.[a]:r;return e.includes(i)}):o:[],description:"배열 필터링 (allowList에 포함된 항목만 반환)"},keys:{fn:o=>o==null||typeof o!="object"?[]:Object.keys(o),description:"객체의 키 배열"},values:{fn:o=>o==null||typeof o!="object"?[]:Object.values(o),description:"객체의 값 배열"},json:{fn:(o,e)=>{try{return JSON.stringify(o,null,e)}catch{return""}},description:"JSON 문자열로 변환"},localized:{fn:(o,e)=>o==null?"":typeof o=="string"?o:typeof o!="object"?String(o):o[e||"ko"]||o.ko||o.en||Object.values(o)[0]||"",description:"다국어 객체에서 로케일에 맞는 값 추출"}},Yi=new Map,mi=class mi{static getInstance(){return mi.instance||(mi.instance=new mi),mi.instance}register(e,a,r){if(Ys[e]){Wi.warn(`[PipeRegistry] 내장 파이프를 덮어쓸 수 없습니다: ${e}`);return}Yi.set(e,{fn:a,description:r}),Wi.log(`[PipeRegistry] 커스텀 파이프 등록됨: ${e}`)}unregister(e){if(Ys[e])return Wi.warn(`[PipeRegistry] 내장 파이프는 해제할 수 없습니다: ${e}`),!1;const a=Yi.delete(e);return a&&Wi.log(`[PipeRegistry] 커스텀 파이프 해제됨: ${e}`),a}get(e){const a=Ys[e];return a?a.fn:Yi.get(e)?.fn}has(e){return!!Ys[e]||Yi.has(e)}execute(e,a,r=[]){const i=this.get(e);if(!i)return Wi.warn(`[PipeRegistry] 알 수 없는 파이프: ${e}`),a;try{return i(a,...r)}catch(l){return Wi.error(`[PipeRegistry] 파이프 실행 오류 (${e}):`,l),a}}list(){const e=[];for(const[a,r]of Object.entries(Ys))e.push({name:a,description:r.description,type:"built-in"});for(const[a,r]of Yi.entries())e.push({name:a,description:r.description,type:"custom"});return e.sort((a,r)=>a.name.localeCompare(r.name))}clearCustomPipes(){Yi.clear()}};$(mi,"instance",null);let Tl=mi;Tl.getInstance();function KS(o){const e=o.trim(),a=e.indexOf("(");if(a===-1)return{name:e,args:[]};const r=e.slice(0,a).trim(),i=e.slice(a+1,-1);if(!i.trim())return{name:r,args:[]};const l=[];let u="",d=null,f=0;for(let g=0;g0?i[g-1]:"")==="\\"){u+=m;continue}if((m==='"'||m==="'")&&!d){d=m;continue}if(m===d){d=null;continue}if(d){u+=m;continue}if(m==="("||m==="["||m==="{"){f++,u+=m;continue}if(m===")"||m==="]"||m==="}"){f--,u+=m;continue}if(m===","&&f===0){l.push(rp(u.trim())),u="";continue}u+=m}return u.trim()&&l.push(rp(u.trim())),{name:r,args:l}}function rp(o){if(o==="null")return null;if(o==="undefined")return;if(o==="true")return!0;if(o==="false")return!1;const e=Number(o);return!isNaN(e)&&o!==""?e:o}function WS(o){const e=[];let a="",r=null,i=0;for(let l=0;l0?o[l-1]:"",f=l0?o[r-1]:"",u=r=2&&o.charCodeAt(0)===64976&&o.charCodeAt(o.length-1)===64977}function sp(o){return o.includes(Rl)}function op(o){return o.slice(1,-1)}function Xi(o){if(typeof o=="string")return Js(o);if(Array.isArray(o))return o.map(Xi);if(o&&typeof o=="object"){const e={};for(const[a,r]of Object.entries(o))e[a]=Xi(r);return e}return o}const Qs=ct("DataBindingEngine");function kl(){try{return window.G7Core?.devTools}catch{return}}class sd extends Error{constructor(a,r,i){super(a);$(this,"path");$(this,"context");this.path=r,this.context=i,this.name="DataBindingError"}}const Sa=class Sa{constructor(e){$(this,"cache",new Map);$(this,"expressionFnCache",new Map);$(this,"renderCycleCache",new Map);$(this,"currentRenderCycleId",0);$(this,"defaultOptions",{defaultValue:void 0,nullSafe:!0,detectCircular:!0,maxDepth:10});e&&(this.defaultOptions={...this.defaultOptions,...e})}startRenderCycle(){this.currentRenderCycleId++,this.renderCycleCache.clear()}isRenderCycleCacheActive(){return this.currentRenderCycleId>0}getFromRenderCycleCache(e){if(this.isRenderCycleCacheActive())return this.renderCycleCache.get(e)}saveToRenderCycleCache(e,a){this.isRenderCycleCacheActive()&&this.renderCycleCache.set(e,a)}resolveBindings(e,a,r,i){const l={...this.defaultOptions,...r},u=kl(),d=a._computed&&!a.$computed?{...a,$computed:a._computed}:a;return e.replace(Sa.BINDING_PATTERN,(f,g)=>{const m=g.trim();let b=!1,v=m;if(m.startsWith($a)&&(b=!0,v=m.slice($a.length)),ci(v)){const R=u?.isEnabled()?performance.now():0;try{const[D,U]=WS(v);let j,P=!1;const X=/[?:|&!+\-*/<>=()]/.test(D)||/\[['"]/.test(D),ge=D.startsWith("_global")||D.startsWith("_local")||D.startsWith("_isolated")||D.startsWith("$parent");if(l.skipCache)X?j=this.evaluateExpression(D,d):j=this.resolvePath(D,d,l);else if(X){const $e=this.getFromRenderCycleCache(`expr:${D}`);$e!==void 0?(j=$e,P=!0):(j=this.evaluateExpression(D,d),this.saveToRenderCycleCache(`expr:${D}`,j))}else if(ge){const $e=this.getFromRenderCycleCache(D);$e!==void 0?(j=$e,P=!0):(j=this.resolvePath(D,d,l),this.saveToRenderCycleCache(D,j))}else{const $e=this.getFromCache(D);$e!==void 0?(j=$e,P=!0):(j=this.resolvePath(D,d,l),this.saveToCache(D,j))}const Ee=YS(j,U);if(u?.isEnabled()){const $e=performance.now()-R;u.trackExpressionEval({expression:`{{${m}}}`,result:this.sanitizeResultForTracking(Ee),resultType:this.getResultType(Ee),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:P,duration:$e,method:"resolveBindings",skipCache:l.skipCache})}const Ce=this.formatValue(Ee);return b?Js(Ce):Ce}catch(D){return Qs.error("Pipe expression evaluation failed:",v,D),f}}if(/[?:&!+\-*/<>=()]/.test(v)||/\|\|/.test(v)||/\[['"]/.test(v)){const R=u?.isEnabled()?performance.now():0;try{let D,U=!1;if(l.skipCache)D=this.evaluateExpression(v,d);else{const P=`expr:${v}`,X=this.getFromRenderCycleCache(P);X!==void 0?(D=X,U=!0):(D=this.evaluateExpression(v,d),this.saveToRenderCycleCache(P,D))}if(u?.isEnabled()){const P=performance.now()-R;u.trackExpressionEval({expression:`{{${m}}}`,result:this.sanitizeResultForTracking(D),resultType:this.getResultType(D),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:U,duration:P,method:"resolveBindings",skipCache:l.skipCache})}const j=this.formatValue(D);return b?Js(j):j}catch(D){return Qs.error("Expression evaluation failed:",v,D),f}}if(l.skipCache){const R=u?.isEnabled()?performance.now():0,D=this.resolvePath(v,d,l);if(u?.isEnabled()){const j=performance.now()-R;u.trackExpressionEval({expression:`{{${m}}}`,result:this.sanitizeResultForTracking(D),resultType:this.getResultType(D),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:j,method:"resolveBindings",skipCache:!0})}const U=this.formatValue(D);return b?Js(U):U}const E=v.startsWith("_global")||v.startsWith("_local")||v.startsWith("_isolated")||v.startsWith("$parent"),A=u?.isEnabled()?performance.now():0;let T,L=!1;if(E){const R=this.getFromRenderCycleCache(v);R!==void 0?(T=R,L=!0):(T=this.resolvePath(v,d,l),this.saveToRenderCycleCache(v,T))}else{const R=this.getFromCache(v);R!==void 0?(T=R,L=!0):(T=this.resolvePath(v,d,l),this.saveToCache(v,T))}if(u?.isEnabled()){const R=performance.now()-A;u.trackExpressionEval({expression:`{{${m}}}`,result:this.sanitizeResultForTracking(T),resultType:this.getResultType(T),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:L,duration:R,method:"resolveBindings",skipCache:l.skipCache})}const M=this.formatValue(T);return b?Js(M):M})}resolve(e,a,r,i){const l={...this.defaultOptions,...r},u=kl(),d=u?.isEnabled()?performance.now():0;if(l.skipCache){const b=this.resolvePath(e,a,l);return u?.isEnabled()&&u.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!1,duration:performance.now()-d,method:"resolve",skipCache:!0}),b}const f=e.startsWith("_global")||e.startsWith("_local")||e.startsWith("_isolated")||e.startsWith("$parent");let g=!1;if(f){const b=this.getFromRenderCycleCache(e);if(b!==void 0)return g=!0,u?.isEnabled()&&u.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),b}else{const b=this.getFromCache(e);if(b!==void 0)return g=!0,u?.isEnabled()&&u.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(b),resultType:this.getResultType(b),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:!0,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),b}const m=this.resolvePath(e,a,l);return f?this.saveToRenderCycleCache(e,m):this.saveToCache(e,m),u?.isEnabled()&&u.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(m),resultType:this.getResultType(m),componentId:i?.componentId,componentName:i?.componentName,propName:i?.propName,fromCache:g,duration:performance.now()-d,method:"resolve",skipCache:l.skipCache}),m}resolvePath(e,a,r,i=0,l=new WeakSet){if(r.maxDepth&&i>r.maxDepth){if(r.detectCircular)throw new sd(`Maximum depth exceeded (${r.maxDepth}). Possible circular reference.`,e,a);return r.defaultValue}const u=this.parsePath(e);let d=a;for(let f=0;fSa.CACHE_EXPIRY){this.cache.delete(e),r?.isEnabled()&&r.recordCacheMiss();return}return r?.isEnabled()&&r.recordCacheHit(),a.value}saveToCache(e,a){this.cache.set(e,{value:a,timestamp:Date.now()})}isActionDefinition(e){return typeof e.handler!="string"?!1:e.params!==void 0||Array.isArray(e.actions)||typeof e.target=="string"||e.onSuccess!==void 0||e.onError!==void 0}extractSingleBinding(e){if(!e.startsWith("{{")||!e.endsWith("}}")||e.length<5)return null;const a=e.slice(2,-2);let r=0;for(let i=0;i0)r--;else if(a[i+1]==="}")return null}}return a}resolveObject(e,a,r){if(this.isSwitchExpression(e)){const d=e;return this.resolveSwitch(d,a,r)}if(this.isActionDefinition(e))return{...e};if("iteration"in e)return{...e};const i={},u=[...["cellChildren","expandChildren","expandContext","render"],...r?.skipBindingKeys||[]];for(const[d,f]of Object.entries(e)){if(u.includes(d)){i[d]=f;continue}if(typeof f=="string"){const g=this.extractSingleBinding(f);if(g!==null){const m=g.trim();let b=!1,v=m;m.startsWith($a)&&(b=!0,v=m.slice($a.length));const S=/[?:|&!+\-*/<>=()]/.test(v)||/\[['"]/.test(v),E=ci(v)?this.resolveBindings(`{{${v}}}`,a,r):S?this.evaluateExpression(v,a,r):this.resolve(v,a,r);i[d]=b&&E!=null?Xi(E):E}else i[d]=this.resolveBindings(f,a,r)}else Array.isArray(f)?i[d]=f.map(g=>typeof g=="string"?this.resolveBindings(g,a,r):typeof g=="object"&&g!==null?this.resolveObject(g,a,r):g):f&&typeof f=="object"?i[d]=this.resolveObject(f,a,r):i[d]=f}return i}clearCache(){this.cache.clear(),this.expressionFnCache.clear()}invalidateCacheByKeys(e){for(const a of this.cache.keys())for(const r of e)if(a===r||a.startsWith(`${r}.`)||a.startsWith(`${r}[`)){this.cache.delete(a);break}}pruneCache(){const e=Date.now();for(const[a,r]of this.cache.entries())e-r.timestamp>Sa.CACHE_EXPIRY&&this.cache.delete(a)}getCacheStats(){const e=Date.now();let a=0;for(const r of this.cache.values())e-r.timestamp>Sa.CACHE_EXPIRY&&a++;return{size:this.cache.size,expired:a}}evaluateExpression(e,a,r){const i=kl(),l=i?.isEnabled()?performance.now():0;i?.isEnabled()&&i.trackBindingEval();try{let u=this.preprocessOptionalChaining(e);u=this.preprocessTranslationTokens(u);const d=this.extractVariablesFromExpression(u),f={...a};for(const D of d)D in f||(f[D]=void 0);a._computed&&!f.$computed&&(f.$computed=a._computed);let g=a.$templateId,m=a.$locale;if(typeof window<"u"&&(!g||!m)){const D=window.__templateApp?.getConfig?.();g??(g=D?.templateId),m??(m=D?.locale)}const b=m||"ko";f.$localized=(D,U)=>{if(D==null&&!U)return"";if(typeof D=="string")return D;if(D&&typeof D=="object"&&D[b])return D[b];if(U&&typeof U=="string"){const j=f.$t?f.$t(U):U;if(j&&j!==U)return j}return D&&typeof D=="object"?D.ko||D.en||Object.values(D)[0]||"":D==null?"":String(D)};const v=typeof window<"u"?window.G7Core:void 0;f.$uuid=()=>v?.uuid?v.uuid():typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,D=>{const U=Math.random()*16|0;return(D==="x"?U:U&3|8).toString(16)});const S={templateId:g||"",locale:b};f.$t=D=>{if(!D||typeof D!="string")return"";try{return Er.getInstance().translate(D,S)}catch(U){return Qs.warn("$t() translation failed for key:",D,U),D}},f.$get=(D,U,j=void 0)=>{if(D==null)return j;const P=Array.isArray(U)?U:[U];if(P.length===0)return D;let X=D;for(const ge of P){if(X==null||ge==null)return j;X=X[ge]}return X??j};const E=Object.keys(f),A=Object.values(f),T=`${u}|${E.join(",")}`;let L=this.expressionFnCache.get(T);const M=!!L;L||(L=new Function(...E,`return (${u});`),this.expressionFnCache.set(T,L));const R=L(...A);if(i?.isEnabled()){const D=performance.now()-l;i.trackExpressionEval({expression:e,result:this.sanitizeResultForTracking(R),resultType:this.getResultType(R),componentId:r?.componentId,componentName:r?.componentName,propName:r?.propName,fromCache:M,duration:D,method:"evaluateExpression",skipCache:r?.skipCache})}return R}catch(u){throw new Error(`Failed to evaluate expression "${e}": ${u instanceof Error?u.message:String(u)}`)}}getResultType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}sanitizeResultForTracking(e){if(e==null||typeof e!="object")return e;try{if(Array.isArray(e))return e.length>10?`[Array(${e.length})]`:e.slice(0,10);const a=Object.keys(e);return a.length>20?`{Object(${a.length} keys)}`:(JSON.stringify(e),e)}catch{return"[Complex Object]"}}preprocessTranslationTokens(e){return e=e.replace(/(['"])(\$t:([a-zA-Z_][a-zA-Z0-9_.\-]*))\1/g,(a,r,i,l)=>`$t('${l}')`),e.replace(/(?(a.push(l),`__STRING_LITERAL_${a.length-1}__`));const i=[];return r=r.replace(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*(?:\|[^'"\s,)]+)?/g,l=>(i.push(l),`__TRANSLATION_TOKEN_${i.length-1}__`)),r=r.replace(/([a-zA-Z_$][a-zA-Z0-9_$]*)\.(?!\?)/g,"$1?."),r=r.replace(/__TRANSLATION_TOKEN_(\d+)__/g,(l,u)=>i[parseInt(u,10)]),r=r.replace(/__STRING_LITERAL_(\d+)__/g,(l,u)=>a[parseInt(u,10)]),r}extractVariablesFromExpression(e){const a=new Set(["true","false","null","undefined","NaN","Infinity","if","else","for","while","do","switch","case","break","continue","return","function","class","const","let","var","new","delete","typeof","instanceof","this","super","import","export","default","try","catch","finally","throw","Math","Date","JSON","Array","Object","String","Number","Boolean","RegExp","Set","Map","WeakSet","WeakMap","Symbol","Promise","BigInt","Error","parseInt","parseFloat","isNaN","isFinite","encodeURI","decodeURI","encodeURIComponent","decodeURIComponent"]),r=/(?{a!==null&&clearTimeout(a),a=setTimeout(()=>{o(...r)},e)})}class QS{constructor(){$(this,"subscribers",new Set);$(this,"currentWidth",1024);$(this,"debouncedHandler",null);$(this,"isInitialized",!1);this.initialize()}initialize(){typeof window>"u"||(this.currentWidth=window.innerWidth,this.isInitialized=!0,this.debouncedHandler=JS(()=>{this.currentWidth=window.innerWidth,this.notifySubscribers()},150),window.addEventListener("resize",this.debouncedHandler))}notifySubscribers(){this.subscribers.forEach(e=>{e(this.currentWidth)})}subscribe(e){return this.subscribers.add(e),e(this.currentWidth),()=>{this.subscribers.delete(e)}}getWidth(){return this.currentWidth}parseRange(e){if(Ol.has(e))return Ol.get(e)??null;let a=null;if(Dl[e])a={...Dl[e]};else{const r=e.match(/^(-?\d*)-(-?\d*)$/);if(r){const[,i,l]=r,u=i===""?0:parseInt(i,10),d=l===""?1/0:parseInt(l,10);!isNaN(u)&&!isNaN(d)&&u<=d&&(a={min:u,max:d})}}return Ol.set(e,a),a}getMatchingKey(e,a){const r=[];for(const i of Object.keys(e)){const l=this.parseRange(i);l&&a>=l.min&&a<=l.max&&r.push({key:i,range:l,isPreset:!!Dl[i]})}return r.length===0?null:(r.sort((i,l)=>{if(i.isPreset!==l.isPreset)return i.isPreset?1:-1;const u=i.range.max-i.range.min,d=l.range.max-l.range.min;return u-d}),r[0].key)}matches(e){const a=this.parseRange(e);return a?this.currentWidth>=a.min&&this.currentWidth<=a.max:!1}clearSubscribers(){this.subscribers.clear()}destroy(){this.debouncedHandler&&typeof window<"u"&&window.removeEventListener("resize",this.debouncedHandler),this.clearSubscribers(),Ol.clear()}_setWidthForTesting(e){this.currentWidth=e,this.notifySubscribers()}}const ui=new QS,Ll=ct("ConditionEvaluator");function ZS(o){switch(o){case"true":return{value:!0};case"false":return{value:!1};case"null":return{value:null};case"undefined":return{value:void 0};default:return}}function ew(o){return/[?:|&!+\-*/<>=()]/.test(o)}function tw(o){if(!o.startsWith("{{")||!o.endsWith("}}"))return null;const e=o.slice(2,-2);let a=null,r=0;for(let i=0;i0?e[i-1]:"")!=="\\"&&((l==='"'||l==="'")&&!a?a=l:l===a&&(a=null),!a&&(l==="{"&&r++,l==="}"&&r--,r<0)))return null}return r===0?e.trim():null}function nw(o,e,a){if(!o)return!0;try{const r=tw(o);let i;if(r!==null){const l=ZS(r);l!==void 0?i=l.value:ci(r)?i=a.resolveBindings(`{{${r}}}`,e,{skipCache:!0}):ew(r)?i=a.evaluateExpression(r,e):i=a.resolve(r,e,{skipCache:!0})}else i=a.resolveBindings(o,e,{skipCache:!0});if(typeof i=="string"){const l=i.toLowerCase().trim();if(l==="false"||l==="0"||l===""||l==="null"||l==="undefined")return!1}return!!i}catch(r){return Ll.warn(`evaluateStringCondition: 조건 평가 실패: ${o}`,r),!1}}function Ml(o,e,a){if(typeof o=="string")return nw(o,e,a);if("and"in o){if(!Array.isArray(o.and)||o.and.length===0)return Ll.warn("evaluateConditionExpression: AND 그룹이 비어있습니다"),!0;for(const r of o.and)if(!Ml(r,e,a))return!1;return!0}if("or"in o){if(!Array.isArray(o.or)||o.or.length===0)return Ll.warn("evaluateConditionExpression: OR 그룹이 비어있습니다"),!1;for(const r of o.or)if(Ml(r,e,a))return!0;return!1}return Ll.warn("evaluateConditionExpression: 알 수 없는 조건 형식",o),!1}function lp(o,e,a){if(!Array.isArray(o)||o.length===0)return{matched:!1,branchIndex:-1};for(let r=0;r=()[\]{}]/.test(o)}function dp(o){if(!o.startsWith("{{")||!o.endsWith("}}"))return null;const e=o.slice(2,-2);let a=null,r=0;for(let i=0;i0?e[i-1]:"")!=="\\"&&((l==='"'||l==="'")&&!a?a=l:l===a&&(a=null),!a&&(l==="{"&&r++,l==="}"&&r--,r<0)))return null}return r===0?e.trim():null}function ld(o,e,a){let r=o;if(o.startsWith("{{")&&o.endsWith("}}")&&(r=o.slice(2,-2).trim()),od(r))try{return a.evaluateExpression(r,e)}catch(i){Ja.warn(`resolveIterationSource: 표현식 평가 실패: ${o}`,i);return}return a.resolve(r,e,{skipCache:!0})}function sw(o,e){const a={...o};if(e?.parentDataContext)for(const[r,i]of Object.entries(e.parentDataContext))r in a||(a[r]=i);if(e?.state){const r=a._local||{};a._local={...r,...e.state}}return e?.isolatedContext?.state&&(a._isolated=e.isolatedContext.state),a}function cd(o,e,a,r){if(!e||!Array.isArray(e)||e.length===0)return o;const i=r?.actionDispatcher??em(),l={...o,actions:e},u=i.bindActionsToProps(l,a,r?.componentContext);return delete u.actions,u}function fp(o,e,a,r){if(typeof o=="boolean")return o;if(!o)return!0;try{const i=dp(o);let l;if(i!==null){const u=iw.get(i);u!==void 0?l=u:ci(i)?l=a.resolveBindings(`{{${i}}}`,e,{skipCache:!0}):od(i)?l=a.evaluateExpression(i,e):l=a.resolve(i,e,{skipCache:!0})}else l=a.resolveBindings(o,e,{skipCache:!0});if(typeof l=="string"){const u=l.toLowerCase().trim();if(u==="false"||u==="0"||u===""||u==="null"||u==="undefined")return!1}return!!l}catch(i){return Ja.warn(`evaluateIfCondition: 조건 평가 실패 (컴포넌트: ${r||"unknown"}):`,i),!1}}function Ji(o,e,a,r){if(o.if!==void 0)return fp(o.if,e,a,r);if(o.condition!==void 0)return fp(o.condition,e,a,r);if(o.conditions===void 0)return!0;const i=o.conditions;try{return aw(i)?Ml(i,e,a):rw(i)?lp(i,e,a).matched:(Ja.warn(`evaluateRenderCondition: 알 수 없는 conditions 형식 (컴포넌트: ${r||"unknown"})`),!0)}catch(l){return Ja.warn(`evaluateRenderCondition: conditions 평가 실패 (컴포넌트: ${r||"unknown"}):`,l),!1}}function hp(o){if(!o.responsive)return o;const e=ui.getWidth(),a=ui.getMatchingKey(o.responsive,e);if(!a)return o;const r=o.responsive[a];return{...o,props:{...o.props,...r.props},children:r.children??o.children,text:r.text??o.text,if:r.if??o.if,iteration:r.iteration??o.iteration}}function Zs(o,e,a,r,i){if(!o||o.length===0)return[];const l=i?.bindingEngine??new wn,u=i?.translationEngine??Er.getInstance(),d=i?.translationContext??{templateId:"",locale:"ko"},f=sw(e,i?.componentContext),g=(v,S)=>{if(typeof v=="string"){if(ip(v))return op(v);if(sp(v)){const A=[],T=v.replace(new RegExp(`${Rl}([^${Xs}]*)${Xs}`,"g"),(M,R)=>(A.push(R),`${_r}${A.length-1}${_r}`));let L=T;return/\$t:[a-zA-Z0-9._-]+/.test(T)&&(L=u.resolveTranslations(T,d,S)),L.replace(new RegExp(`${_r}(\\d+)${_r}`,"g"),(M,R)=>A[parseInt(R)])}if(v.startsWith("$t:defer:")){const A="$t:"+v.slice(9);return u.resolveTranslations(A,d,S)}if(v.startsWith("$t:"))return u.resolveTranslations(v,d,S);const E=dp(v);if(E!==null){let A=!1,T=E;E.startsWith($a)&&(A=!0,T=E.slice($a.length));let L;if(ci(T))L=l.resolveBindings(`{{${T}}}`,S,{skipCache:!0});else if(od(T))try{L=l.evaluateExpression(T,S)}catch(M){Ja.warn("renderItemChildren: 표현식 평가 실패:",M);return}else L=l.resolve(T,S,{skipCache:!0});return!A&&typeof L=="string"&&/\$t:[a-zA-Z0-9._-]+/.test(L)&&(L=u.resolveTranslations(L,d,S)),A&&L!=null?Xi(L):L}if(v.includes("{{")){const A=l.resolveBindings(v,S,{skipCache:!0});return typeof A=="string"&&/\$t:[a-zA-Z0-9._-]+/.test(A)?u.resolveTranslations(A,d,S):A}return v}if(Array.isArray(v))return v.map(E=>g(E,S));if(v&&typeof v=="object"){const E={};for(const[A,T]of Object.entries(v))E[A]=g(T,S);return E}return v},m=(v,S)=>{if(!v)return{};const E={};for(const[A,T]of Object.entries(v))E[A]=g(T,S);return E},b=(v,S,E)=>{const A=hp(v),T=A.iteration;if(!T)return[];const L=ld(T.source,E,l);if(!Array.isArray(L))return Ja.warn(`renderItemChildren: iteration.source가 배열이 아닙니다: ${T.source}`),[];const M=cp();if(M?.isEnabled()){const D=`${S}-iteration`;M.trackIteration(D,T.source,T.item_var,T.index_var,L.length)}const R=a[A.name];return R?L.flatMap((D,U)=>{const j={...E,[T.item_var]:D,[`${T.item_var}_index`]:U,...T.index_var?{[T.index_var]:U}:{}},P=Ji({if:A.if,condition:A.condition,conditions:A.conditions},j,l,A.id);if(A.if&&M?.isEnabled()){const Ye=`${S}-iter-${U}-if`;M.trackIfCondition(Ye,A.if,P,A.name)}if(!P)return[];const X=A.id?String(g(A.id,j)):void 0,ge=`${S}-iter-${U}`,Ee=i?.getRemountKey?i.getRemountKey(X,ge):ge,Ce=m(A.props,j),$e=cd(Ce,A.actions,j,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let Pe=null;return A.text!==void 0?Pe=g(A.text,j):A.children&&A.children.length>0&&(Pe=Zs(A.children,j,a,Ee,i)),M?.isEnabled()&&M.trackRender(A.name),[We.createElement(R,{key:Ee,...$e},Pe)]}):(Ja.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${A.name}`),[])};return o.flatMap((v,S)=>{const E=hp(v),A=E.id?String(g(E.id,f)):void 0,T=r?`${r}-${A||S}`:A||`child-${S}`,L=i?.getRemountKey?i.getRemountKey(A,T):T;if(E.iteration)return b(E,L,f);const M=Ji({if:E.if,condition:E.condition,conditions:E.conditions},f,l,E.id),R=cp();if(E.if&&R?.isEnabled()){const X=`${L}-if`;R.trackIfCondition(X,E.if,M,E.name)}if(!M)return[];const D=a[E.name];if(!D)return Ja.warn(`renderItemChildren: 컴포넌트를 찾을 수 없습니다: ${E.name}`),[];const U=m(E.props,f),j=cd(U,E.actions,f,{actionDispatcher:i?.actionDispatcher,componentContext:i?.componentContext});let P=null;return E.text!==void 0?P=g(E.text,f):E.children&&E.children.length>0&&(P=Zs(E.children,f,a,L,i)),R?.isEnabled()&&R.trackRender(E.name),[We.createElement(D,{key:L,...j},P)]})}function Qi(o,e,a,r){let i,l;return a instanceof wn?(i=a,l=r):(i=up,l=a),i.resolveBindings(o,e,l)}function ow(o,e,a,r){const i=a??up,l={skipCache:!0,...r};let u;try{u=i.resolveBindings(o.key,e,{skipCache:l.skipCache})?.toString()?.trim()??""}catch(g){Ja.warn("resolveClassMap: key 평가 실패:",o.key,g),u=""}let d="";u&&o.variants&&u in o.variants?d=o.variants[u]:o.default&&(d=o.default);const f=[];return o.base?.trim()&&f.push(o.base.trim()),d?.trim()&&f.push(d.trim()),f.join(" ")}function gp(o,e){return function(){return o.apply(e,arguments)}}const{toString:lw}=Object.prototype,{getPrototypeOf:$l}=Object,{iterator:Nl,toStringTag:pp}=Symbol,Il=(o=>e=>{const a=lw.call(e);return o[a]||(o[a]=a.slice(8,-1).toLowerCase())})(Object.create(null)),fa=o=>(o=o.toLowerCase(),e=>Il(e)===o),Hl=o=>e=>typeof e===o,{isArray:Zi}=Array,es=Hl("undefined");function eo(o){return o!==null&&!es(o)&&o.constructor!==null&&!es(o.constructor)&&xn(o.constructor.isBuffer)&&o.constructor.isBuffer(o)}const mp=fa("ArrayBuffer");function cw(o){let e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e=ArrayBuffer.isView(o):e=o&&o.buffer&&mp(o.buffer),e}const uw=Hl("string"),xn=Hl("function"),yp=Hl("number"),to=o=>o!==null&&typeof o=="object",dw=o=>o===!0||o===!1,zl=o=>{if(Il(o)!=="object")return!1;const e=$l(o);return(e===null||e===Object.prototype||Object.getPrototypeOf(e)===null)&&!(pp in o)&&!(Nl in o)},fw=o=>{if(!to(o)||eo(o))return!1;try{return Object.keys(o).length===0&&Object.getPrototypeOf(o)===Object.prototype}catch{return!1}},hw=fa("Date"),gw=fa("File"),pw=o=>!!(o&&typeof o.uri<"u"),mw=o=>o&&typeof o.getParts<"u",yw=fa("Blob"),bw=fa("FileList"),vw=o=>to(o)&&xn(o.pipe);function Sw(){return typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{}}const bp=Sw(),vp=typeof bp.FormData<"u"?bp.FormData:void 0,ww=o=>{if(!o)return!1;if(vp&&o instanceof vp)return!0;const e=$l(o);if(!e||e===Object.prototype||!xn(o.append))return!1;const a=Il(o);return a==="formdata"||a==="object"&&xn(o.toString)&&o.toString()==="[object FormData]"},Cw=fa("URLSearchParams"),[Ew,_w,Aw,xw]=["ReadableStream","Request","Response","Headers"].map(fa),Tw=o=>o.trim?o.trim():o.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function no(o,e,{allOwnKeys:a=!1}={}){if(o===null||typeof o>"u")return;let r,i;if(typeof o!="object"&&(o=[o]),Zi(o))for(r=0,i=o.length;r0;)if(i=a[r],e===i.toLowerCase())return i;return null}const di=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global,wp=o=>!es(o)&&o!==di;function ud(...o){const{caseless:e,skipUndefined:a}=wp(this)&&this||{},r={},i=(l,u)=>{if(u==="__proto__"||u==="constructor"||u==="prototype")return;const d=e&&Sp(r,u)||u,f=dd(r,d)?r[d]:void 0;zl(f)&&zl(l)?r[d]=ud(f,l):zl(l)?r[d]=ud({},l):Zi(l)?r[d]=l.slice():(!a||!es(l))&&(r[d]=l)};for(let l=0,u=o.length;l(no(e,(i,l)=>{a&&xn(i)?Object.defineProperty(o,l,{__proto__:null,value:gp(i,a),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(o,l,{__proto__:null,value:i,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:r}),o),kw=o=>(o.charCodeAt(0)===65279&&(o=o.slice(1)),o),Dw=(o,e,a,r)=>{o.prototype=Object.create(e.prototype,r),Object.defineProperty(o.prototype,"constructor",{__proto__:null,value:o,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(o,"super",{__proto__:null,value:e.prototype}),a&&Object.assign(o.prototype,a)},Ow=(o,e,a,r)=>{let i,l,u;const d={};if(e=e||{},o==null)return e;do{for(i=Object.getOwnPropertyNames(o),l=i.length;l-- >0;)u=i[l],(!r||r(u,o,e))&&!d[u]&&(e[u]=o[u],d[u]=!0);o=a!==!1&&$l(o)}while(o&&(!a||a(o,e))&&o!==Object.prototype);return e},Lw=(o,e,a)=>{o=String(o),(a===void 0||a>o.length)&&(a=o.length),a-=e.length;const r=o.indexOf(e,a);return r!==-1&&r===a},Mw=o=>{if(!o)return null;if(Zi(o))return o;let e=o.length;if(!yp(e))return null;const a=new Array(e);for(;e-- >0;)a[e]=o[e];return a},$w=(o=>e=>o&&e instanceof o)(typeof Uint8Array<"u"&&$l(Uint8Array)),Nw=(o,e)=>{const r=(o&&o[Nl]).call(o);let i;for(;(i=r.next())&&!i.done;){const l=i.value;e.call(o,l[0],l[1])}},Iw=(o,e)=>{let a;const r=[];for(;(a=o.exec(e))!==null;)r.push(a);return r},Hw=fa("HTMLFormElement"),zw=o=>o.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(a,r,i){return r.toUpperCase()+i}),dd=(({hasOwnProperty:o})=>(e,a)=>o.call(e,a))(Object.prototype),jw=fa("RegExp"),Cp=(o,e)=>{const a=Object.getOwnPropertyDescriptors(o),r={};no(a,(i,l)=>{let u;(u=e(i,l,o))!==!1&&(r[l]=u||i)}),Object.defineProperties(o,r)},Uw=o=>{Cp(o,(e,a)=>{if(xn(o)&&["arguments","caller","callee"].includes(a))return!1;const r=o[a];if(xn(r)){if(e.enumerable=!1,"writable"in e){e.writable=!1;return}e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+a+"'")})}})},Bw=(o,e)=>{const a={},r=i=>{i.forEach(l=>{a[l]=!0})};return Zi(o)?r(o):r(String(o).split(e)),a},Pw=()=>{},qw=(o,e)=>o!=null&&Number.isFinite(o=+o)?o:e;function Gw(o){return!!(o&&xn(o.append)&&o[pp]==="FormData"&&o[Nl])}const Vw=o=>{const e=new WeakSet,a=r=>{if(to(r)){if(e.has(r))return;if(eo(r))return r;if(!("toJSON"in r)){e.add(r);const i=Zi(r)?[]:{};return no(r,(l,u)=>{const d=a(l);!es(d)&&(i[u]=d)}),e.delete(r),i}}return r};return a(o)},Fw=fa("AsyncFunction"),Kw=o=>o&&(to(o)||xn(o))&&xn(o.then)&&xn(o.catch),Ep=((o,e)=>o?setImmediate:e?((a,r)=>(di.addEventListener("message",({source:i,data:l})=>{i===di&&l===a&&r.length&&r.shift()()},!1),i=>{r.push(i),di.postMessage(a,"*")}))(`axios@${Math.random()}`,[]):a=>setTimeout(a))(typeof setImmediate=="function",xn(di.postMessage)),Ww=typeof queueMicrotask<"u"?queueMicrotask.bind(di):typeof process<"u"&&process.nextTick||Ep,F={isArray:Zi,isArrayBuffer:mp,isBuffer:eo,isFormData:ww,isArrayBufferView:cw,isString:uw,isNumber:yp,isBoolean:dw,isObject:to,isPlainObject:zl,isEmptyObject:fw,isReadableStream:Ew,isRequest:_w,isResponse:Aw,isHeaders:xw,isUndefined:es,isDate:hw,isFile:gw,isReactNativeBlob:pw,isReactNative:mw,isBlob:yw,isRegExp:jw,isFunction:xn,isStream:vw,isURLSearchParams:Cw,isTypedArray:$w,isFileList:bw,forEach:no,merge:ud,extend:Rw,trim:Tw,stripBOM:kw,inherits:Dw,toFlatObject:Ow,kindOf:Il,kindOfTest:fa,endsWith:Lw,toArray:Mw,forEachEntry:Nw,matchAll:Iw,isHTMLForm:Hw,hasOwnProperty:dd,hasOwnProp:dd,reduceDescriptors:Cp,freezeMethods:Uw,toObjectSet:Bw,toCamelCase:zw,noop:Pw,toFiniteNumber:qw,findKey:Sp,global:di,isContextDefined:wp,isSpecCompliantForm:Gw,toJSONObject:Vw,isAsyncFn:Fw,isThenable:Kw,setImmediate:Ep,asap:Ww,isIterable:o=>o!=null&&xn(o[Nl])},Yw=F.toObjectSet(["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"]),Xw=o=>{const e={};let a,r,i;return o&&o.split(` +`).forEach(function(u){i=u.indexOf(":"),a=u.substring(0,i).trim().toLowerCase(),r=u.substring(i+1).trim(),!(!a||e[a]&&Yw[a])&&(a==="set-cookie"?e[a]?e[a].push(r):e[a]=[r]:e[a]=e[a]?e[a]+", "+r:r)}),e};function Jw(o){let e=0,a=o.length;for(;ee;){const r=o.charCodeAt(a-1);if(r!==9&&r!==32)break;a-=1}return e===0&&a===o.length?o:o.slice(e,a)}const Qw=new RegExp("[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+","g"),Zw=new RegExp("[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+","g");function fd(o,e){return F.isArray(o)?o.map(a=>fd(a,e)):Jw(String(o).replace(e,""))}const e0=o=>fd(o,Qw),t0=o=>fd(o,Zw);function _p(o){const e=Object.create(null);return F.forEach(o.toJSON(),(a,r)=>{e[r]=t0(a)}),e}const Ap=Symbol("internals");function ao(o){return o&&String(o).trim().toLowerCase()}function jl(o){return o===!1||o==null?o:F.isArray(o)?o.map(jl):e0(String(o))}function n0(o){const e=Object.create(null),a=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let r;for(;r=a.exec(o);)e[r[1]]=r[2];return e}const a0=o=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(o.trim());function hd(o,e,a,r,i){if(F.isFunction(r))return r.call(this,e,a);if(i&&(e=a),!!F.isString(e)){if(F.isString(r))return e.indexOf(r)!==-1;if(F.isRegExp(r))return r.test(e)}}function r0(o){return o.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(e,a,r)=>a.toUpperCase()+r)}function i0(o,e){const a=F.toCamelCase(" "+e);["get","set","has"].forEach(r=>{Object.defineProperty(o,r+a,{__proto__:null,value:function(i,l,u){return this[r].call(this,e,i,l,u)},configurable:!0})})}let Cn=class{constructor(e){e&&this.set(e)}set(e,a,r){const i=this;function l(d,f,g){const m=ao(f);if(!m)throw new Error("header name must be a non-empty string");const b=F.findKey(i,m);(!b||i[b]===void 0||g===!0||g===void 0&&i[b]!==!1)&&(i[b||f]=jl(d))}const u=(d,f)=>F.forEach(d,(g,m)=>l(g,m,f));if(F.isPlainObject(e)||e instanceof this.constructor)u(e,a);else if(F.isString(e)&&(e=e.trim())&&!a0(e))u(Xw(e),a);else if(F.isObject(e)&&F.isIterable(e)){let d={},f,g;for(const m of e){if(!F.isArray(m))throw TypeError("Object iterator must return a key-value pair");d[g=m[0]]=(f=d[g])?F.isArray(f)?[...f,m[1]]:[f,m[1]]:m[1]}u(d,a)}else e!=null&&l(a,e,r);return this}get(e,a){if(e=ao(e),e){const r=F.findKey(this,e);if(r){const i=this[r];if(!a)return i;if(a===!0)return n0(i);if(F.isFunction(a))return a.call(this,i,r);if(F.isRegExp(a))return a.exec(i);throw new TypeError("parser must be boolean|regexp|function")}}}has(e,a){if(e=ao(e),e){const r=F.findKey(this,e);return!!(r&&this[r]!==void 0&&(!a||hd(this,this[r],r,a)))}return!1}delete(e,a){const r=this;let i=!1;function l(u){if(u=ao(u),u){const d=F.findKey(r,u);d&&(!a||hd(r,r[d],d,a))&&(delete r[d],i=!0)}}return F.isArray(e)?e.forEach(l):l(e),i}clear(e){const a=Object.keys(this);let r=a.length,i=!1;for(;r--;){const l=a[r];(!e||hd(this,this[l],l,e,!0))&&(delete this[l],i=!0)}return i}normalize(e){const a=this,r={};return F.forEach(this,(i,l)=>{const u=F.findKey(r,l);if(u){a[u]=jl(i),delete a[l];return}const d=e?r0(l):String(l).trim();d!==l&&delete a[l],a[d]=jl(i),r[d]=!0}),this}concat(...e){return this.constructor.concat(this,...e)}toJSON(e){const a=Object.create(null);return F.forEach(this,(r,i)=>{r!=null&&r!==!1&&(a[i]=e&&F.isArray(r)?r.join(", "):r)}),a}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([e,a])=>e+": "+a).join(` +`)}getSetCookie(){return this.get("set-cookie")||[]}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(e){return e instanceof this?e:new this(e)}static concat(e,...a){const r=new this(e);return a.forEach(i=>r.set(i)),r}static accessor(e){const r=(this[Ap]=this[Ap]={accessors:{}}).accessors,i=this.prototype;function l(u){const d=ao(u);r[d]||(i0(i,u),r[d]=!0)}return F.isArray(e)?e.forEach(l):l(e),this}};Cn.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]),F.reduceDescriptors(Cn.prototype,({value:o},e)=>{let a=e[0].toUpperCase()+e.slice(1);return{get:()=>o,set(r){this[a]=r}}}),F.freezeMethods(Cn);const s0="[REDACTED ****]";function o0(o){if(F.hasOwnProp(o,"toJSON"))return!0;let e=Object.getPrototypeOf(o);for(;e&&e!==Object.prototype;){if(F.hasOwnProp(e,"toJSON"))return!0;e=Object.getPrototypeOf(e)}return!1}function l0(o,e){const a=new Set(e.map(l=>String(l).toLowerCase())),r=[],i=l=>{if(l===null||typeof l!="object"||F.isBuffer(l))return l;if(r.indexOf(l)!==-1)return;l instanceof Cn&&(l=l.toJSON()),r.push(l);let u;if(F.isArray(l))u=[],l.forEach((d,f)=>{const g=i(d);F.isUndefined(g)||(u[f]=g)});else{if(!F.isPlainObject(l)&&o0(l))return r.pop(),l;u=Object.create(null);for(const[d,f]of Object.entries(l)){const g=a.has(d.toLowerCase())?s0:i(f);F.isUndefined(g)||(u[d]=g)}}return r.pop(),u};return i(o)}let Oe=class kS extends Error{static from(e,a,r,i,l,u){const d=new kS(e.message,a||e.code,r,i,l);return d.cause=e,d.name=e.name,e.status!=null&&d.status==null&&(d.status=e.status),u&&Object.assign(d,u),d}constructor(e,a,r,i,l){super(e),Object.defineProperty(this,"message",{__proto__:null,value:e,enumerable:!0,writable:!0,configurable:!0}),this.name="AxiosError",this.isAxiosError=!0,a&&(this.code=a),r&&(this.config=r),i&&(this.request=i),l&&(this.response=l,this.status=l.status)}toJSON(){const e=this.config,a=e&&F.hasOwnProp(e,"redact")?e.redact:void 0,r=F.isArray(a)&&a.length>0?l0(e,a):F.toJSONObject(e);return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:r,code:this.code,status:this.status}}};Oe.ERR_BAD_OPTION_VALUE="ERR_BAD_OPTION_VALUE",Oe.ERR_BAD_OPTION="ERR_BAD_OPTION",Oe.ECONNABORTED="ECONNABORTED",Oe.ETIMEDOUT="ETIMEDOUT",Oe.ECONNREFUSED="ECONNREFUSED",Oe.ERR_NETWORK="ERR_NETWORK",Oe.ERR_FR_TOO_MANY_REDIRECTS="ERR_FR_TOO_MANY_REDIRECTS",Oe.ERR_DEPRECATED="ERR_DEPRECATED",Oe.ERR_BAD_RESPONSE="ERR_BAD_RESPONSE",Oe.ERR_BAD_REQUEST="ERR_BAD_REQUEST",Oe.ERR_CANCELED="ERR_CANCELED",Oe.ERR_NOT_SUPPORT="ERR_NOT_SUPPORT",Oe.ERR_INVALID_URL="ERR_INVALID_URL",Oe.ERR_FORM_DATA_DEPTH_EXCEEDED="ERR_FORM_DATA_DEPTH_EXCEEDED";const c0=null;function gd(o){return F.isPlainObject(o)||F.isArray(o)}function xp(o){return F.endsWith(o,"[]")?o.slice(0,-2):o}function pd(o,e,a){return o?o.concat(e).map(function(i,l){return i=xp(i),!a&&l?"["+i+"]":i}).join(a?".":""):e}function u0(o){return F.isArray(o)&&!o.some(gd)}const d0=F.toFlatObject(F,{},null,function(e){return/^is[A-Z]/.test(e)});function Ul(o,e,a){if(!F.isObject(o))throw new TypeError("target must be an object");e=e||new FormData,a=F.toFlatObject(a,{metaTokens:!0,dots:!1,indexes:!1},!1,function(T,L){return!F.isUndefined(L[T])});const r=a.metaTokens,i=a.visitor||b,l=a.dots,u=a.indexes,d=a.Blob||typeof Blob<"u"&&Blob,f=a.maxDepth===void 0?100:a.maxDepth,g=d&&F.isSpecCompliantForm(e);if(!F.isFunction(i))throw new TypeError("visitor must be a function");function m(A){if(A===null)return"";if(F.isDate(A))return A.toISOString();if(F.isBoolean(A))return A.toString();if(!g&&F.isBlob(A))throw new Oe("Blob is not supported. Use a Buffer instead.");return F.isArrayBuffer(A)||F.isTypedArray(A)?g&&typeof Blob=="function"?new Blob([A]):Buffer.from(A):A}function b(A,T,L){let M=A;if(F.isReactNative(e)&&F.isReactNativeBlob(A))return e.append(pd(L,T,l),m(A)),!1;if(A&&!L&&typeof A=="object"){if(F.endsWith(T,"{}"))T=r?T:T.slice(0,-2),A=JSON.stringify(A);else if(F.isArray(A)&&u0(A)||(F.isFileList(A)||F.endsWith(T,"[]"))&&(M=F.toArray(A)))return T=xp(T),M.forEach(function(D,U){!(F.isUndefined(D)||D===null)&&e.append(u===!0?pd([T],U,l):u===null?T:T+"[]",m(D))}),!1}return gd(A)?!0:(e.append(pd(L,T,l),m(A)),!1)}const v=[],S=Object.assign(d0,{defaultVisitor:b,convertValue:m,isVisitable:gd});function E(A,T,L=0){if(!F.isUndefined(A)){if(L>f)throw new Oe("Object is too deeply nested ("+L+" levels). Max depth: "+f,Oe.ERR_FORM_DATA_DEPTH_EXCEEDED);if(v.indexOf(A)!==-1)throw Error("Circular reference detected in "+T.join("."));v.push(A),F.forEach(A,function(R,D){(!(F.isUndefined(R)||R===null)&&i.call(e,R,F.isString(D)?D.trim():D,T,S))===!0&&E(R,T?T.concat(D):[D],L+1)}),v.pop()}}if(!F.isObject(o))throw new TypeError("data must be an object");return E(o),e}function Tp(o){const e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E","%20":"+"};return encodeURIComponent(o).replace(/[!'()~]|%20/g,function(r){return e[r]})}function md(o,e){this._pairs=[],o&&Ul(o,this,e)}const Rp=md.prototype;Rp.append=function(e,a){this._pairs.push([e,a])},Rp.toString=function(e){const a=e?function(r){return e.call(this,r,Tp)}:Tp;return this._pairs.map(function(i){return a(i[0])+"="+a(i[1])},"").join("&")};function f0(o){return encodeURIComponent(o).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+")}function kp(o,e,a){if(!e)return o;const r=a&&a.encode||f0,i=F.isFunction(a)?{serialize:a}:a,l=i&&i.serialize;let u;if(l?u=l(e,i):u=F.isURLSearchParams(e)?e.toString():new md(e,i).toString(r),u){const d=o.indexOf("#");d!==-1&&(o=o.slice(0,d)),o+=(o.indexOf("?")===-1?"?":"&")+u}return o}class Dp{constructor(){this.handlers=[]}use(e,a,r){return this.handlers.push({fulfilled:e,rejected:a,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null}),this.handlers.length-1}eject(e){this.handlers[e]&&(this.handlers[e]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(e){F.forEach(this.handlers,function(r){r!==null&&e(r)})}}const yd={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0},h0={isBrowser:!0,classes:{URLSearchParams:typeof URLSearchParams<"u"?URLSearchParams:md,FormData:typeof FormData<"u"?FormData:null,Blob:typeof Blob<"u"?Blob:null},protocols:["http","https","file","blob","url","data"]},bd=typeof window<"u"&&typeof document<"u",vd=typeof navigator=="object"&&navigator||void 0,g0=bd&&(!vd||["ReactNative","NativeScript","NS"].indexOf(vd.product)<0),p0=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope&&typeof self.importScripts=="function",m0=bd&&window.location.href||"http://localhost",yn={...Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:bd,hasStandardBrowserEnv:g0,hasStandardBrowserWebWorkerEnv:p0,navigator:vd,origin:m0},Symbol.toStringTag,{value:"Module"})),...h0};function y0(o,e){return Ul(o,new yn.classes.URLSearchParams,{visitor:function(a,r,i,l){return yn.isNode&&F.isBuffer(a)?(this.append(r,a.toString("base64")),!1):l.defaultVisitor.apply(this,arguments)},...e})}function b0(o){return F.matchAll(/\w+|\[(\w*)]/g,o).map(e=>e[0]==="[]"?"":e[1]||e[0])}function v0(o){const e={},a=Object.keys(o);let r;const i=a.length;let l;for(r=0;r=a.length;return u=!u&&F.isArray(i)?i.length:u,f?(F.hasOwnProp(i,u)?i[u]=F.isArray(i[u])?i[u].concat(r):[i[u],r]:i[u]=r,!d):((!F.hasOwnProp(i,u)||!F.isObject(i[u]))&&(i[u]=[]),e(a,r,i[u],l)&&F.isArray(i[u])&&(i[u]=v0(i[u])),!d)}if(F.isFormData(o)&&F.isFunction(o.entries)){const a={};return F.forEachEntry(o,(r,i)=>{e(b0(r),i,a,0)}),a}return null}const ts=(o,e)=>o!=null&&F.hasOwnProp(o,e)?o[e]:void 0;function S0(o,e,a){if(F.isString(o))try{return(e||JSON.parse)(o),F.trim(o)}catch(r){if(r.name!=="SyntaxError")throw r}return(a||JSON.stringify)(o)}const ro={transitional:yd,adapter:["xhr","http","fetch"],transformRequest:[function(e,a){const r=a.getContentType()||"",i=r.indexOf("application/json")>-1,l=F.isObject(e);if(l&&F.isHTMLForm(e)&&(e=new FormData(e)),F.isFormData(e))return i?JSON.stringify(Op(e)):e;if(F.isArrayBuffer(e)||F.isBuffer(e)||F.isStream(e)||F.isFile(e)||F.isBlob(e)||F.isReadableStream(e))return e;if(F.isArrayBufferView(e))return e.buffer;if(F.isURLSearchParams(e))return a.setContentType("application/x-www-form-urlencoded;charset=utf-8",!1),e.toString();let d;if(l){const f=ts(this,"formSerializer");if(r.indexOf("application/x-www-form-urlencoded")>-1)return y0(e,f).toString();if((d=F.isFileList(e))||r.indexOf("multipart/form-data")>-1){const g=ts(this,"env"),m=g&&g.FormData;return Ul(d?{"files[]":e}:e,m&&new m,f)}}return l||i?(a.setContentType("application/json",!1),S0(e)):e}],transformResponse:[function(e){const a=ts(this,"transitional")||ro.transitional,r=a&&a.forcedJSONParsing,i=ts(this,"responseType"),l=i==="json";if(F.isResponse(e)||F.isReadableStream(e))return e;if(e&&F.isString(e)&&(r&&!i||l)){const d=!(a&&a.silentJSONParsing)&&l;try{return JSON.parse(e,ts(this,"parseReviver"))}catch(f){if(d)throw f.name==="SyntaxError"?Oe.from(f,Oe.ERR_BAD_RESPONSE,this,null,ts(this,"response")):f}}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,env:{FormData:yn.classes.FormData,Blob:yn.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:"application/json, text/plain, */*","Content-Type":void 0}}};F.forEach(["delete","get","head","post","put","patch","query"],o=>{ro.headers[o]={}});function Sd(o,e){const a=this||ro,r=e||a,i=Cn.from(r.headers);let l=r.data;return F.forEach(o,function(d){l=d.call(a,l,i.normalize(),e?e.status:void 0)}),i.normalize(),l}function Lp(o){return!!(o&&o.__CANCEL__)}let io=class extends Oe{constructor(e,a,r){super(e??"canceled",Oe.ERR_CANCELED,a,r),this.name="CanceledError",this.__CANCEL__=!0}};function Mp(o,e,a){const r=a.config.validateStatus;!a.status||!r||r(a.status)?o(a):e(new Oe("Request failed with status code "+a.status,a.status>=400&&a.status<500?Oe.ERR_BAD_REQUEST:Oe.ERR_BAD_RESPONSE,a.config,a.request,a))}function w0(o){const e=/^([-+\w]{1,25}):(?:\/\/)?/.exec(o);return e&&e[1]||""}function C0(o,e){o=o||10;const a=new Array(o),r=new Array(o);let i=0,l=0,u;return e=e!==void 0?e:1e3,function(f){const g=Date.now(),m=r[l];u||(u=g),a[i]=f,r[i]=g;let b=l,v=0;for(;b!==i;)v+=a[b++],b=b%o;if(i=(i+1)%o,i===l&&(l=(l+1)%o),g-u{a=m,i=null,l&&(clearTimeout(l),l=null),o(...g)};return[(...g)=>{const m=Date.now(),b=m-a;b>=r?u(g,m):(i=g,l||(l=setTimeout(()=>{l=null,u(i)},r-b)))},()=>i&&u(i)]}const Bl=(o,e,a=3)=>{let r=0;const i=C0(50,250);return E0(l=>{if(!l||typeof l.loaded!="number")return;const u=l.loaded,d=l.lengthComputable?l.total:void 0,f=d!=null?Math.min(u,d):u,g=Math.max(0,f-r),m=i(g);r=Math.max(r,f);const b={loaded:f,total:d,progress:d?f/d:void 0,bytes:g,rate:m||void 0,estimated:m&&d?(d-f)/m:void 0,event:l,lengthComputable:d!=null,[e?"download":"upload"]:!0};o(b)},a)},$p=(o,e)=>{const a=o!=null;return[r=>e[0]({lengthComputable:a,total:o,loaded:r}),e[1]]},Np=o=>(...e)=>F.asap(()=>o(...e)),_0=yn.hasStandardBrowserEnv?((o,e)=>a=>(a=new URL(a,yn.origin),o.protocol===a.protocol&&o.host===a.host&&(e||o.port===a.port)))(new URL(yn.origin),yn.navigator&&/(msie|trident)/i.test(yn.navigator.userAgent)):()=>!0,A0=yn.hasStandardBrowserEnv?{write(o,e,a,r,i,l,u){if(typeof document>"u")return;const d=[`${o}=${encodeURIComponent(e)}`];F.isNumber(a)&&d.push(`expires=${new Date(a).toUTCString()}`),F.isString(r)&&d.push(`path=${r}`),F.isString(i)&&d.push(`domain=${i}`),l===!0&&d.push("secure"),F.isString(u)&&d.push(`SameSite=${u}`),document.cookie=d.join("; ")},read(o){if(typeof document>"u")return null;const e=document.cookie.split(";");for(let a=0;ao instanceof Cn?{...o}:o;function fi(o,e){e=e||{};const a=Object.create(null);Object.defineProperty(a,"hasOwnProperty",{__proto__:null,value:Object.prototype.hasOwnProperty,enumerable:!1,writable:!0,configurable:!0});function r(g,m,b,v){return F.isPlainObject(g)&&F.isPlainObject(m)?F.merge.call({caseless:v},g,m):F.isPlainObject(m)?F.merge({},m):F.isArray(m)?m.slice():m}function i(g,m,b,v){if(F.isUndefined(m)){if(!F.isUndefined(g))return r(void 0,g,b,v)}else return r(g,m,b,v)}function l(g,m){if(!F.isUndefined(m))return r(void 0,m)}function u(g,m){if(F.isUndefined(m)){if(!F.isUndefined(g))return r(void 0,g)}else return r(void 0,m)}function d(g,m,b){if(F.hasOwnProp(e,b))return r(g,m);if(F.hasOwnProp(o,b))return r(void 0,g)}const f={url:l,method:l,data:l,baseURL:u,transformRequest:u,transformResponse:u,paramsSerializer:u,timeout:u,timeoutMessage:u,withCredentials:u,withXSRFToken:u,adapter:u,responseType:u,xsrfCookieName:u,xsrfHeaderName:u,onUploadProgress:u,onDownloadProgress:u,decompress:u,maxContentLength:u,maxBodyLength:u,beforeRedirect:u,transport:u,httpAgent:u,httpsAgent:u,cancelToken:u,socketPath:u,allowedSocketPaths:u,responseEncoding:u,validateStatus:d,headers:(g,m,b)=>i(Hp(g),Hp(m),b,!0)};return F.forEach(Object.keys({...o,...e}),function(m){if(m==="__proto__"||m==="constructor"||m==="prototype")return;const b=F.hasOwnProp(f,m)?f[m]:i,v=F.hasOwnProp(o,m)?o[m]:void 0,S=F.hasOwnProp(e,m)?e[m]:void 0,E=b(v,S,m);F.isUndefined(E)&&b!==d||(a[m]=E)}),a}const R0=["content-type","content-length"];function k0(o,e,a){if(a!=="content-only"){o.set(e);return}Object.entries(e).forEach(([r,i])=>{R0.includes(r.toLowerCase())&&o.set(r,i)})}const D0=o=>encodeURIComponent(o).replace(/%([0-9A-F]{2})/gi,(e,a)=>String.fromCharCode(parseInt(a,16))),zp=o=>{const e=fi({},o),a=v=>F.hasOwnProp(e,v)?e[v]:void 0,r=a("data");let i=a("withXSRFToken");const l=a("xsrfHeaderName"),u=a("xsrfCookieName");let d=a("headers");const f=a("auth"),g=a("baseURL"),m=a("allowAbsoluteUrls"),b=a("url");if(e.headers=d=Cn.from(d),e.url=kp(Ip(g,b,m),o.params,o.paramsSerializer),f&&d.set("Authorization","Basic "+btoa((f.username||"")+":"+(f.password?D0(f.password):""))),F.isFormData(r)&&(yn.hasStandardBrowserEnv||yn.hasStandardBrowserWebWorkerEnv?d.setContentType(void 0):F.isFunction(r.getHeaders)&&k0(d,r.getHeaders(),a("formDataHeaderPolicy"))),yn.hasStandardBrowserEnv&&(F.isFunction(i)&&(i=i(e)),i===!0||i==null&&_0(e.url))){const S=l&&u&&A0.read(u);S&&d.set(l,S)}return e},O0=typeof XMLHttpRequest<"u"&&function(o){return new Promise(function(a,r){const i=zp(o);let l=i.data;const u=Cn.from(i.headers).normalize();let{responseType:d,onUploadProgress:f,onDownloadProgress:g}=i,m,b,v,S,E;function A(){S&&S(),E&&E(),i.cancelToken&&i.cancelToken.unsubscribe(m),i.signal&&i.signal.removeEventListener("abort",m)}let T=new XMLHttpRequest;T.open(i.method.toUpperCase(),i.url,!0),T.timeout=i.timeout;function L(){if(!T)return;const R=Cn.from("getAllResponseHeaders"in T&&T.getAllResponseHeaders()),U={data:!d||d==="text"||d==="json"?T.responseText:T.response,status:T.status,statusText:T.statusText,headers:R,config:o,request:T};Mp(function(P){a(P),A()},function(P){r(P),A()},U),T=null}"onloadend"in T?T.onloadend=L:T.onreadystatechange=function(){!T||T.readyState!==4||T.status===0&&!(T.responseURL&&T.responseURL.startsWith("file:"))||setTimeout(L)},T.onabort=function(){T&&(r(new Oe("Request aborted",Oe.ECONNABORTED,o,T)),A(),T=null)},T.onerror=function(D){const U=D&&D.message?D.message:"Network Error",j=new Oe(U,Oe.ERR_NETWORK,o,T);j.event=D||null,r(j),A(),T=null},T.ontimeout=function(){let D=i.timeout?"timeout of "+i.timeout+"ms exceeded":"timeout exceeded";const U=i.transitional||yd;i.timeoutErrorMessage&&(D=i.timeoutErrorMessage),r(new Oe(D,U.clarifyTimeoutError?Oe.ETIMEDOUT:Oe.ECONNABORTED,o,T)),A(),T=null},l===void 0&&u.setContentType(null),"setRequestHeader"in T&&F.forEach(_p(u),function(D,U){T.setRequestHeader(U,D)}),F.isUndefined(i.withCredentials)||(T.withCredentials=!!i.withCredentials),d&&d!=="json"&&(T.responseType=i.responseType),g&&([v,E]=Bl(g,!0),T.addEventListener("progress",v)),f&&T.upload&&([b,S]=Bl(f),T.upload.addEventListener("progress",b),T.upload.addEventListener("loadend",S)),(i.cancelToken||i.signal)&&(m=R=>{T&&(r(!R||R.type?new io(null,o,T):R),T.abort(),A(),T=null)},i.cancelToken&&i.cancelToken.subscribe(m),i.signal&&(i.signal.aborted?m():i.signal.addEventListener("abort",m)));const M=w0(i.url);if(M&&!yn.protocols.includes(M)){r(new Oe("Unsupported protocol "+M+":",Oe.ERR_BAD_REQUEST,o));return}T.send(l||null)})},L0=(o,e)=>{if(o=o?o.filter(Boolean):[],!e&&!o.length)return;const a=new AbortController;let r=!1;const i=function(f){if(!r){r=!0,u();const g=f instanceof Error?f:this.reason;a.abort(g instanceof Oe?g:new io(g instanceof Error?g.message:g))}};let l=e&&setTimeout(()=>{l=null,i(new Oe(`timeout of ${e}ms exceeded`,Oe.ETIMEDOUT))},e);const u=()=>{o&&(l&&clearTimeout(l),l=null,o.forEach(f=>{f.unsubscribe?f.unsubscribe(i):f.removeEventListener("abort",i)}),o=null)};o.forEach(f=>f.addEventListener("abort",i));const{signal:d}=a;return d.unsubscribe=()=>F.asap(u),d},M0=function*(o,e){let a=o.byteLength;if(a{const i=$0(o,e);let l=0,u,d=f=>{u||(u=!0,r&&r(f))};return new ReadableStream({async pull(f){try{const{done:g,value:m}=await i.next();if(g){d(),f.close();return}let b=m.byteLength;if(a){let v=l+=b;a(v)}f.enqueue(new Uint8Array(m))}catch(g){throw d(g),g}},cancel(f){return d(f),i.return()}},{highWaterMark:2})};function I0(o){if(!o||typeof o!="string"||!o.startsWith("data:"))return 0;const e=o.indexOf(",");if(e<0)return 0;const a=o.slice(5,e),r=o.slice(e+1);if(/;base64/i.test(a)){let u=r.length;const d=r.length;for(let S=0;S=48&&E<=57||E>=65&&E<=70||E>=97&&E<=102)&&(A>=48&&A<=57||A>=65&&A<=70||A>=97&&A<=102)&&(u-=2,S+=2)}let f=0,g=d-1;const m=S=>S>=2&&r.charCodeAt(S-2)===37&&r.charCodeAt(S-1)===51&&(r.charCodeAt(S)===68||r.charCodeAt(S)===100);g>=0&&(r.charCodeAt(g)===61?(f++,g--):m(g)&&(f++,g-=3)),f===1&&g>=0&&(r.charCodeAt(g)===61||m(g))&&f++;const v=Math.floor(u/4)*3-(f||0);return v>0?v:0}if(typeof Buffer<"u"&&typeof Buffer.byteLength=="function")return Buffer.byteLength(r,"utf8");let l=0;for(let u=0,d=r.length;u=55296&&f<=56319&&u+1=56320&&g<=57343?(l+=4,u++):l+=3}else l+=3}return l}const wd="1.16.1",Up=64*1024,{isFunction:Pl}=F,Bp=(o,...e)=>{try{return!!o(...e)}catch{return!1}},H0=o=>{const e=F.global!==void 0&&F.global!==null?F.global:globalThis,{ReadableStream:a,TextEncoder:r}=e;o=F.merge.call({skipUndefined:!0},{Request:e.Request,Response:e.Response},o);const{fetch:i,Request:l,Response:u}=o,d=i?Pl(i):typeof fetch=="function",f=Pl(l),g=Pl(u);if(!d)return!1;const m=d&&Pl(a),b=d&&(typeof r=="function"?(L=>M=>L.encode(M))(new r):async L=>new Uint8Array(await new l(L).arrayBuffer())),v=f&&m&&Bp(()=>{let L=!1;const M=new l(yn.origin,{body:new a,method:"POST",get duplex(){return L=!0,"half"}}),R=M.headers.has("Content-Type");return M.body!=null&&M.body.cancel(),L&&!R}),S=g&&m&&Bp(()=>F.isReadableStream(new u("").body)),E={stream:S&&(L=>L.body)};d&&["text","arrayBuffer","blob","formData","stream"].forEach(L=>{!E[L]&&(E[L]=(M,R)=>{let D=M&&M[L];if(D)return D.call(M);throw new Oe(`Response type '${L}' is not supported`,Oe.ERR_NOT_SUPPORT,R)})});const A=async L=>{if(L==null)return 0;if(F.isBlob(L))return L.size;if(F.isSpecCompliantForm(L))return(await new l(yn.origin,{method:"POST",body:L}).arrayBuffer()).byteLength;if(F.isArrayBufferView(L)||F.isArrayBuffer(L))return L.byteLength;if(F.isURLSearchParams(L)&&(L=L+""),F.isString(L))return(await b(L)).byteLength},T=async(L,M)=>{const R=F.toFiniteNumber(L.getContentLength());return R??A(M)};return async L=>{let{url:M,method:R,data:D,signal:U,cancelToken:j,timeout:P,onDownloadProgress:X,onUploadProgress:ge,responseType:Ee,headers:Ce,withCredentials:$e="same-origin",fetchOptions:Pe,maxContentLength:Ye,maxBodyLength:ht}=zp(L);const Q=F.isNumber(Ye)&&Ye>-1,fe=F.isNumber(ht)&&ht>-1;let ve=i||fetch;Ee=Ee?(Ee+"").toLowerCase():"text";let te=L0([U,j&&j.toAbortSignal()],P),he=null;const x=te&&te.unsubscribe&&(()=>{te.unsubscribe()});let K;try{if(Q&&typeof M=="string"&&M.startsWith("data:")&&I0(M)>Ye)throw new Oe("maxContentLength size of "+Ye+" exceeded",Oe.ERR_BAD_RESPONSE,L,he);if(fe&&R!=="get"&&R!=="head"){const Se=await T(Ce,D);if(typeof Se=="number"&&isFinite(Se)&&Se>ht)throw new Oe("Request body larger than maxBodyLength limit",Oe.ERR_BAD_REQUEST,L,he)}if(ge&&v&&R!=="get"&&R!=="head"&&(K=await T(Ce,D))!==0){let Se=new l(M,{method:"POST",body:D,duplex:"half"}),He;if(F.isFormData(D)&&(He=Se.headers.get("content-type"))&&Ce.setContentType(He),Se.body){const[Ae,Et]=$p(K,Bl(Np(ge)));D=jp(Se.body,Up,Ae,Et)}}F.isString($e)||($e=$e?"include":"omit");const re=f&&"credentials"in l.prototype;if(F.isFormData(D)){const Se=Ce.getContentType();Se&&/^multipart\/form-data/i.test(Se)&&!/boundary=/i.test(Se)&&Ce.delete("content-type")}Ce.set("User-Agent","axios/"+wd,!1);const de={...Pe,signal:te,method:R.toUpperCase(),headers:_p(Ce.normalize()),body:D,duplex:"half",credentials:re?$e:void 0};he=f&&new l(M,de);let ie=await(f?ve(he,Pe):ve(M,de));if(Q){const Se=F.toFiniteNumber(ie.headers.get("content-length"));if(Se!=null&&Se>Ye)throw new Oe("maxContentLength size of "+Ye+" exceeded",Oe.ERR_BAD_RESPONSE,L,he)}const ye=S&&(Ee==="stream"||Ee==="response");if(S&&ie.body&&(X||Q||ye&&x)){const Se={};["status","statusText","headers"].forEach(ft=>{Se[ft]=ie[ft]});const He=F.toFiniteNumber(ie.headers.get("content-length")),[Ae,Et]=X&&$p(He,Bl(Np(X),!0))||[];let kt=0;const _t=ft=>{if(Q&&(kt=ft,kt>Ye))throw new Oe("maxContentLength size of "+Ye+" exceeded",Oe.ERR_BAD_RESPONSE,L,he);Ae&&Ae(ft)};ie=new u(jp(ie.body,Up,_t,()=>{Et&&Et(),x&&x()}),Se)}Ee=Ee||"text";let Ne=await E[F.findKey(E,Ee)||"text"](ie,L);if(Q&&!S&&!ye){let Se;if(Ne!=null&&(typeof Ne.byteLength=="number"?Se=Ne.byteLength:typeof Ne.size=="number"?Se=Ne.size:typeof Ne=="string"&&(Se=typeof r=="function"?new r().encode(Ne).byteLength:Ne.length)),typeof Se=="number"&&Se>Ye)throw new Oe("maxContentLength size of "+Ye+" exceeded",Oe.ERR_BAD_RESPONSE,L,he)}return!ye&&x&&x(),await new Promise((Se,He)=>{Mp(Se,He,{data:Ne,headers:Cn.from(ie.headers),status:ie.status,statusText:ie.statusText,config:L,request:he})})}catch(re){if(x&&x(),te&&te.aborted&&te.reason instanceof Oe){const de=te.reason;throw de.config=L,he&&(de.request=he),re!==de&&(de.cause=re),de}throw re&&re.name==="TypeError"&&/Load failed|fetch/i.test(re.message)?Object.assign(new Oe("Network Error",Oe.ERR_NETWORK,L,he,re&&re.response),{cause:re.cause||re}):Oe.from(re,re&&re.code,L,he,re&&re.response)}}},z0=new Map,Pp=o=>{let e=o&&o.env||{};const{fetch:a,Request:r,Response:i}=e,l=[r,i,a];let u=l.length,d=u,f,g,m=z0;for(;d--;)f=l[d],g=m.get(f),g===void 0&&m.set(f,g=d?new Map:H0(e)),m=g;return g};Pp();const Cd={http:c0,xhr:O0,fetch:{get:Pp}};F.forEach(Cd,(o,e)=>{if(o){try{Object.defineProperty(o,"name",{__proto__:null,value:e})}catch{}Object.defineProperty(o,"adapterName",{__proto__:null,value:e})}});const qp=o=>`- ${o}`,j0=o=>F.isFunction(o)||o===null||o===!1;function U0(o,e){o=F.isArray(o)?o:[o];const{length:a}=o;let r,i;const l={};for(let u=0;u`adapter ${f} `+(g===!1?"is not supported by the environment":"is not available in the build"));let d=a?u.length>1?`since : +`+u.map(qp).join(` +`):" "+qp(u[0]):"as no adapter specified";throw new Oe("There is no suitable adapter to dispatch the request "+d,"ERR_NOT_SUPPORT")}return i}const Gp={getAdapter:U0,adapters:Cd};function Ed(o){if(o.cancelToken&&o.cancelToken.throwIfRequested(),o.signal&&o.signal.aborted)throw new io(null,o)}function Vp(o){return Ed(o),o.headers=Cn.from(o.headers),o.data=Sd.call(o,o.transformRequest),["post","put","patch"].indexOf(o.method)!==-1&&o.headers.setContentType("application/x-www-form-urlencoded",!1),Gp.getAdapter(o.adapter||ro.adapter,o)(o).then(function(r){Ed(o),o.response=r;try{r.data=Sd.call(o,o.transformResponse,r)}finally{delete o.response}return r.headers=Cn.from(r.headers),r},function(r){if(!Lp(r)&&(Ed(o),r&&r.response)){o.response=r.response;try{r.response.data=Sd.call(o,o.transformResponse,r.response)}finally{delete o.response}r.response.headers=Cn.from(r.response.headers)}return Promise.reject(r)})}const ql={};["object","boolean","number","function","string","symbol"].forEach((o,e)=>{ql[o]=function(r){return typeof r===o||"a"+(e<1?"n ":" ")+o}});const Fp={};ql.transitional=function(e,a,r){function i(l,u){return"[Axios v"+wd+"] Transitional option '"+l+"'"+u+(r?". "+r:"")}return(l,u,d)=>{if(e===!1)throw new Oe(i(u," has been removed"+(a?" in "+a:"")),Oe.ERR_DEPRECATED);return a&&!Fp[u]&&(Fp[u]=!0,console.warn(i(u," has been deprecated since v"+a+" and will be removed in the near future"))),e?e(l,u,d):!0}},ql.spelling=function(e){return(a,r)=>(console.warn(`${r} is likely a misspelling of ${e}`),!0)};function B0(o,e,a){if(typeof o!="object")throw new Oe("options must be an object",Oe.ERR_BAD_OPTION_VALUE);const r=Object.keys(o);let i=r.length;for(;i-- >0;){const l=r[i],u=Object.prototype.hasOwnProperty.call(e,l)?e[l]:void 0;if(u){const d=o[l],f=d===void 0||u(d,l,o);if(f!==!0)throw new Oe("option "+l+" must be "+f,Oe.ERR_BAD_OPTION_VALUE);continue}if(a!==!0)throw new Oe("Unknown option "+l,Oe.ERR_BAD_OPTION)}}const Gl={assertOptions:B0,validators:ql},Jn=Gl.validators;let hi=class{constructor(e){this.defaults=e||{},this.interceptors={request:new Dp,response:new Dp}}async request(e,a){try{return await this._request(e,a)}catch(r){if(r instanceof Error){let i={};Error.captureStackTrace?Error.captureStackTrace(i):i=new Error;const l=(()=>{if(!i.stack)return"";const u=i.stack.indexOf(` `);return u===-1?"":i.stack.slice(u+1)})();try{if(!r.stack)r.stack=l;else if(l){const u=l.indexOf(` `),d=u===-1?-1:l.indexOf(` `,u+1),f=d===-1?"":l.slice(d+1);String(r.stack).endsWith(f)||(r.stack+=` -`+l)}}catch{}}throw r}}_request(e,n){typeof e=="string"?(n=n||{},n.url=e):n=e||{},n=hi(this.defaults,n);const{transitional:r,paramsSerializer:i,headers:l}=n;r!==void 0&&Kl.assertOptions(r,{silentJSONParsing:En.transitional(En.boolean),forcedJSONParsing:En.transitional(En.boolean),clarifyTimeoutError:En.transitional(En.boolean),legacyInterceptorReqResOrdering:En.transitional(En.boolean),advertiseZstdAcceptEncoding:En.transitional(En.boolean),validateStatusUndefinedResolves:En.transitional(En.boolean)},!1),i!=null&&(V.isFunction(i)?n.paramsSerializer={serialize:i}:Kl.assertOptions(i,{encode:En.function,serialize:En.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),Kl.assertOptions(n,{baseUrl:En.spelling("baseURL"),withXsrfToken:En.spelling("withXSRFToken")},!0),n.method=(n.method||this.defaults.method||"get").toLowerCase();let u=l&&V.merge(l.common,l[n.method]);l&&V.forEach(["delete","get","head","post","put","patch","query","common"],_=>{delete l[_]}),n.headers=Cn.concat(u,l);const d=[];let f=!0;this.interceptors.request.forEach(function(x){if(typeof x.runWhen=="function"&&x.runWhen(n)===!1)return;f=f&&x.synchronous;const k=n.transitional||Sd;k&&k.legacyInterceptorReqResOrdering?d.unshift(x.fulfilled,x.rejected):d.push(x.fulfilled,x.rejected)});const g=[];this.interceptors.response.forEach(function(x){g.push(x.fulfilled,x.rejected)});let m,b=0,v;if(!f){const _=[Zp.bind(this),void 0];for(_.unshift(...d),_.push(...g),v=_.length,m=Promise.resolve(n);b{if(!r._listeners)return;let l=r._listeners.length;for(;l-- >0;)r._listeners[l](i);r._listeners=null}),this.promise.then=i=>{let l;const u=new Promise(d=>{r.subscribe(d),l=d}).then(i);return u.cancel=function(){r.unsubscribe(l)},u},e(function(l,u,d){r.reason||(r.reason=new lo(l,u,d),n(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;const n=this._listeners.indexOf(e);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const e=new AbortController,n=r=>{e.abort(r)};return this.subscribe(n),e.signal.unsubscribe=()=>this.unsubscribe(n),e.signal}static source(){let e;return{token:new HS(function(i){e=i}),cancel:e}}};function iC(o){return function(n){return o.apply(null,n)}}function sC(o){return V.isObject(o)&&o.isAxiosError===!0}const Td={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Td).forEach(([o,e])=>{Td[e]=o});function tm(o){const e=new gi(o),n=mp(gi.prototype.request,e);return V.extend(n,gi.prototype,e,{allOwnKeys:!0}),V.extend(n,e,null,{allOwnKeys:!0}),n.create=function(i){return tm(hi(o,i))},n}const Ft=tm(oo);Ft.Axios=gi,Ft.CanceledError=lo,Ft.CancelToken=rC,Ft.isCancel=zp,Ft.VERSION=_d,Ft.toFormData=Pl,Ft.AxiosError=Ce,Ft.Cancel=Ft.CanceledError,Ft.all=function(e){return Promise.all(e)},Ft.spread=iC,Ft.isAxiosError=sC,Ft.mergeConfig=hi,Ft.AxiosHeaders=Cn,Ft.formToJSON=o=>Hp(V.isHTMLForm(o)?new FormData(o):o),Ft.getAdapter=Qp.getAdapter,Ft.HttpStatusCode=Td,Ft.default=Ft;const{Axios:CT,AxiosError:ET,CanceledError:_T,isCancel:AT,CancelToken:xT,VERSION:TT,all:RT,Cancel:kT,isAxiosError:DT,spread:OT,toFormData:LT,AxiosHeaders:MT,HttpStatusCode:$T,formToJSON:NT,getAdapter:IT,mergeConfig:HT,create:zT}=Ft,Rd="g7.identity.redirectStash",Wl=dt("IdentityGuardInterceptor");class wt{static setLauncher(e){this.launcher=e}static hasLauncher(){return this.launcher!==null}static markDomainNoticeShown(){this.domainNoticeShown=!0}static consumeDomainNoticeShown(){const e=this.domainNoticeShown;return this.domainNoticeShown=!1,e}static resolveDeferred(e){const n=this.deferredResolver;if(!n){Wl.warn("IdentityGuardInterceptor.resolveDeferred 호출됐지만 대기 중인 launcher 가 없습니다. 모달이 launcher 외부에서 열렸는지 확인하세요.");return}this.deferredResolver=null,n(e)}static createDeferred(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null,e({status:"cancelled"})}return new Promise(e=>{this.deferredResolver=e})}static isIdentityRequired(e,n){return e===428&&typeof n=="object"&&n!==null&&n.error_code==="identity_verification_required"}static async handle(e,n,r){this.domainNoticeShown=!1;const i=this.launcher??lC,u=!!(r&&(r.email||r.phone))?{...e.verification,target:r}:e.verification,d=await i(u);if(d.status!=="verified")return null;const f=u.return_request;if(!f)return null;const g=oC(f.url,d.token);return fetch(g,{method:f.method,headers:n?.headers??{Accept:"application/json","Content-Type":"application/json"},body:n?.body,credentials:n?.credentials??"same-origin"})}static redirectExternally(e){const n=e.redirect_url;if(!n)return Wl.error("redirectExternally 호출 시 verification.redirect_url 이 없습니다. payload:",e),Promise.resolve({status:"failed",failureCode:"MISSING_REDIRECT_URL",reason:"verification.redirect_url is required for external_redirect flow"});if(typeof window<"u"){const r={return_url:window.location.href,payload:e,stashed_at:Date.now()};try{window.sessionStorage?.setItem(Rd,JSON.stringify(r))}catch(i){Wl.warn("sessionStorage 접근 실패 — stash 없이 redirect 합니다.",i)}window.location.href=n}return new Promise(()=>{})}static reset(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null;try{e({status:"cancelled"})}catch{}}this.launcher=null,this.domainNoticeShown=!1}}$(wt,"launcher",null),$(wt,"deferredResolver",null),$(wt,"domainNoticeShown",!1);function oC(o,e){if(!e)return o;try{const n=typeof window<"u"?window.location.origin:"http://localhost",r=new URL(o,n);return r.searchParams.set("verification_token",e),/^https?:\/\//i.test(o)?r.toString():`${r.pathname}${r.search}${r.hash}`}catch{const n=o.includes("?")?"&":"?";return`${o}${n}verification_token=${encodeURIComponent(e)}`}}const lC=async o=>{if(typeof window>"u")return{status:"cancelled"};if(o.render_hint==="external_redirect"||o.redirect_url)return wt.redirectExternally(o);const e=window.G7Core;if(!e?.dispatch)return console.error("[IdentityGuardInterceptor] defaultLauncher: G7Core 가 초기화되지 않아 본인인증 흐름을 시작할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};try{await e.dispatch({handler:"toast",params:{message:"본인 확인이 필요합니다.",variant:"warning"}})}catch{}const n=window.location.href,r={return_url:n,payload:o,stashed_at:Date.now()};try{window.sessionStorage?.setItem(Rd,JSON.stringify(r))}catch{}const i=`/identity/challenge?return=${encodeURIComponent(n)}`;try{await e.dispatch({handler:"navigate",params:{path:i}})}catch(l){Wl.warn("navigate 실패 — window.location 으로 폴백",l),window.location.href=i}return new Promise(()=>{})},cC=dt("ApiClient");function kd(){return typeof window<"u"&&window.__G7_DEVTOOLS__?window.__G7_DEVTOOLS__:null}class uC{constructor(e={}){$(this,"client");$(this,"config");$(this,"TOKEN_KEY","auth_token");this.config={baseURL:e.baseURL||"/api",timeout:e.timeout||3e4,onTokenExpired:e.onTokenExpired,onUnauthorized:e.onUnauthorized,onError:e.onError},this.client=Ft.create({baseURL:this.config.baseURL,timeout:this.config.timeout,headers:{"Content-Type":"application/json",Accept:"application/json"},paramsSerializer:{serialize:n=>{const r=[];for(const[i,l]of Object.entries(n))if(l!=null)if(Array.isArray(l)){const u=i.endsWith("[]")?i:`${i}[]`;for(const d of l)r.push(`${encodeURIComponent(u)}=${encodeURIComponent(String(d))}`)}else r.push(`${encodeURIComponent(i)}=${encodeURIComponent(String(l))}`);return r.join("&")}}}),this.setupInterceptors()}setToken(e){typeof window<"u"&&localStorage.setItem(this.TOKEN_KEY,e)}getToken(){return typeof window<"u"?localStorage.getItem(this.TOKEN_KEY):null}removeToken(){typeof window<"u"&&localStorage.removeItem(this.TOKEN_KEY)}setupInterceptors(){this.client.interceptors.request.use(e=>{e.url?.startsWith("/api")&&e.baseURL==="/api"&&(e.baseURL="");const n=this.getToken();if(n&&e.headers&&(e.headers.Authorization=`Bearer ${n}`),typeof window<"u"&&e.headers){const i=localStorage.getItem("g7_locale");i&&(e.headers["Accept-Language"]=i)}const r=kd();if(r?.isEnabled()){const i=this.buildFullUrl(e),l=(e.method||"GET").toUpperCase(),u=r.trackRequest(i,l,{requestBody:e.data});e._devToolsRequestId=u}return e},e=>Promise.reject(e)),this.client.interceptors.response.use(e=>{const n=e.config,r=n._devToolsRequestId;if(r){const i=kd();i?.isEnabled()&&i.completeRequest(r,e.status,e.data),n._devToolsRequestId=null}return e},async e=>{const n=e.config,r=n?.url||"",l=["/auth/","/layouts/"].some(u=>r.includes(u));if(e.response?.status===401&&!n?._retry&&!l){if(n._retry=!0,await ga.getInstance().refreshToken()){const f=this.getToken();return f&&(n.headers.Authorization=`Bearer ${f}`),n._devToolsRequestId=null,this.client(n)}return this.completeDevToolsRequest(n,e),this.removeToken(),this.config.onUnauthorized&&this.config.onUnauthorized(),Promise.reject(e)}if(e.response?.status===401&&l)return this.completeDevToolsRequest(n,e),Promise.reject(e);if(wt.isIdentityRequired(e.response?.status,e.response?.data)){this.completeDevToolsRequest(n,e);const u={},d=n?.headers??{};for(const[v,S]of Object.entries(d))typeof S=="string"&&(u[v]=S);let f;n?.data!==void 0&&n?.data!==null&&(f=typeof n.data=="string"?n.data:JSON.stringify(n.data),u["Content-Type"]=u["Content-Type"]??"application/json");const g=n?.identity_target,m=await wt.handle(e.response.data,{headers:u,body:f,credentials:"same-origin"},g);if(!m)return Promise.reject(e);let b=null;try{b=await m.json()}catch{b=null}return m.ok?{...e.response,status:m.status,data:b}:Promise.reject(Object.assign(new Error("replay failed"),{response:{status:m.status,data:b}}))}return e.response?.status===403?(this.completeDevToolsRequest(n,e),this.callOnError(e),Promise.reject(e)):(this.completeDevToolsRequest(n,e),e.response&&this.callOnError(e),Promise.reject(e))})}async get(e,n){return(await this.client.get(e,n)).data}async post(e,n,r){return(await this.client.post(e,n,r)).data}async put(e,n,r){return(await this.client.put(e,n,r)).data}async patch(e,n,r){return(await this.client.patch(e,n,r)).data}async delete(e,n){return(await this.client.delete(e,n)).data}getInstance(){return this.client}setOnUnauthorized(e){this.config.onUnauthorized=e}setOnError(e){this.config.onError=e}completeDevToolsRequest(e,n){if(!e?._devToolsRequestId)return;const r=kd();if(!r?.isEnabled())return;const i=e._devToolsRequestId;e._devToolsRequestId=null,n.response?r.completeRequest(i,n.response.status,n.response.data):n.request?r.failRequest(i,n.message||"Network error"):r.failRequest(i,n.message||"Request error")}buildFullUrl(e){let n=e.url||"";if(e.baseURL&&!n.startsWith("http")&&(n=`${e.baseURL}${n.startsWith("/")?"":"/"}${n}`),e.params&&typeof e.params=="object"){const r=e.params,i=[];for(const[l,u]of Object.entries(r))if(u!=null)if(Array.isArray(u)){const d=l.endsWith("[]")?l:`${l}[]`;for(const f of u)i.push(`${encodeURIComponent(d)}=${encodeURIComponent(String(f))}`)}else i.push(`${encodeURIComponent(l)}=${encodeURIComponent(String(u))}`);i.length>0&&(n+=(n.includes("?")?"&":"?")+i.join("&"))}return n}callOnError(e){if(!this.config.onError)return;const n={status:e.response?.status||0,message:e.response?.data?.message||e.message||"Unknown error",data:e.response?.data,statusText:e.response?.statusText};try{this.config.onError(n)}catch(r){cC.error("Error in onError handler:",r)}}}let Dd=null;function Ia(){return Dd||(Dd=new uC),Dd}const co=dt("AuthManager");function is(o,e,n,r){try{window.G7Core?.devTools?.trackAuthEvent?.(o,e,n,r)}catch{}}const Yl={admin:{type:"admin",loginPath:"/admin/login",defaultPath:"/admin",userEndpoint:"/admin/auth/user",loginEndpoint:"/auth/admin/login",logoutEndpoint:"/admin/auth/logout",refreshEndpoint:"/admin/auth/refresh"},user:{type:"user",loginPath:"/login",defaultPath:"/",userEndpoint:"/auth/user",loginEndpoint:"/auth/login",logoutEndpoint:"/auth/logout",refreshEndpoint:"/auth/refresh"}},Ca=class Ca{constructor(){$(this,"state");$(this,"config");$(this,"isRefreshing",!1);$(this,"refreshPromise",null);$(this,"eventHandlers",new Map);this.state={isAuthenticated:!1,user:null,type:null},this.config=new Map,this.config.set("admin",Yl.admin),this.config.set("user",Yl.user)}static getInstance(){return Ca.instance||(Ca.instance=new Ca),Ca.instance}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}emit(e,...n){const r=this.eventHandlers.get(e);r&&r.forEach(i=>i(...n))}isAuthenticated(){return this.state.isAuthenticated}getUser(){return this.state.user}getAuthType(){return this.state.type}async checkAuth(e){const n=Ia();if(!n.getToken())return this.clearState(),!1;const i=this.config.get(e);if(!i)return co.error(`Unknown auth type: ${e}`),!1;try{const l=await n.get(i.userEndpoint);return l.success&&l.data?(this.state={isAuthenticated:!0,user:l.data,type:e},this.emit("authStateChange",this.state),!0):(this.clearState(),!1)}catch(l){return l.response?.status===401&&await this.refreshToken()?this.checkAuth(e):(this.clearState(),!1)}}async preloadAuth(e){try{return await this.checkAuth(e)}catch(n){return co.warn(`Preload auth failed for type ${e}:`,n),!1}}async login(e,n,r){const i=Ia();if(!this.config.get(e))throw new Error(`Unknown auth type: ${e}`);const u=e==="admin"?Yl.admin.loginEndpoint:Yl.user.loginEndpoint;try{const d=r?.headers?{headers:r.headers}:void 0,f=await i.post(u,n,d);if(f.success&&f.data){i.setToken(f.data.token);const g=f.data.user.language,m=localStorage.getItem(Ca.LOCALE_STORAGE_KEY),b=g&&g!==m;if(g)try{localStorage.setItem(Ca.LOCALE_STORAGE_KEY,g)}catch(v){co.warn("Failed to save user language to localStorage:",v)}return this.state={isAuthenticated:!0,user:f.data.user,type:e},this.emit("login",this.state),this.emit("authStateChange",this.state),is("login",!0,void 0,{userId:f.data.user.uuid,email:f.data.user.email,type:e}),b&&window.__templateApp&&window.__templateApp.changeLocale(g),f.data.user}throw new Error("Login failed")}catch(d){this.clearState();const f=d.response?.data?.message,g=new Error(f||d.message||"Login failed");throw g.response=d.response,g.status=d.response?.status,is("login",!1,g.message,{type:e,status:d.response?.status}),g}}async logout(){const e=Ia(),n=this.state.type?this.config.get(this.state.type):null;try{n&&await e.post(n.logoutEndpoint)}catch(r){co.warn("Logout API call failed:",r)}finally{e.removeToken();const r={...this.state};if(this.clearState(),this.emit("logout",r),this.emit("authStateChange",this.state),is("logout",!0,void 0,{previousType:r.type}),n&&r.type){const i=window.location.pathname+window.location.search;window.location.href=this.getLoginRedirectUrl(r.type,i)}}}async refreshToken(){if(this.isRefreshing&&this.refreshPromise)return this.refreshPromise;this.isRefreshing=!0,this.refreshPromise=this.doRefreshToken();try{return await this.refreshPromise}finally{this.isRefreshing=!1,this.refreshPromise=null}}async doRefreshToken(){const e=this.state.type;if(!e)return!1;const n=this.config.get(e);if(!n)return!1;const r=Ia();try{const i=await r.post(n.refreshEndpoint);return i.success&&i.data?.token?(r.setToken(i.data.token),this.emit("tokenRefreshed"),is("token-refresh",!0,void 0,{type:e}),!0):(is("token-refresh",!1,"No token in response",{type:e}),!1)}catch(i){return co.error("Token refresh failed:",i),is("token-refresh",!1,i instanceof Error?i.message:"Unknown error",{type:e}),!1}}getLoginRedirectUrl(e,n,r){const i=this.config.get(e);if(!i)return"/login";const l=encodeURIComponent(n);let u=`${i.loginPath}?redirect=${l}`;return r&&(u+=`&reason=${encodeURIComponent(r)}`),u}updateConfig(e,n){if(n.loginPath!==void 0){const i=n.loginPath;if(!i.startsWith("/")||i.startsWith("//"))throw new Error(`AuthManager.updateConfig: loginPath must be a same-origin path starting with '/' (got: ${i})`)}const r=this.config.get(e);r&&this.config.set(e,{...r,...n})}getRedirectUrl(e){const r=this.config.get(e)?.defaultPath||"/",l=new URLSearchParams(window.location.search).get("redirect");if(l)try{const u=decodeURIComponent(l);if(u.startsWith("/"))return u}catch{}return r}getConfig(e){return this.config.get(e)}clearState(){this.state={isAuthenticated:!1,user:null,type:null}}static resetInstance(){Ca.instance=null}};$(Ca,"instance"),$(Ca,"LOCALE_STORAGE_KEY","g7_locale");let ga=Ca;const nm={401:{handler:"navigate",params:{path:"{{auth.loginPath}}"}},403:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},404:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},422:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},default:{handler:"toast",params:{type:"error",message:"{{error.message}}"}}},Qa={findHandler(o,e){if(o){if(o[e])return o[e];if(o[String(e)])return o[String(e)];if(o.default)return o.default}},normalizeOnError(o){return o?Array.isArray(o)?o:[o]:[]},injectErrorCode(o,e,n){if(o.handler!=="showErrorPage"||o.params?.errorCode!==void 0)return o;const r=typeof e=="string"?parseInt(e,10):e,i=isNaN(r)?n:r;return i===void 0?o:{...o,params:{...o.params,errorCode:i}}}},pa=dt("ErrorHandlingResolver"),kr=class kr{constructor(){$(this,"templateErrorHandling",null);$(this,"layoutErrorHandling",null);$(this,"executeAction",null)}static getInstance(){return kr.instance||(kr.instance=new kr),kr.instance}static resetInstance(){kr.instance=null}setTemplateConfig(e){this.templateErrorHandling=e,pa.log("Template config set:",e)}setLayoutConfig(e){this.layoutErrorHandling=e,pa.log("Layout config set:",e)}setActionExecutor(e){this.executeAction=e}resolve(e,n={}){const{errorHandling:r,onError:i}=n;pa.log("Resolving error:",e,{hasActionErrorHandling:!!r,hasActionOnError:!!i,hasLayoutConfig:!!this.layoutErrorHandling,hasTemplateConfig:!!this.templateErrorHandling});let l=Qa.findHandler(r,e);if(l){const u=this.getMatchedKey(r,e);return{handler:Qa.injectErrorCode(l,u,e),level:"action",matchedKey:u}}if(i){const u=Qa.normalizeOnError(i);if(u.length>0)return{handler:u.length===1?u[0]:{handler:"sequence",actions:u},level:"action",matchedKey:"onError"}}if(l=Qa.findHandler(this.layoutErrorHandling||void 0,e),l){const u=this.getMatchedKey(this.layoutErrorHandling,e);return{handler:Qa.injectErrorCode(l,u,e),level:"layout",matchedKey:u}}if(l=Qa.findHandler(this.templateErrorHandling||void 0,e),l){const u=this.getMatchedKey(this.templateErrorHandling,e);return{handler:Qa.injectErrorCode(l,u,e),level:"template",matchedKey:u}}if(l=Qa.findHandler(nm,e),l){const u=this.getMatchedKey(nm,e);return{handler:Qa.injectErrorCode(l,u,e),level:"system",matchedKey:u}}return{handler:null,level:null,matchedKey:null}}async execute(e,n){if(!this.executeAction){pa.error("Action executor is not set");return}pa.log("Executing handler:",e.handler,{errorContext:n});try{return await this.executeAction(e,{error:n})}catch(r){throw pa.error("Failed to execute handler:",r),r}}async resolveAndExecute(e,n,r={}){const i=this.resolve(e,r);if(pa.log("Resolve result:",{errorCode:e,handler:i.handler?.handler,level:i.level,matchedKey:i.matchedKey}),!i.handler)return pa.warn("No handler found for error:",e),{handled:!1};try{return{handled:!0,result:await this.execute(i.handler,n)}}catch(l){return pa.error("Handler execution failed:",l),{handled:!1}}}getMatchedKey(e,n){return e[n]?n:e[String(n)]?String(n):"default"}clearLayoutConfig(){this.layoutErrorHandling=null,pa.log("Layout config cleared")}clearAllConfig(){this.templateErrorHandling=null,this.layoutErrorHandling=null,pa.log("All config cleared")}getConfigStatus(){return{hasTemplate:this.templateErrorHandling!==null,hasLayout:this.layoutErrorHandling!==null,hasExecutor:this.executeAction!==null}}};$(kr,"instance",null);let Od=kr;function ma(){return Od.getInstance()}var Ld={exports:{}},uo={};var am;function dC(){if(am)return uo;am=1;var o=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function n(r,i,l){var u=null;if(l!==void 0&&(u=""+l),i.key!==void 0&&(u=""+i.key),"key"in i){l={};for(var d in i)d!=="key"&&(l[d]=i[d])}else l=i;return i=l.ref,{$$typeof:o,type:r,key:u,ref:i!==void 0?i:null,props:l}}return uo.Fragment=e,uo.jsx=n,uo.jsxs=n,uo}var rm;function fC(){return rm||(rm=1,Ld.exports=dC()),Ld.exports}var ft=fC();const hC=Ya({__proto__:null,default:Xa(ft)},[ft]),Md=dt("ParentContextProvider"),im=I.createContext(null);let Xl=null;function $d(){Xl?(Md.log("[triggerModalParentUpdate] 모달 부모 컨텍스트 업데이트 트리거"),Xl()):Md.warn("[triggerModalParentUpdate] Provider가 아직 마운트되지 않음")}const sm=({children:o})=>{const[e,n]=I.useState(0),r=I.useRef(!0),i=I.useCallback(()=>{const u=window.__g7LayoutContextStack||[];return u[u.length-1]?.dataContext},[]);I.useEffect(()=>(r.current=!0,Xl=()=>{r.current&&(n(u=>u+1),Md.log("[ParentContextProvider] 버전 업데이트됨"))},()=>{r.current=!1,Xl=null}),[]);const l={version:e,getParentDataContext:i};return ft.jsx(im.Provider,{value:l,children:o})};function gC(){return I.useContext(im)}const z=dt("ActionDispatcher"),om=new Set(["navigate","navigateBack","navigateForward","replaceUrl","refresh","logout"]),pC=new Set(["redirect"]);function qn(){try{return window.G7Core?.devTools}catch{return}}class Ut extends Error{constructor(n,r,i){super(n);$(this,"action");$(this,"originalError");$(this,"unknownHandler",!1);this.action=r,this.originalError=i,this.name="ActionError"}}const gs=class gs{constructor(e={},n,r){$(this,"bindingEngine");$(this,"translationEngine");$(this,"translationContext");$(this,"customHandlers",new Map);$(this,"defaultContext");$(this,"globalStateUpdater");$(this,"errorHandlingSetup",!1);$(this,"debounceTimers",new Map);$(this,"pendingDebounceFlushers",new Map);$(this,"debounceAccumulatedValues",new Map);$(this,"globalHeaders",[]);$(this,"namedActions",{});$(this,"previewMode",!1);$(this,"intervals",new Map);this.bindingEngine=new Rn,this.translationEngine=n,this.translationContext=r,this.defaultContext=e,this.registerDefaultHandlers(),this.setupErrorHandling()}setGlobalHeaders(e){this.globalHeaders=e||[]}setNamedActions(e){this.namedActions=e||{};const n=qn();n?.isEnabled?.()&&n.setNamedActionDefinitions?.(this.namedActions),z.log("[setNamedActions] registered:",Object.keys(this.namedActions))}getNamedActions(){return this.namedActions}setPreviewMode(e){this.previewMode=e,z.log("Preview mode:",e?"enabled":"disabled")}isPreviewMode(){return this.previewMode}static getPreviewSuppressedHandlers(){return om}static getPreviewSuppressedLayoutFeatures(){return pC}resolveActionRef(e){if(!e.actionRef)return e;const n=this.namedActions[e.actionRef];if(!n)return z.warn(`[resolveActionRef] named action not found: "${e.actionRef}"`),e;const r=qn();r?.isEnabled?.()&&r.trackNamedActionRef?.({actionRefName:e.actionRef,resolvedHandler:n.handler,timestamp:Date.now()});const{actionRef:i,type:l,key:u,event:d,...f}=e,g={...n,...Object.fromEntries(Object.entries(f).filter(([,m])=>m!==void 0)),type:l??n.type};return u!==void 0&&(g.key=u),d!==void 0&&(g.event=d),g}matchesPattern(e,n){if(n==="*")return!0;const r=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}getMatchingGlobalHeaders(e,n){const r={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,u])=>{const d=Zi(u,n,{skipCache:!0});d!=null&&d!==""&&(r[l]=String(d))});return r}setupErrorHandling(){if(this.errorHandlingSetup)return;ma().setActionExecutor(async(n,r)=>{const i={type:"click",handler:n.handler,target:n.target,params:n.params,actions:n.actions},l={data:{...this.defaultContext.data,error:r.error}};return await this.dispatchAction(i,l)}),this.errorHandlingSetup=!0}registerDefaultHandlers(){this.registerHandler("refetchDataSource",async(n,r)=>{const i=n.params?.dataSourceId,l=n.params?.sync;if(!i){z.warn("refetchDataSource: dataSourceId is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.refetch){const u=window.G7Core?.state?.get?.()||{},d={...r.state?._global,...u},f=window.G7Core?.state?.getLocal?.()||{},g=r.data?._local||r.state||{},m={...f,...g},b=r.isolatedContext?.state;z.log("[refetchDataSource] localStateOverride:",m),await window.G7Core.dataSource.refetch(i,{...l?{sync:!0}:{},...d?{globalStateOverride:d}:{},...m?{localStateOverride:m}:{},...b?{isolatedStateOverride:b}:{}})}else z.warn("refetchDataSource: G7Core.dataSource.refetch is not available")}),this.registerHandler("appendDataSource",async(n,r)=>{const{dataSourceId:i,dataPath:l,newData:u}=n.params||{};if(!i){z.warn("appendDataSource: dataSourceId is required");return}if(u===void 0){z.warn("appendDataSource: newData is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.updateData){if(!Array.isArray(u)){z.warn("appendDataSource: newData must be an array, got:",typeof u,u);return}await window.G7Core.dataSource.updateData(i,l||null,u,"append")}else z.warn("appendDataSource: G7Core.dataSource.updateData is not available")}),this.registerHandler("updateDataSource",async(n,r)=>{const{dataSourceId:i,data:l,merge:u=!1}=n.params||{};if(!i){z.warn("updateDataSource: dataSourceId is required");return}if(l===void 0){z.warn("updateDataSource: data is required");return}typeof window<"u"&&window.G7Core?.dataSource?.set?(z.log(`[updateDataSource] Updating dataSource '${i}' with:`,l),window.G7Core.dataSource.set(i,l,{merge:u})):z.warn("updateDataSource: G7Core.dataSource.set is not available")}),this.registerHandler("scrollIntoView",async(n,r)=>{const{selector:i,behavior:l="smooth",block:u="nearest",inline:d="nearest",waitForElement:f=!1,timeout:g=2e3,delay:m=0,retryCount:b=0,retryInterval:v=50,scrollContainer:S}=n.params||{};if(!i){z.warn("scrollIntoView: selector is required");return}if(typeof window>"u"||typeof document>"u"){z.warn("scrollIntoView: window/document is not available");return}let _=null;if(f){if(_=await new Promise(x=>{const k=document.querySelector(i);if(k){x(k);return}const O=setTimeout(()=>{M.disconnect(),x(null)},g),M=new MutationObserver(()=>{const T=document.querySelector(i);T&&(clearTimeout(O),M.disconnect(),x(T))});M.observe(document.body,{childList:!0,subtree:!0})}),!_){z.warn(`scrollIntoView: element not found for selector "${i}" after ${g}ms timeout`);return}}else{m>0&&await new Promise(k=>setTimeout(k,m)),_=document.querySelector(i);let x=0;for(;!_&&xsetTimeout(k,v)),_=document.querySelector(i),x++;if(!_){z.warn(`scrollIntoView: element not found for selector "${i}" after ${x+1} attempts`);return}}if(S){const x=document.querySelector(S);if(!x){z.warn(`scrollIntoView: container not found for selector "${S}"`);return}const k=_.getBoundingClientRect(),O=x.getBoundingClientRect(),M=k.top-O.top+x.scrollTop,T=M+k.height;let D;switch(u){case"start":D=M;break;case"center":D=M-x.clientHeight/2+k.height/2;break;case"end":D=T-x.clientHeight;break;default:const j=k.topO.bottom;if(j)D=M;else if(H)D=T-x.clientHeight;else return;break}x.scrollTo({top:Math.max(0,D),behavior:l});return}_.scrollIntoView({behavior:l,block:u,inline:d})}),this.registerHandler("reloadExtensions",async(n,r)=>{if(typeof window>"u"){z.warn("reloadExtensions: window is not available");return}const i=window.__templateApp;if(!i){z.warn("reloadExtensions: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState()}catch(d){throw z.error("reloadExtensions: reloadExtensionState failed",d),d}else z.warn("reloadExtensions: TemplateApp.reloadExtensionState unavailable");const{moduleInfo:l,pluginInfo:u}=n.params||{};if(l)try{await this.executeAction({handler:"reloadModuleHandlers",params:n.params},r)}catch(d){z.error("reloadExtensions: reloadModuleHandlers failed",d)}if(u)try{await this.executeAction({handler:"reloadPluginHandlers",params:n.params},r)}catch(d){z.error("reloadExtensions: reloadPluginHandlers failed",d)}z.log("reloadExtensions: done")}),this.registerHandler("reloadRoutes",async(n,r)=>{if(typeof window>"u"){z.warn("reloadRoutes: window is not available");return}const i=window.__templateApp;if(!i){z.warn("reloadRoutes: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),z.log("reloadRoutes: delegated to reloadExtensionState")}catch(l){throw z.error("reloadRoutes: Failed to reload routes",l),l}else{const l=i.getRouter?.();l&&(await l.loadRoutes(),z.log("reloadRoutes: Routes reloaded (legacy fallback)"))}}),this.registerHandler("refresh",async(n,r)=>{if(typeof window>"u"){z.warn("refresh: window is not available");return}const i=Number(n.params?.delayMs??0);i>0&&await new Promise(l=>setTimeout(l,i)),window.location.reload()}),this.registerHandler("remount",async(n,r)=>{const{componentId:i}=n.params||{};if(!i){z.warn("remount: componentId parameter is required");return}if(!this.globalStateUpdater){z.warn("remount: globalStateUpdater is not set");return}const d=(window.G7Core?.state?.get()||{})._global?._remountKeys||{},f=d[i]||0;this.globalStateUpdater({_remountKeys:{...d,[i]:f+1}}),z.log(`remount: ${i} key incremented to ${f+1}`)}),this.registerHandler("reloadTranslations",async(n,r)=>{if(typeof window>"u"){z.warn("reloadTranslations: window is not available");return}const i=window.__templateApp;if(!i){z.warn("reloadTranslations: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),z.log("reloadTranslations: delegated to reloadExtensionState")}catch(l){throw z.error("reloadTranslations: Failed to reload translations",l),l}}),this.registerHandler("reloadModuleHandlers",async(n,r)=>{if(typeof window>"u"){z.warn("reloadModuleHandlers: window is not available");return}const{moduleInfo:i,action:l}=n.params||{};if(!i){z.warn("reloadModuleHandlers: moduleInfo is required");return}const u=i.data||i;if(!u.identifier){z.warn("reloadModuleHandlers: moduleInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){z.warn('reloadModuleHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){z.warn("reloadModuleHandlers: G7Config not available");return}const f=u.identifier;try{if(l==="add"){if(u.assets&&(d.moduleAssets=d.moduleAssets||{},d.moduleAssets[f]=u.assets,z.log(`reloadModuleHandlers: Added assets for ${f}`),u.assets.js)){const g=u.assets.js,m=`module-${f}`;if(document.getElementById(m)){z.warn(`reloadModuleHandlers: Script ${m} already loaded`);return}const b=document.createElement("script");if(b.id=m,b.src=g,b.async=!0,await new Promise((v,S)=>{b.onload=()=>{z.log(`reloadModuleHandlers: Script loaded successfully for ${f}`),v()},b.onerror=()=>{z.error(`reloadModuleHandlers: Failed to load script for ${f}`),S(new Error(`Failed to load module script: ${g}`))},document.head.appendChild(b)}),u.assets.css){const v=u.assets.css,S=`module-css-${f}`;if(!document.getElementById(S)){const _=document.createElement("link");_.id=S,_.rel="stylesheet",_.href=v,document.head.appendChild(_),z.log(`reloadModuleHandlers: CSS loaded for ${f}`)}}}}else if(l==="remove"){d.moduleAssets&&d.moduleAssets[f]&&(delete d.moduleAssets[f],z.log(`reloadModuleHandlers: Removed assets for ${f}`));const g=`module-${f}`,m=document.getElementById(g);m&&(m.remove(),z.log(`reloadModuleHandlers: Removed script for ${f}`));const b=`module-css-${f}`,v=document.getElementById(b);v&&(v.remove(),z.log(`reloadModuleHandlers: Removed CSS for ${f}`))}}catch(g){throw z.error(`reloadModuleHandlers: Failed to ${l} module assets`,g),g}}),this.registerHandler("reloadPluginHandlers",async(n,r)=>{if(typeof window>"u"){z.warn("reloadPluginHandlers: window is not available");return}const{pluginInfo:i,action:l}=n.params||{};if(!i){z.warn("reloadPluginHandlers: pluginInfo is required");return}const u=i.data||i;if(!u.identifier){z.warn("reloadPluginHandlers: pluginInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){z.warn('reloadPluginHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){z.warn("reloadPluginHandlers: G7Config not available");return}const f=u.identifier;try{if(l==="add"){if(u.assets&&(d.pluginAssets=d.pluginAssets||{},d.pluginAssets[f]=u.assets,z.log(`reloadPluginHandlers: Added assets for ${f}`),u.assets.js)){const g=u.assets.js,m=`plugin-${f}`;if(document.getElementById(m)){z.warn(`reloadPluginHandlers: Script ${m} already loaded`);return}const b=document.createElement("script");if(b.id=m,b.src=g,b.async=!0,await new Promise((v,S)=>{b.onload=()=>{z.log(`reloadPluginHandlers: Script loaded successfully for ${f}`),v()},b.onerror=()=>{z.error(`reloadPluginHandlers: Failed to load script for ${f}`),S(new Error(`Failed to load plugin script: ${g}`))},document.head.appendChild(b)}),u.assets.css){const v=u.assets.css,S=`plugin-css-${f}`;if(!document.getElementById(S)){const _=document.createElement("link");_.id=S,_.rel="stylesheet",_.href=v,document.head.appendChild(_),z.log(`reloadPluginHandlers: CSS loaded for ${f}`)}}}}else if(l==="remove"){d.pluginAssets&&d.pluginAssets[f]&&(delete d.pluginAssets[f],z.log(`reloadPluginHandlers: Removed assets for ${f}`));const g=`plugin-${f}`,m=document.getElementById(g);m&&(m.remove(),z.log(`reloadPluginHandlers: Removed script for ${f}`));const b=`plugin-css-${f}`,v=document.getElementById(b);v&&(v.remove(),z.log(`reloadPluginHandlers: Removed CSS for ${f}`))}}catch(g){throw z.error(`reloadPluginHandlers: Failed to ${l} plugin assets`,g),g}});let e=!1;this.registerHandler("showErrorPage",async(n,r)=>{if(e){z.log("showErrorPage: Already active, skipping duplicate call");return}if(e=!0,typeof window>"u"){e=!1,z.warn("showErrorPage: window is not available");return}const i=window.__templateApp;if(!i){e=!1,z.warn("showErrorPage: TemplateApp not initialized");return}const l=i.getErrorPageHandler?.();if(!l){e=!1,z.warn("showErrorPage: ErrorPageHandler not available");return}const u=n.params?.errorCode||500,d=n.params?.target||"content";let f=n.params?.containerId;f||(f=d==="full"?"app":"main_content"),z.log("showErrorPage:",{errorCode:u,target:d,containerId:f});const g=(m,b=6e4)=>new Promise((v,S)=>{const _=document.getElementById(m);if(_){v(_);return}z.log(`showErrorPage: Waiting for container #${m}...`);let x;const k=new MutationObserver((O,M)=>{const T=document.getElementById(m);T&&(z.log(`showErrorPage: Container #${m} found`),M.disconnect(),clearTimeout(x),v(T))});k.observe(document.body,{childList:!0,subtree:!0}),x=setTimeout(()=>{k.disconnect(),S(new Error(`Container #${m} not found within ${b}ms`))},b)});try{await g(f);const m=ma(),b=m.layoutErrorHandling;m.clearLayoutConfig();try{await l.renderError(u,f)||z.warn(`showErrorPage: Failed to render error page for code ${u}`)}finally{m.setLayoutConfig(b)}}catch(m){throw z.error("showErrorPage: Error rendering error page:",m),m}finally{e=!1}}),this.registerHandler("emitEvent",async(n,r)=>{const i=n.params?.event,l=n.params?.data;if(!i){z.warn("emitEvent: event parameter is required");return}if(typeof window>"u"){z.warn("emitEvent: window is not available");return}const u=window.G7Core;if(!u?.componentEvent?.emit){z.warn("emitEvent: G7Core.componentEvent is not available");return}try{z.log(`emitEvent: Emitting "${i}"`,l);const d={...l,_context:{data:r.data,state:r.state}},f=await u.componentEvent.emit(i,d);if(!f||f.length===0?z.warn(`emitEvent: No listeners found for "${i}"`):z.log(`emitEvent: Event "${i}" completed with ${f.length} listener(s)`,f),this.globalStateUpdater){const g=(u?.state?.get()||{})._local||{},m={event:i,success:!0,data:f.length===1?f[0]:f,listeners:f.length};this.globalStateUpdater({_local:{...g,_eventResult:m}});const b=r.state&&typeof r.state=="object"&&!Array.isArray(r.state)?r.state:g;window.__g7SequenceLocalSync={...b,_eventResult:m}}}catch(d){if(z.error(`emitEvent: Event "${i}" failed`,d),this.globalStateUpdater){const f=u?.state?.get()||{};this.globalStateUpdater({_local:{...f._local,_eventResult:{event:i,success:!1,error:d instanceof Error?d.message:String(d)}}})}throw d}}),this.registerHandler("updateProductField",async(n,r)=>{const{productId:i,field:l,value:u}=n.params||{};if(!i||!l){z.warn("updateProductField: productId and field are required");return}if(typeof window>"u"){z.warn("updateProductField: window is not available");return}const d=window.G7Core;if(!d?.state?.get||!this.globalStateUpdater){z.warn("updateProductField: G7Core.state or globalStateUpdater is not available");return}try{const f=d.state.get()||{},m=(f.products?.data?.data||[]).map(v=>v.id===i?{...v,[l]:u,_modified:!0}:v),b=new Set(f._local?.modifiedProductIds||[]);b.add(i),this.globalStateUpdater({products:{...f.products,data:{...f.products?.data,data:m}},_local:{...f._local,modifiedProductIds:Array.from(b)}}),z.log(`updateProductField: Updated product ${i}, field: ${l}, value:`,u)}catch(f){throw z.error("updateProductField: Error updating product field",f),f}}),this.registerHandler("updateOptionField",async(n,r)=>{const{productId:i,optionId:l,field:u,value:d}=n.params||{};if(!i||!l||!u){z.warn("updateOptionField: productId, optionId and field are required");return}if(typeof window>"u"){z.warn("updateOptionField: window is not available");return}const f=window.G7Core;if(!f?.state?.get||!this.globalStateUpdater){z.warn("updateOptionField: G7Core.state or globalStateUpdater is not available");return}try{const g=f.state.get()||{},b=(g.products?.data?.data||[]).map(S=>{if(S.id===i&&S.options){const _=S.options.map(x=>x.id===l?{...x,[u]:d,_modified:!0}:x);return{...S,options:_,_modified:!0}}return S}),v=new Set(g._local?.modifiedProductIds||[]);v.add(i),this.globalStateUpdater({products:{...g.products,data:{...g.products?.data,data:b}},_local:{...g._local,modifiedProductIds:Array.from(v)}}),z.log(`updateOptionField: Updated product ${i} option ${l}, field: ${u}, value:`,d)}catch(g){throw z.error("updateOptionField: Error updating option field",g),g}}),this.registerHandler("setLocale",async(n,r)=>{const i=n.target;if(!i||typeof i!="string"){z.warn("setLocale: Invalid locale:",i);return}const l=window.__templateApp;if(l&&typeof l.changeLocale=="function")try{await l.changeLocale(i),z.log("setLocale: Locale changed to",i)}catch(u){z.error("setLocale: Failed to change locale:",u),window.location.reload()}else{z.warn("setLocale: TemplateApp not found, falling back to page reload");try{localStorage.setItem("g7_locale",i)}catch{}window.location.reload()}}),this.registerHandler("suppress",async()=>{z.log("suppress: Error intentionally suppressed")}),this.registerBuiltInHandlerMetadata()}registerBuiltInHandlerMetadata(){const e=qn();if(!e?.isEnabled())return;const n=[{name:"refetchDataSource",description:"데이터 소스를 다시 fetch합니다"},{name:"appendDataSource",description:"데이터 소스에 새 데이터를 병합합니다 (무한 스크롤용)"},{name:"updateDataSource",description:"API 응답으로 데이터 소스를 직접 업데이트합니다 (refetch 대체)"},{name:"reloadRoutes",description:"라우트를 다시 로드합니다"},{name:"refresh",description:"현재 페이지를 새로고침합니다"},{name:"remount",description:"컴포넌트를 리마운트합니다"},{name:"reloadTranslations",description:"다국어 파일을 다시 로드합니다"},{name:"showErrorPage",description:"에러 페이지를 표시합니다"},{name:"emitEvent",description:"이벤트를 발생시킵니다"},{name:"updateProductField",description:"상품 필드를 인라인 수정합니다"},{name:"updateOptionField",description:"상품 옵션 필드를 인라인 수정합니다"},{name:"setLocale",description:"언어를 변경합니다 (DB 저장 + UI 리렌더링)"}];for(const r of n)e.trackHandlerRegistration(r.name,"built-in",r.description)}createHandler(e,n,r){z.log("createHandler called for:",e.handler,e.type);const i=window.__g7DevTools,l=Date.now(),u=r?.state?{...r.state}:null,d=`handler_${e.handler}_${l}`;return async f=>{if(z.log("Handler invoked for:",e.handler,"event:",f.type),i?.isEnabled?.()&&u){const m=window.G7Core?.state?.getLocal?.()??r?.state??{},b=Date.now()-l;for(const v of Object.keys(u)){const S=u[v],_=m[v];S!==_&&b>100&&(i.trackStaleClosureWarning?.({type:"event-handler-stale",location:`createHandler(${e.handler})`,capturedPath:`_local.${v}`,capturedValue:S,capturedAt:l,currentValue:_,actionId:d,stackTrace:new Error().stack}),z.warn(`[Stale Closure] _local.${v} changed after handler creation:`,`captured="${S}" → current="${_}" (${b}ms ago)`))}}try{f.type!=="change"&&f.preventDefault();let g={};if(f.type==="submit"&&f.target instanceof HTMLFormElement){const v=f.target;new FormData(v).forEach((_,x)=>{g[x]=_})}const m={...this.defaultContext,data:{...n,form:g,_local:r?.state||{},$event:f},event:f,...r&&{state:r.state,setState:r.setState},isolatedContext:r?.isolatedContext};if(e.confirm){let v=this.resolveValue(e.confirm,m.data);if(this.translationEngine&&this.translationContext&&v.startsWith("$t:")&&(v=this.translationEngine.resolveTranslations(v,this.translationContext,m.data)),!confirm(v))return}const b=window.__g7ActionContext;window.__g7ActionContext={...r,data:m.data};try{await this.executeAction(e,m)}finally{window.__g7ActionContext=b}}catch(g){if(z.error("Action execution failed:",g),e.onError){const m=g instanceof Ut&&g.originalError?g.originalError:g,b={...this.defaultContext,data:{...n,error:m,_local:r?.state||{},$event:f},event:f,...r&&{state:r.state,setState:r.setState}},v=Array.isArray(e.onError)?e.onError:[e.onError];for(const S of v)await this.executeAction(S,b)}}}}generateActionId(e,n){const r=e.handler,i=n||"no-target",l=Date.now(),u=Math.random().toString(36).substring(2,9);return`${r}_${i.replace(/[^a-zA-Z0-9]/g,"_")}_${l}_${u}`}async executeAction(e,n){if(e=this.resolveActionRef(e),typeof e.handler=="string"&&e.handler.includes("{{")){const f=this.evaluateExpression(e.handler,n.data);e={...e,handler:f==null?"":String(f)}}const r=qn(),i=r?.isEnabled()?`action_${Date.now()}_${Math.random().toString(36).substring(2,11)}`:void 0,l=performance.now();let u;if(i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),context:this.sanitizeForDevTools({hasState:!!n.state,hasSetState:!!n.setState,dataKeys:n.data?Object.keys(n.data):[]}),startTime:l,status:"started"}),e.if!==void 0)try{const f=this.resolveValue(e.if,n.data),g=f===!0||f==="true"||f&&f!=="false"&&f!=="0"&&f!==!1;if(z.log("[executeAction] if condition result:",e.handler,g),!g)return i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:{skipped:!0,reason:"if condition false"}}),{success:!0,data:void 0}}catch(f){return z.error("[executeAction] if condition error:",e.handler,f),i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:"ConditionEvaluationError",message:f instanceof Error?f.message:String(f),stack:f instanceof Error?f.stack:void 0}}),{success:!0,data:void 0}}let d;try{const f=this.resolveParams(e.params,n.data);i&&(u=this.sanitizeForDevTools(f));const g=e.target?this.resolveValue(e.target,n.data):void 0;if(e.handler==="apiCall"&&n.setState){d=this.generateActionId(e,g);const b=n.state?.loadingActions||{};n.setState({loadingActions:{...b,[d]:!0}})}if(this.previewMode&&om.has(e.handler))return z.warn(`Preview mode: "${e.handler}" suppressed`,g||f),i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"skipped",metadata:{reason:"preview_mode_suppressed"}}),{success:!0,data:void 0};let m;switch(e.handler){case"ensureIdentityVerified":m=await this.handleEnsureIdentityVerified(f);break;case"resolveIdentityChallenge":m=this.handleResolveIdentityChallenge(f);break;case"navigate":const b=f.path||g;m=await this.handleNavigate(b,f,n);break;case"navigateBack":m=await this.handleNavigateBack();break;case"navigateForward":m=await this.handleNavigateForward();break;case"openWindow":{const _=f.path||g;m=await this.handleOpenWindow(_,f);break}case"replaceUrl":{const _=f.path||g||window.location.pathname;m=await this.handleReplaceUrl(_,f);break}case"apiCall":if(e.onSuccess&&r?.isEnabled()&&i){const _=window.G7Core,x={};if(_?.state?.get){const k=_.state.get();x._global=k}n.state&&(x._local=n.state),r.registerStateCaptureForHandler?.(i,["_global","_local"],x)}const v=e.auth_mode??(e.auth_required?"required":"none"),S=e.identity_target?this.resolveParams(e.identity_target,n.data):void 0;m=await this.handleApiCall(g,f,n,v,S);break;case"login":m=await this.handleLogin(g,f,n);break;case"logout":m=await this.handleLogout(g,n);break;case"setState":z.log("[executeAction] setState resolvedParams:",f),m=await this.handleSetState(e.render!==void 0?{...f,__render:e.render}:f,n);break;case"setError":m=await this.handleSetError(g,f,n);break;case"openModal":m=await this.handleOpenModal(g,n);break;case"closeModal":m=await this.handleCloseModal(n);break;case"showAlert":m=await this.handleShowAlert(g,n);break;case"toast":m=await this.handleToast(f,n);break;case"switch":m=await this.handleSwitch(e,n);break;case"conditions":m=await this.handleConditions(e,n);break;case"sequence":m=await this.handleSequence(e,n);break;case"parallel":m=await this.handleParallel(e,n);break;case"startInterval":m=this.handleStartInterval(f,n);break;case"stopInterval":m=this.handleStopInterval(f);break;case"loadScript":m=await this.handleLoadScript(f,e,n);break;case"callExternal":m=await this.handleCallExternal(f,e,n);break;case"callExternalEmbed":m=await this.handleCallExternalEmbed(f,e,n);break;case"saveToLocalStorage":m=await this.handleSaveToLocalStorage(f,n);break;case"loadFromLocalStorage":m=await this.handleLoadFromLocalStorage(f,n);break;default:m=await this.handleCustomAction({...e,target:g,params:f},n);break}if(e.resultTo&&m!==void 0){const{target:b,key:v,merge:S}=e.resultTo,_=this.resolveValue(v,n.data),x=S==="replace"?"replace":S==="shallow"?"shallow":"deep";if(b==="_local"&&n.setState){const k=this.buildNestedUpdate(_,m),O=x!=="deep"?{...k,__mergeMode:x}:k;n.setState(O),z.log(`[resultTo] Saved to _local.${_} (merge=${x}):`,m)}else if(b==="_local"&&this.globalStateUpdater){const M=(window.G7Core?.state?.get()||{})._local||{},T=this.buildNestedUpdate(_,m);let D;x==="replace"?D=T:x==="shallow"?D={...M,...T}:D=this.deepMergeWithState(T,M),this.globalStateUpdater({_local:D}),z.log(`[resultTo] Saved to _local.${_} via globalStateUpdater (merge=${x}):`,m)}else if(b==="_global"&&this.globalStateUpdater){const k=this.buildNestedUpdate(_,m);this.globalStateUpdater(k),z.log(`[resultTo] Saved to _global.${_}:`,m)}else if(b==="_isolated"&&n.isolatedContext){const k=this.buildNestedUpdate(_,m);n.isolatedContext.mergeState(k,x),z.log(`[resultTo] Saved to _isolated.${_} (merge=${x}):`,m)}else z.warn(`[resultTo] Cannot save result: target=${b}, setState=${!!n.setState}, globalStateUpdater=${!!this.globalStateUpdater}, isolatedContext=${!!n.isolatedContext}`)}if(e.onSuccess){if(r?.isEnabled()&&i&&e.handler==="apiCall"){const S=window.G7Core,_={};S?.state?.get&&(_._global=S.state.get()),n.state&&(_._local=n.state),r.detectStaleClosure?.(i,`${e.handler} → onSuccess`,_,"callback-state-capture",i)}const b={...n,data:{...n.data,result:m,response:m}},v=Array.isArray(e.onSuccess)?e.onSuccess:[e.onSuccess];v.length>1?await this.handleSequence({handler:"sequence",type:"click",actions:v},b):v.length===1&&await this.executeAction(v[0],b)}return i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:u,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:this.sanitizeForDevTools(m)}),{success:!0,data:m}}catch(f){const g=f instanceof Ut?f:new Ut(`Failed to execute action: ${e.handler}`,e,f instanceof Error?f:void 0),m=g.originalError?.response||{},b=m.data||{},v=g.originalError?.status||m.status||500,S=b.message||g.message,_={status:v,message:S,errors:b.errors||m.errors,data:b,statusText:g.originalError?.statusText,error_code:b.error_code??m.error_code};if(g.unknownHandler)throw z.warn(`Unknown action handler "${e.handler}" — skipped (extension not loaded?). Not surfaced to the user.`),g;const k=ma().resolve(v,{errorHandling:e.errorHandling,onError:e.onError});if(k.handler){if(r?.isEnabled()&&i&&e.handler==="apiCall"){const M=window.G7Core,T={};M?.state?.get&&(T._global=M.state.get()),n.state&&(T._local=n.state),r.detectStaleClosure?.(i,`${e.handler} → onError`,T,"callback-state-capture",i)}const O={...n,data:{...n.data,error:_}};try{const M={type:"click",handler:k.handler.handler,target:k.handler.target,params:k.handler.params,actions:k.handler.actions};return await this.executeAction(M,O),{success:!1,error:g}}catch(M){return z.error("Error executing error handler:",M),{success:!1,error:g}}}throw i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:u,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:g.name,message:g.message,stack:g.stack}}),g}finally{if(e.handler==="apiCall"&&n.setState&&d){const f=n.state?.loadingActions||{},{[d]:g,...m}=f;n.setState({loadingActions:m})}}}async handleNavigate(e,n,r){let i=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)i=this.buildMergedQueryPath(e,n.query??{});else{const u=new URLSearchParams;for(const[f,g]of Object.entries(n.query))if(g!=null&&g!=="")if(Array.isArray(g)){const m=f.endsWith("[]")?f:`${f}[]`;for(const b of g)b!=null&&b!==""&&u.append(m,String(b))}else u.set(f,String(g));const d=u.toString();d&&(i=`${e}?${d}`)}if(z.log("handleNavigate:",{target:e,params:n,finalPath:i,replace:n.replace,windowLocationSearch:window.location.search}),n.replace===!0){const u=window.G7Core;if(u?.updateQueryParams){const d=typeof n.transition_overlay_target=="string"?n.transition_overlay_target:void 0;await u.updateQueryParams(i,d?{transitionOverlayTarget:d}:void 0),z.log("handleNavigate: Used updateQueryParams for replace mode",d?{transitionOverlayTarget:d}:void 0),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}z.warn("handleNavigate: G7Core.updateQueryParams not available, falling back to React Router")}const l=this.resolveNavigateFallbackAction(i,n);if(l){z.warn(`handleNavigate: No route matched, falling back to "${l.handler}"`),await this.dispatchAction({type:"click",handler:l.handler,params:l.params},r),this.applyScrollOption(n.scroll,n.scrollBehavior,"top");return}if(!r.navigate)throw new Ut("Navigate function is not provided in context");r.navigate(i,{replace:n.replace===!0}),this.applyScrollOption(n.scroll,n.scrollBehavior,"top")}resolveNavigateFallbackAction(e,n){const r=n.fallback;if(r===!1||n.replace===!0)return null;const l=window.__templateApp?.getRouter?.();if(!l||typeof l.match!="function"||typeof l.getRoutes=="function"&&l.getRoutes().length===0)return null;const u=e.split("?")[0];return l.match(u)?null:this.resolveNavigateFallback(r,e,n)}resolveNavigateFallback(e,n,r){return e==null?{handler:"openWindow",params:{path:n,target:"_self"}}:typeof e=="string"?{handler:e,params:{path:n}}:typeof e=="object"&&typeof e.handler=="string"?{handler:e.handler,params:{path:n,...e.params||{}}}:(z.warn("resolveNavigateFallback: unrecognized fallback option, using openWindow",e),{handler:"openWindow",params:{path:n,target:"_blank"}})}applyScrollOption(e,n,r){const i=e===void 0?r:e;if(i==="preserve")return;const l=n==="smooth"?"smooth":"instant",u=b=>{if(typeof b!="object"||b===null)return!1;const v=b;return"container"in v||"to"in v||"block"in v||"offset"in v},d=(b,v,S=0)=>{b===window?window.scrollTo({top:v,left:S,behavior:l}):b.scrollTo({top:v,left:S,behavior:l})},f=(b,v,S,_)=>{if(v===window){b.scrollIntoView({behavior:l,block:S}),_&&window.scrollBy({top:-_,left:0,behavior:l});return}const x=v,k=b.getBoundingClientRect(),O=x.getBoundingClientRect(),M=x.scrollTop+(k.top-O.top);let T;S==="center"?T=M-(x.clientHeight-b.clientHeight)/2:S==="end"?T=M-(x.clientHeight-b.clientHeight):T=M,T-=_,x.scrollTo({top:Math.max(0,T),left:0,behavior:l})},g=()=>{window.scrollTo({top:0,left:0,behavior:l});const b=document.getElementById("app")??document.body;if(!b)return;b.querySelectorAll("*").forEach(S=>{if(S.scrollTop===0&&S.scrollLeft===0)return;const _=window.getComputedStyle(S),x=_.overflowY,k=_.overflowX;(x==="auto"||x==="scroll"||k==="auto"||k==="scroll")&&S.scrollTo({top:0,left:0,behavior:l})})},m=()=>{try{if(u(i)){const b=i,v=b.block??"start",S=typeof b.offset=="number"?b.offset:0;let _=window;if(typeof b.container=="string"&&b.container.length>0){const k=document.querySelector(b.container);if(!k){z.warn(`applyScrollOption: container not found: ${b.container}`);return}_=k}const x=b.to??"top";if(x==="top"){_===window?g():d(_,0);return}if(typeof x=="number"){d(_,x-S);return}if(typeof x=="object"&&x!==null&&("x"in x||"y"in x)){const{x:k=0,y:O=0}=x;d(_,O-S,k);return}if(typeof x=="string"&&(x.startsWith("#")||x.startsWith("."))){const k=document.querySelector(x);k?f(k,_,v,S):z.warn(`applyScrollOption: target element not found: ${x}`);return}z.warn('applyScrollOption: invalid "to" value in extended form',x);return}if(i==="top"){g();return}if(typeof i=="number"){window.scrollTo({top:i,left:0,behavior:l});return}if(typeof i=="object"&&i!==null&&("x"in i||"y"in i)){const{x:b=0,y:v=0}=i;window.scrollTo({top:v,left:b,behavior:l});return}if(typeof i=="string"&&(i.startsWith("#")||i.startsWith("."))){const b=document.querySelector(i);b&&b.scrollIntoView({behavior:l,block:"start"});return}}catch(b){z.warn("applyScrollOption: failed to apply scroll",b)}};typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(m):m()}async handleOpenWindow(e,n){let r=e;if(n.query){const l=new URLSearchParams;for(const[d,f]of Object.entries(n.query))f!=null&&f!==""&&l.set(d,String(f));const u=l.toString();u&&(r=`${e}?${u}`)}const i=n.target==="_self"?"_self":"_blank";z.log("handleOpenWindow:",{target:e,params:n,finalPath:r,windowTarget:i}),i==="_self"?window.location.assign(r):window.open(r,"_blank")}async handleNavigateBack(){z.log("handleNavigateBack"),window.history.back()}async handleNavigateForward(){z.log("handleNavigateForward"),window.history.forward()}async handleReplaceUrl(e,n){let r=e;if(n.query||n.mergeQuery===!0)if(n.mergeQuery===!0)r=this.buildMergedQueryPath(e,n.query??{});else{const i=new URLSearchParams;for(const[u,d]of Object.entries(n.query))if(d!=null&&d!=="")if(Array.isArray(d)){const f=u.endsWith("[]")?u:`${u}[]`;for(const g of d)g!=null&&g!==""&&i.append(f,String(g))}else i.set(u,String(d));const l=i.toString();l&&(r=`${e}?${l}`)}z.log("handleReplaceUrl:",{target:e,params:n,finalPath:r}),window.history.replaceState(null,"",r),this.applyScrollOption(n.scroll,n.scrollBehavior,"preserve")}buildMergedQueryPath(e,n){const r=new URLSearchParams(window.location.search);for(const[u,d]of Object.entries(n))if(d==null||d==="")r.delete(u);else if(Array.isArray(d)){const f=u.endsWith("[]")?u:`${u}[]`;r.delete(u),r.delete(f);for(const g of d)g!=null&&g!==""&&r.append(f,String(g))}else r.set(u,String(d));const i=r.toString(),l=e.split("?")[0];return i?`${l}?${i}`:l}async ensureCsrfToken(){try{await fetch("/sanctum/csrf-cookie",{credentials:"include"})}catch(e){throw new Ut("Failed to fetch CSRF token",void 0,e instanceof Error?e:void 0)}}getCsrfTokenFromCookie(){const r=`; ${document.cookie}`.split("; XSRF-TOKEN=");if(r.length===2){const i=r.pop()?.split(";").shift();return i?decodeURIComponent(i):null}return null}buildQueryString(e){const n=new URLSearchParams;for(const[r,i]of Object.entries(e))if(!(i==null||i===""))if(Array.isArray(i))for(const l of i)l!=null&&l!==""&&n.append(`${r}[]`,String(l));else typeof i=="object"?n.append(r,JSON.stringify(i)):n.append(r,String(i));return n.toString()}async handleEnsureIdentityVerified(e){const n=typeof e.purpose=="string"?e.purpose:"sensitive_action",r=e.target&&typeof e.target=="object"?e.target:void 0;return await wt.handle({success:!1,error_code:"identity_verification_required",message:"",verification:{policy_key:e.policy_key??"",purpose:n,provider_id:e.provider_id??null,render_hint:e.render_hint??null,return_request:null}},void 0,r)!==null}handleResolveIdentityChallenge(e){const n=typeof e.result=="string"?e.result:"cancelled";let r;switch(n){case"verified":{const i=typeof e.token=="string"?e.token:"";if(!i){z.warn("resolveIdentityChallenge: result=verified 인데 token 이 비어있습니다. failed 로 강등합니다."),r={status:"failed",failureCode:"MISSING_TOKEN"};break}r={status:"verified",token:i,providerData:e.providerData&&typeof e.providerData=="object"?e.providerData:void 0};break}case"pending":{const i=typeof e.pollUrl=="string"?e.pollUrl:"",l=typeof e.expiresAt=="string"?e.expiresAt:"";if(!i||!l){z.warn("resolveIdentityChallenge: result=pending 인데 pollUrl/expiresAt 누락 — failed 로 강등합니다."),r={status:"failed",failureCode:"MALFORMED_PENDING"};break}r={status:"pending",pollUrl:i,pollIntervalMs:typeof e.pollIntervalMs=="number"?e.pollIntervalMs:void 0,expiresAt:l};break}case"failed":r={status:"failed",failureCode:typeof e.failureCode=="string"?e.failureCode:"UNKNOWN",reason:typeof e.reason=="string"?e.reason:void 0};break;default:r={status:"cancelled"};break}return wt.resolveDeferred(r),!0}async handleApiCall(e,n,r,i="none",l){const{method:u="GET",body:d,headers:f,contentType:g}=n;u!=="GET"&&u!=="HEAD"&&await this.ensureCsrfToken();const m=this.getCsrfTokenFromCookie();let b={};if(i==="required"||i==="optional"){const X=Ia().getToken();X&&(b={Authorization:`Bearer ${X}`})}let v=e;const S=n.query||(u==="GET"?d:null);if(S&&typeof S=="object"){const P=this.buildQueryString(S);if(P){const X=e.includes("?")?"&":"?";v=`${e}${X}${P}`}}const _=window.G7Core?.state?.getGlobal?.()||r.state?._global||{},x=window.G7Core?.state?.getLocal?.()||r.state?._local||{},k={_global:_,_local:x},O=this.getMatchingGlobalHeaders(e,k),M={};if(typeof window<"u"){const P=localStorage.getItem("g7_locale");P&&(M["Accept-Language"]=P)}const T=g==="multipart/form-data",D={method:u,headers:{...T?{}:{"Content-Type":"application/json"},Accept:"application/json",...M,...m&&{"X-XSRF-TOKEN":m},...b,...O,...f},credentials:"include"};if(d&&u!=="GET")if(T){const P=new FormData;for(const[X,pe]of Object.entries(d))pe instanceof File||pe instanceof Blob?P.append(X,pe):pe!=null&&P.append(X,typeof pe=="object"?JSON.stringify(pe):String(pe));D.body=P}else D.body=JSON.stringify(d);const j=qn();let H=null;j?.isEnabled()&&(H=j.trackRequest(v,u));try{let P=await fetch(v,D),X;try{X=await P.json()}catch{X=null}if(wt.isIdentityRequired(P.status,X)){const pe=await wt.handle(X,{body:D.body,headers:D.headers,credentials:D.credentials},l);if(pe){P=pe;try{X=await P.json()}catch{X=null}}}if(H&&j?.isEnabled()&&(j.completeRequest(H,P.status,X),H=null),!P.ok){const pe=X||{},_e=new Error(pe.message||`API call failed: ${P.statusText}`);throw _e.response=pe,_e.status=P.status,_e.statusText=P.statusText,new Ut(pe.message||`API call failed: ${P.statusText}`,void 0,_e)}return X}catch(P){throw H&&j?.isEnabled()&&j.failRequest(H,P instanceof Error?P.message:String(P)),P}}async handleLogin(e,n,r){const{body:i}=n;if(!i||!i.email||!i.password)throw new Ut("Login requires email and password in body params");const l=e==="user"?"user":"admin",u=l==="admin"?"/api/auth/admin/login":"/api/auth/login",d=window.G7Core?.state?.getGlobal?.()||r.state?._global||{},f=window.G7Core?.state?.getLocal?.()||r.state?._local||{},g={_global:d,_local:f},m=this.getMatchingGlobalHeaders(u,g),b=ga.getInstance();try{const v=Object.keys(m).length>0?{headers:m}:void 0;return{user:await b.login(l,{email:i.email,password:i.password},v)}}catch(v){const S=v.message||"Login failed",_=new Error(S);throw _.response=v.response?.data||{},_.status=v.status||v.response?.status||500,new Ut(S,void 0,_)}}async handleLogout(e,n){await ga.getInstance().logout()}async handleSetState(e,n){z.log("[handleSetState] START, params:",e);const{target:r="component",scope:i,merge:l,__render:u,...d}=e,f=qn(),g=window.G7Core;if(typeof r=="string"&&(r.startsWith("$parent.")||r.startsWith("$root.")))return this.handleParentScopeSetState(r,d,l,n);const m=n.state&&Object.keys(n.state).length>0?n.state:g?.state?.get()||{},b=r==="global"?"_global":r.startsWith("_local.")?r:"_local",v=b==="_global"?m._global:b.startsWith("_local.")?this.getNestedProperty(m._local||{},b.slice(7)):m._local,S=f?.isEnabled()?f.startStateChange(b,v,d,{actionId:n.actionId,handlerType:"setState",source:`target=${r}`}):void 0,_=l==="replace"?"replace":l==="shallow"?"shallow":"deep",x=_!=="deep"?{...d,__mergeMode:_,...S?{__setStateId:S}:{}}:{...d,...S?{__setStateId:S}:{}};if(r==="global"){if(!this.globalStateUpdater){z.warn("Global state updater is not set"),S&&f&&f.completeStateChange(S);return}const k=g?.state?.get()||{},{__setStateId:O,__mergeMode:M,...T}=x,D=_==="deep"?this.deepMergeWithState(T,k):T,j={...D,...S?{__setStateId:S}:{},..._!=="deep"?{__mergeMode:_}:{}};return z.log("setState global:",j),this.globalStateUpdater(j,{render:u}),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"global",..._!=="deep"?{__mergeMode:_}:{},...D}}else if(r==="isolated"){const k=n.isolatedContext;if(k){const{__mergeMode:O,__setStateId:M,...T}=x;return k.mergeState(T,_),z.log("[handleSetState] isolated state updated:",T,"mergeMode:",_),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"isolated",...T}}else if(z.warn('[handleSetState] isolated target used but no IsolatedStateContext found. Make sure the component has "isolatedState" attribute. Falling back to local state.'),n.setState){const M=window.__g7PendingLocalState||n.state||{},T=_==="deep"?this.deepMergeWithState(x,M):x;return n.setState(T),S&&f&&setTimeout(()=>f.completeStateChange(S),0),T}else throw S&&f&&f.completeStateChange(S),new Ut("isolated target used but no IsolatedStateContext found and no setState function in context")}else if(r==="local"||r==="component"||r==="_local"){if(i==="parent"||i==="root"){const O=window.__g7LayoutContextStack||[];if(O.length>0){const M=i==="parent"?O[O.length-1]:O[0];if(M?.setState){const{__mergeMode:T,__setStateId:D,...j}=x,H=_==="deep"?this.deepMergeWithState(j,M.state||{}):j;return z.log(`[handleSetState] scope=${i}: 타겟 컨텍스트에 상태 업데이트`,H),M.setState(H),S&&f&&setTimeout(()=>f.completeStateChange(S),0),H}}z.warn(`[handleSetState] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const k=n.setState&&!n._isDispatchFallbackContext;if(z.log("[handleSetState] isRealComponentContext:",k,"context.setState:",!!n.setState,"_isDispatchFallbackContext:",n._isDispatchFallbackContext),k){z.log("[handleSetState] Using COMPONENT setState path"),z.log("[handleSetState] resolvedPayload:",x),z.log("[handleSetState] context.state:",n.state),z.log("[handleSetState] mergeMode:",_);const M=window.__g7PendingLocalState||n.state||{},T=_==="deep"?this.deepMergeWithState(x,{}):x;z.log("[handleSetState] convertedPayload (변경 필드만, dot notation 변환):",T),n.setState(T);const D=_==="deep"?this.deepMergeWithState(x,M):_==="shallow"?{...n.state||{},...x}:x,{__mergeMode:j,__setStateId:H,...P}=D;window.__g7PendingLocalState=P,z.log("[handleSetState] __g7PendingLocalState updated:",P),this.globalStateUpdater&&(this.globalStateUpdater({_local:P},{render:!1}),z.log("[handleSetState] _global._local synced (render:false):",P));const{__mergeMode:X,__setStateId:pe,..._e}=x;if(_==="replace")window.__g7ForcedLocalFields=_e;else{const we=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=this.deepMergeWithState(_e,we)}return z.log("[handleSetState] __g7ForcedLocalFields updated:",_e),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"local",...D}}else if(this.globalStateUpdater){z.log("[handleSetState] Using GLOBAL STATE UPDATER path for _local");const M=window.__g7PendingLocalState||m._local||{};z.log("[handleSetState] currentLocal (with pending):",M);const T=_==="deep"?this.deepMergeWithState(x,M):x;return z.log("[handleSetState] finalLocal (merged):",T),this.globalStateUpdater({_local:T},{render:u}),window.__g7PendingLocalState=T,z.log("[handleSetState] __g7PendingLocalState updated for globalStateUpdater path"),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"local",...T}}else throw S&&f&&f.completeStateChange(S),new Ut("setState function is not provided in context and globalStateUpdater is not set")}else if(r.startsWith("_local.")){const k=r.slice(7),{__mergeMode:O,__setStateId:M,...T}=x,D=T.value!==void 0?T.value:Object.values(T)[0];if(n.setState){const H=window.__g7PendingLocalState||n.state||{},P=this.createNestedUpdate(k,D,H),X={...H,...P};return n.setState(P),S&&f&&setTimeout(()=>f.completeStateChange(S),0),X}else throw S&&f&&f.completeStateChange(S),new Ut("setState function is not provided in context")}else{if(!n.setState)throw S&&f&&f.completeStateChange(S),new Ut("setState function is not provided in context");const O=window.__g7PendingLocalState||n.state||{},M=_==="deep"?this.deepMergeWithState(x,O):x;return n.setState(M),S&&f&&setTimeout(()=>f.completeStateChange(S),0),M}}async handleParentScopeSetState(e,n,r,i){const l=qn(),u=window.G7Core,d=r==="replace"?"replace":r==="shallow"?"shallow":"deep",f=e.startsWith("$parent."),g=f?"$parent.":"$root.",m=e.slice(g.length);let b,v;if(m==="_local"||m==="_global")b=m,v=void 0;else if(m.startsWith("_local."))b="_local",v=m.slice(7);else if(m.startsWith("_global."))b="_global",v=m.slice(8);else{z.warn(`[handleParentScopeSetState] 지원하지 않는 타겟 형식: ${e}. _local 또는 _global으로 시작해야 합니다.`);return}const S=window.__g7LayoutContextStack||[];if(S.length===0){z.warn(`[handleParentScopeSetState] 레이아웃 컨텍스트 스택이 비어있습니다. target=${e}`);return}const _=f?S[S.length-1]:S[0];if(!_){z.warn(`[handleParentScopeSetState] 타겟 컨텍스트를 찾을 수 없습니다. target=${e}`);return}const x=b==="_global"?_.state?._global:_.state?._local,k=l?.isEnabled()?l.startStateChange(e,x,n,{actionId:i.actionId,handlerType:"setState",source:`target=${e}`}):void 0,{__mergeMode:O,__setStateId:M,...T}=n;if(b==="_global"){if(!this.globalStateUpdater){z.warn("[handleParentScopeSetState] globalStateUpdater가 설정되지 않았습니다."),k&&l&&l.completeStateChange(k);return}const D=u?.state?.get()||{};if(v){const j=T.value!==void 0?T.value:Object.values(T)[0],H=this.createNestedUpdate(v,j,D),P={...D,...H};z.log(`[handleParentScopeSetState] ${e}: 전역 상태 중첩 경로 업데이트`,P),this.globalStateUpdater(P)}else{const j=d==="deep"?this.deepMergeWithState(T,D):T;z.log(`[handleParentScopeSetState] ${e}: 전역 상태 업데이트 (mergeMode=${d})`,j),this.globalStateUpdater(d!=="deep"?{...j,__mergeMode:d}:j)}return k&&l&&setTimeout(()=>l.completeStateChange(k),0),{__target:e,...T}}else{if(!_.setState){z.warn(`[handleParentScopeSetState] 타겟 컨텍스트에 setState가 없습니다. target=${e}`),k&&l&&l.completeStateChange(k);return}const D=_.state||{};if(v){const j=T.value!==void 0?T.value:Object.values(T)[0],H=this.createNestedUpdate(v,j,D),P={...D,...H};return z.log(`[handleParentScopeSetState] ${e}: 로컬 상태 중첩 경로 업데이트`,H),_.setState(H),_.state=P,_.dataContext&&(_.dataContext._local=P),$d(),k&&l&&setTimeout(()=>l.completeStateChange(k),0),P}else{const j=d!=="deep"?{...T,__mergeMode:d}:T,H=d==="replace"?T:d==="shallow"?{...D,...T}:this.deepMergeWithState(T,D);return z.log(`[handleParentScopeSetState] ${e}: 로컬 상태 업데이트 (mergeMode=${d})`,H),_.setState(j),_.state=H,_.dataContext&&(_.dataContext._local=H),$d(),k&&l&&setTimeout(()=>l.completeStateChange(k),0),H}}}deepMergeWithState(e,n){const r=["errors"];let i=e;if("..."in e){const u=e["..."];if(u&&typeof u=="object"&&!Array.isArray(u)){const{"...":d,...f}=e;i={...u,...f}}else{const{"...":d,...f}=e;i=f}}const l={...n};for(const[u,d]of Object.entries(i)){if(r.includes(u)){l[u]=d;continue}if(u.includes(".")&&u!=="..."){const f=this.createNestedUpdate(u,d,l);this.deepMergeInto(l,f,l);continue}if(d!==null&&typeof d=="object"&&!Array.isArray(d)&&Object.getPrototypeOf(d)!==Object.prototype&&Object.getPrototypeOf(d)!==null){l[u]=d;continue}d!==null&&typeof d=="object"&&!Array.isArray(d)&&n[u]!==null&&typeof n[u]=="object"&&!Array.isArray(n[u])?l[u]=this.deepMergeWithState(d,n[u]):l[u]=d}return l}deepMergeInto(e,n,r){for(const[i,l]of Object.entries(n)){if(l===null){e[i]=l;continue}if(e[i]===null){e[i]=l;continue}typeof l=="object"&&!Array.isArray(l)&&e[i]!==void 0&&e[i]!==null&&typeof e[i]=="object"&&!Array.isArray(e[i])?(e[i]===r[i]&&(e[i]={...e[i]}),this.deepMergeInto(e[i],l,r[i]||{})):typeof l=="object"&&!Array.isArray(l)&&e[i]===void 0&&r[i]!==null&&typeof r[i]=="object"&&!Array.isArray(r[i])?e[i]={...r[i],...l}:e[i]=l}}createNestedUpdate(e,n,r){const i=e.split(".");if(i.length===1)return{[i[0]]:n};const l={};let u=l,d=r;for(let f=0;f{this.globalStateUpdater({_local:S})};m.push({state:b,setState:v,dataContext:{_local:b||n.data?._local,_global:l?n.data?._global:n.data?._global||i?.state?.get?.(),_computed:n.data?._computed}}),window.__g7LayoutContextStack=m,z.log(`[handleOpenModal] 레이아웃 컨텍스트 스택에 push, 스택 크기: ${m.length}, state:`,b,l?"(direct)":"(fallback via G7Core)")}else z.warn(`[handleOpenModal] 컨텍스트 스택에 push 실패 - setState: ${!!n.setState}, state: ${n.state!==void 0}, G7Core: ${!!i?.state?.getLocal}`);const f=[...r,e];this.globalStateUpdater({modalStack:f,activeModal:e});const g=qn();if(g?.isEnabled()){const m=r.length>0?r[r.length-1]:void 0;g.trackModalOpen?.({modalId:e,modalName:e,scopeType:"isolated",parentModalId:m,initialState:n.state?{...n.state}:{}})}}async handleCloseModal(e){if(!this.globalStateUpdater){z.warn("Global state updater is not set for closeModal");return}const n=e?.data?._global?.modalStack||[],r=n.length>0?n[n.length-1]:null,i=n.slice(0,-1),l=i.length>0?i[i.length-1]:null,u=window.__g7LayoutContextStack||[];if(u.length>0){const d=u[u.length-1];d&&(d.dataContext=void 0),u.pop(),window.__g7LayoutContextStack=u,z.log(`[handleCloseModal] 레이아웃 컨텍스트 스택에서 pop, 스택 크기: ${u.length}`)}if(r){const d=qn();d?.isEnabled()&&d.trackModalClose?.(r,e?.state)}this.globalStateUpdater({modalStack:i,activeModal:l})}async handleShowAlert(e,n){let r=e;this.translationEngine&&this.translationContext&&e.startsWith("$t:")&&(r=this.translationEngine.resolveTranslations(e,this.translationContext,n.data)),alert(r)}async handleToast(e,n){const{type:r="info",message:i,icon:l,duration:u}=e;if(r==="error"){const f=n.data?.error;if((f?.error_code==="identity_verification_required"||f?.data?.error_code==="identity_verification_required")&&wt.consumeDomainNoticeShown()){z.log("[Toast] IDV 도메인 안내 표출됨 — 중복 가드 토스트 1건 skip");return}}let d=i;if(this.translationEngine&&this.translationContext&&i?.startsWith("$t:")&&(d=this.translationEngine.resolveTranslations(i,this.translationContext,n.data)),this.globalStateUpdater){const g={id:`toast_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,type:r,message:d,...l&&{icon:l},...u&&{duration:u}},S=[...window.G7Core?.state?.get()?.toasts||[],g];this.globalStateUpdater({toasts:S})}else z.log(`[Toast ${r}] ${d}`)}async handleSwitch(e,n){const{cases:r}=e;if(!r){z.warn("switch handler requires cases property");return}const i=this.resolveParams(e.params,n.data);let l;if(i?.value!==void 0?(l=i.value,z.log("switch handler: using params.value as case key:",l)):(l=n.data?.$args?.[0],z.log("switch handler: using $args[0] as case key:",l)),l==null){if(r.default)return z.log("switch handler: case key is undefined, using default case"),await this.executeAction(r.default,n);z.warn("switch handler: case key is not defined and no default case");return}const u=String(l);let d=r[u];if(!d&&r.default&&(z.log(`switch handler: no case found for key "${u}", using default case`),d=r.default),!d){z.warn(`switch handler: no case found for key "${u}" and no default case`);return}return await this.executeAction(d,n)}async handleConditions(e,n){const{conditions:r}=e;if(!r||!Array.isArray(r)){z.warn("conditions handler requires conditions array");return}const i=n.data||{},l=up(r,i,this.bindingEngine);if(!l.matched){z.log("conditions handler: no matching branch found");return}const u=r[l.branchIndex];if(z.log(`conditions handler: matched branch ${l.branchIndex}`),!u.then){z.warn('conditions handler: matched branch has no "then" action');return}return Array.isArray(u.then)?await this.handleSequence({...e,actions:u.then},n):await this.executeAction(u.then,n)}handleStartInterval(e,n){const r=typeof e.id=="string"?e.id:"",i=Number(e.intervalMs??1e3),l=Array.isArray(e.actions)?e.actions:[];if(r===""||!l.length||i<=0)return z.warn("startInterval requires non-empty id, positive intervalMs, and actions array"),{success:!1};const u=this.intervals.get(r);u&&(clearInterval(u),this.intervals.delete(r));const d=setInterval(()=>{for(const f of l)this.executeAction(f,n).catch(g=>{z.error(`[startInterval:${r}] tick action failed`,g)})},i);return this.intervals.set(r,d),{success:!0,id:r}}handleStopInterval(e){const n=typeof e.id=="string"?e.id:"";if(n==="")return z.warn("stopInterval requires id parameter"),{success:!1};const r=this.intervals.get(n);return r&&(clearInterval(r),this.intervals.delete(n)),{success:!0,id:n}}stopAllIntervals(){for(const e of this.intervals.values())clearInterval(e);this.intervals.clear()}async handleSequence(e,n){const r=e.actions||e.params?.actions;if(!r||!Array.isArray(r)||r.length===0)return z.warn("sequence handler requires non-empty actions array"),[];const i=qn(),l=i?.isEnabled()&&i.startSequenceExecution?.({eventType:e.type})||"";window.__g7SequenceLocalSync=void 0;const u=[];let d,f=n.state?{...n.state}:{},g=n.isolatedContext?.state?{...n.isolatedContext.state}:{},m=n.data?._computed||{};const b=window.G7Core,v=()=>({_global:b?.state?.get()||{},_local:{...f},_isolated:Object.keys(g).length>0?{...g}:void 0});for(let S=0;S0){const T={},D={...n.data,_local:f,_computed:T,$computed:T,_isolated:g};for(const[j,H]of Object.entries(M))if(typeof H=="string"){const P=H.trim();if(P.startsWith("{{")&&P.endsWith("}}"))try{const X=P.slice(2,-2).trim();T[j]=this.bindingEngine.evaluateExpression(X,D,{skipCache:!0})}catch{T[j]=m[j]}}m=T,z.log("[handleSequence] _computed recalculated after setState:",m)}}if(_.handler!=="setState"){const M=window.__g7SequenceLocalSync;if(M&&M!==f){f=M,window.__g7SequenceLocalSync=void 0,z.log("[handleSequence] currentState synchronized from __g7SequenceLocalSync after custom handler:",_.handler);const T=n.data?._computedDefinitions;if(T&&Object.keys(T).length>0){const D={},j={...n.data,_local:f,_computed:D,$computed:D,_isolated:g};for(const[H,P]of Object.entries(T))if(typeof P=="string"){const X=P.trim();if(X.startsWith("{{")&&X.endsWith("}}"))try{const pe=X.slice(2,-2).trim();D[H]=this.bindingEngine.evaluateExpression(pe,j,{skipCache:!0})}catch{D[H]=m[H]}}m=D,z.log("[handleSequence] _computed recalculated after custom handler:",m)}}}l&&i?.isEnabled()&&i.captureSequenceActionAfter?.(l,S,v(),Date.now()-x,O.data)}catch(O){throw l&&i?.isEnabled()&&(i.captureSequenceActionAfter?.(l,S,v(),Date.now()-x,void 0,O),i.endSequenceExecution?.(l,O)),z.error(`sequence handler: action[${S}] failed:`,O),O}}return window.__g7SequenceLocalSync=void 0,l&&i?.isEnabled()&&i.endSequenceExecution?.(l),u}async handleParallel(e,n){const r=e.actions||e.params?.actions;if(!r||!Array.isArray(r)||r.length===0)return z.warn("parallel handler requires non-empty actions array"),[];z.log(`parallel handler: executing ${r.length} actions in parallel`);const i=n.isolatedContext?{...n.isolatedContext,state:{...n.isolatedContext.state}}:null,l=r.map((f,g)=>{const m={...n,isolatedContext:i};return this.executeAction(f,m).catch(b=>{throw z.error(`parallel handler: action[${g}] failed:`,b),b})}),u=await Promise.allSettled(l),d=u.filter(f=>f.status==="rejected").length;return d>0&&z.warn(`parallel handler: ${d}/${r.length} actions failed`),u}async handleLoadScript(e,n,r){const{src:i,id:l,async:u=!0,defer:d=!1}=e;if(!i)throw new Ut('loadScript handler requires "src" parameter',n);const f=l||`script_${i.replace(/[^a-zA-Z0-9]/g,"_")}`;return gs.loadedScripts.has(f)?(z.log(`loadScript: script already loaded, skipping: ${f}`),n.onLoad&&await this.executeAction(n.onLoad,r),!0):document.getElementById(f)?(z.log(`loadScript: script element already exists: ${f}`),gs.loadedScripts.add(f),n.onLoad&&await this.executeAction(n.onLoad,r),!0):(z.log(`loadScript: loading script: ${i}`),new Promise((m,b)=>{const v=document.createElement("script");v.id=f,v.src=i,v.async=u,v.defer=d,v.onload=async()=>{if(z.log(`loadScript: script loaded successfully: ${f}`),gs.loadedScripts.add(f),n.onLoad)try{await this.executeAction(n.onLoad,r)}catch(S){z.error("loadScript: onLoad action failed:",S)}m(!0)},v.onerror=S=>{z.error(`loadScript: failed to load script: ${i}`,S),b(new Ut(`Failed to load script: ${i}`,n))},document.head.appendChild(v)}))}async handleCallExternal(e,n,r){const i=e.constructor,l=e.args||{},u=e.method,d=e.methodArgs||[],f=e.callbackEvent,g=e.embedTarget,m=e.callbackSetState,b=e.callbackAction;if(!i)throw new Ut('callExternal handler requires "constructor" parameter',n);const v=this.getNestedProperty(window,i);if(!v||typeof v!="function")throw new Ut(`Constructor not found or not a function: ${i}. Make sure the script is loaded first using loadScript handler.`,n);z.log(`callExternal: calling constructor ${i}`);const S={...l};for(const[x,k]of Object.entries(l))k===!0&&(S[x]=O=>{if(z.log(`callExternal: callback triggered for ${x}`,O),f&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(f,O),m&&r.setState){const M=j=>{const H={};for(const[P,X]of Object.entries(j))typeof X=="string"?H[P]=this.getNestedProperty(O,X):typeof X=="object"&&X!==null&&(H[P]=M(X));return H},T=M(m);z.log("callExternal: callbackSetState mapping result",T);const D=this.deepMergeWithState(T,r.state||{});z.log("callExternal: merged with existing state",D),r.setState(D)}else f&&r.setState&&r.setState({[`${f.replace(/:/g,"_")}_result`]:O});if(b){const M={...r,data:{...r.data,$event:O}},T=Array.isArray(b)?b:[b];for(const D of T)try{this.executeAction(D,M)}catch(j){z.error("callExternal: callbackAction failed:",j)}}});const _=new v(S);if(u&&typeof _[u]=="function"){if(z.log(`callExternal: calling method ${u}`),u==="embed"&&g){const x=document.querySelector(g);if(x)return _[u](x,...d);throw new Ut(`Embed target element not found: ${g}`,n)}return _[u](...d)}return _}async handleCallExternalEmbed(e,n,r){const i=e.constructor,l=e.args||{},u=e.callbackSetState,d=e.callbackEvent,f=e.layerClassName,g=e.callbackAction;if(!i)throw new Ut('callExternalEmbed handler requires "constructor" parameter',n);const m=this.getNestedProperty(window,i);if(!m||typeof m!="function")throw new Ut(`Constructor not found or not a function: ${i}. Make sure the script is loaded first.`,n);z.log(`callExternalEmbed: creating layer for ${i}`);const{layer:b,closeLayer:v}=this.createEmbedLayer(f),S={...l};for(const[x,k]of Object.entries(l))k===!0&&(S[x]=O=>{if(z.log(`callExternalEmbed: callback triggered for ${x}`,O),v(),d&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(d,O),u&&r.setState){const M=j=>{const H={};for(const[P,X]of Object.entries(j))typeof X=="string"?H[P]=this.getNestedProperty(O,X):typeof X=="object"&&X!==null&&(H[P]=M(X));return H},T=M(u);z.log("callExternalEmbed: callbackSetState mapping result",T);const D=this.deepMergeWithState(T,r.state||{});z.log("callExternalEmbed: merged with existing state",D),r.setState(D)}if(g){const M={...r,data:{...r.data,$event:O}},T=Array.isArray(g)?g:[g];for(const D of T)try{this.executeAction(D,M)}catch(j){z.error("callExternalEmbed: callbackAction failed:",j)}}});const _=new m(S);if(typeof _.embed=="function")_.embed(b),z.log("callExternalEmbed: embedded in layer");else throw v(),new Ut(`Constructor ${i} does not have embed method`,n);return _}isImeComposing(e){return e?.isComposing===!0||e?.keyCode===229}createEmbedLayer(e){const n=document.createElement("div");n.id="g7-embed-overlay",n.style.cssText=` +`+l)}}catch{}}throw r}}_request(e,a){typeof e=="string"?(a=a||{},a.url=e):a=e||{},a=fi(this.defaults,a);const{transitional:r,paramsSerializer:i,headers:l}=a;r!==void 0&&Gl.assertOptions(r,{silentJSONParsing:Jn.transitional(Jn.boolean),forcedJSONParsing:Jn.transitional(Jn.boolean),clarifyTimeoutError:Jn.transitional(Jn.boolean),legacyInterceptorReqResOrdering:Jn.transitional(Jn.boolean)},!1),i!=null&&(F.isFunction(i)?a.paramsSerializer={serialize:i}:Gl.assertOptions(i,{encode:Jn.function,serialize:Jn.function},!0)),a.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?a.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:a.allowAbsoluteUrls=!0),Gl.assertOptions(a,{baseUrl:Jn.spelling("baseURL"),withXsrfToken:Jn.spelling("withXSRFToken")},!0),a.method=(a.method||this.defaults.method||"get").toLowerCase();let u=l&&F.merge(l.common,l[a.method]);l&&F.forEach(["delete","get","head","post","put","patch","query","common"],E=>{delete l[E]}),a.headers=Cn.concat(u,l);const d=[];let f=!0;this.interceptors.request.forEach(function(A){if(typeof A.runWhen=="function"&&A.runWhen(a)===!1)return;f=f&&A.synchronous;const T=a.transitional||yd;T&&T.legacyInterceptorReqResOrdering?d.unshift(A.fulfilled,A.rejected):d.push(A.fulfilled,A.rejected)});const g=[];this.interceptors.response.forEach(function(A){g.push(A.fulfilled,A.rejected)});let m,b=0,v;if(!f){const E=[Vp.bind(this),void 0];for(E.unshift(...d),E.push(...g),v=E.length,m=Promise.resolve(a);b{if(!r._listeners)return;let l=r._listeners.length;for(;l-- >0;)r._listeners[l](i);r._listeners=null}),this.promise.then=i=>{let l;const u=new Promise(d=>{r.subscribe(d),l=d}).then(i);return u.cancel=function(){r.unsubscribe(l)},u},e(function(l,u,d){r.reason||(r.reason=new io(l,u,d),a(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this._listeners.push(e):this._listeners=[e]}unsubscribe(e){if(!this._listeners)return;const a=this._listeners.indexOf(e);a!==-1&&this._listeners.splice(a,1)}toAbortSignal(){const e=new AbortController,a=r=>{e.abort(r)};return this.subscribe(a),e.signal.unsubscribe=()=>this.unsubscribe(a),e.signal}static source(){let e;return{token:new DS(function(i){e=i}),cancel:e}}};function q0(o){return function(a){return o.apply(null,a)}}function G0(o){return F.isObject(o)&&o.isAxiosError===!0}const _d={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(_d).forEach(([o,e])=>{_d[e]=o});function Kp(o){const e=new hi(o),a=gp(hi.prototype.request,e);return F.extend(a,hi.prototype,e,{allOwnKeys:!0}),F.extend(a,e,null,{allOwnKeys:!0}),a.create=function(i){return Kp(fi(o,i))},a}const Pt=Kp(ro);Pt.Axios=hi,Pt.CanceledError=io,Pt.CancelToken=P0,Pt.isCancel=Lp,Pt.VERSION=wd,Pt.toFormData=Ul,Pt.AxiosError=Oe,Pt.Cancel=Pt.CanceledError,Pt.all=function(e){return Promise.all(e)},Pt.spread=q0,Pt.isAxiosError=G0,Pt.mergeConfig=fi,Pt.AxiosHeaders=Cn,Pt.formToJSON=o=>Op(F.isHTMLForm(o)?new FormData(o):o),Pt.getAdapter=Gp.getAdapter,Pt.HttpStatusCode=_d,Pt.default=Pt;const{Axios:oT,AxiosError:lT,CanceledError:cT,isCancel:uT,CancelToken:dT,VERSION:fT,all:hT,Cancel:gT,isAxiosError:pT,spread:mT,toFormData:yT,AxiosHeaders:bT,HttpStatusCode:vT,formToJSON:ST,getAdapter:wT,mergeConfig:CT,create:ET}=Pt,Ad="g7.identity.redirectStash",Vl=ct("IdentityGuardInterceptor");class St{static setLauncher(e){this.launcher=e}static hasLauncher(){return this.launcher!==null}static markDomainNoticeShown(){this.domainNoticeShown=!0}static consumeDomainNoticeShown(){const e=this.domainNoticeShown;return this.domainNoticeShown=!1,e}static resolveDeferred(e){const a=this.deferredResolver;if(!a){Vl.warn("IdentityGuardInterceptor.resolveDeferred 호출됐지만 대기 중인 launcher 가 없습니다. 모달이 launcher 외부에서 열렸는지 확인하세요.");return}this.deferredResolver=null,a(e)}static createDeferred(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null,e({status:"cancelled"})}return new Promise(e=>{this.deferredResolver=e})}static isIdentityRequired(e,a){return e===428&&typeof a=="object"&&a!==null&&a.error_code==="identity_verification_required"}static async handle(e,a,r){this.domainNoticeShown=!1;const i=this.launcher??F0,u=!!(r&&(r.email||r.phone))?{...e.verification,target:r}:e.verification,d=await i(u);if(d.status!=="verified")return null;const f=u.return_request;if(!f)return null;const g=V0(f.url,d.token);return fetch(g,{method:f.method,headers:a?.headers??{Accept:"application/json","Content-Type":"application/json"},body:a?.body,credentials:a?.credentials??"same-origin"})}static redirectExternally(e){const a=e.redirect_url;if(!a)return Vl.error("redirectExternally 호출 시 verification.redirect_url 이 없습니다. payload:",e),Promise.resolve({status:"failed",failureCode:"MISSING_REDIRECT_URL",reason:"verification.redirect_url is required for external_redirect flow"});if(typeof window<"u"){const r={return_url:window.location.href,payload:e,stashed_at:Date.now()};try{window.sessionStorage?.setItem(Ad,JSON.stringify(r))}catch(i){Vl.warn("sessionStorage 접근 실패 — stash 없이 redirect 합니다.",i)}window.location.href=a}return new Promise(()=>{})}static reset(){if(this.deferredResolver){const e=this.deferredResolver;this.deferredResolver=null;try{e({status:"cancelled"})}catch{}}this.launcher=null,this.domainNoticeShown=!1}}$(St,"launcher",null),$(St,"deferredResolver",null),$(St,"domainNoticeShown",!1);function V0(o,e){if(!e)return o;try{const a=typeof window<"u"?window.location.origin:"http://localhost",r=new URL(o,a);return r.searchParams.set("verification_token",e),/^https?:\/\//i.test(o)?r.toString():`${r.pathname}${r.search}${r.hash}`}catch{const a=o.includes("?")?"&":"?";return`${o}${a}verification_token=${encodeURIComponent(e)}`}}const F0=async o=>{if(typeof window>"u")return{status:"cancelled"};if(o.render_hint==="external_redirect"||o.redirect_url)return St.redirectExternally(o);const e=window.G7Core;if(!e?.dispatch)return console.error("[IdentityGuardInterceptor] defaultLauncher: G7Core 가 초기화되지 않아 본인인증 흐름을 시작할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};try{await e.dispatch({handler:"toast",params:{message:"본인 확인이 필요합니다.",variant:"warning"}})}catch{}const a=window.location.href,r={return_url:a,payload:o,stashed_at:Date.now()};try{window.sessionStorage?.setItem(Ad,JSON.stringify(r))}catch{}const i=`/identity/challenge?return=${encodeURIComponent(a)}`;try{await e.dispatch({handler:"navigate",params:{path:i}})}catch(l){Vl.warn("navigate 실패 — window.location 으로 폴백",l),window.location.href=i}return new Promise(()=>{})},K0=ct("ApiClient");function xd(){return typeof window<"u"&&window.__G7_DEVTOOLS__?window.__G7_DEVTOOLS__:null}class W0{constructor(e={}){$(this,"client");$(this,"config");$(this,"TOKEN_KEY","auth_token");this.config={baseURL:e.baseURL||"/api",timeout:e.timeout||3e4,onTokenExpired:e.onTokenExpired,onUnauthorized:e.onUnauthorized,onError:e.onError},this.client=Pt.create({baseURL:this.config.baseURL,timeout:this.config.timeout,headers:{"Content-Type":"application/json",Accept:"application/json"},paramsSerializer:{serialize:a=>{const r=[];for(const[i,l]of Object.entries(a))if(l!=null)if(Array.isArray(l)){const u=i.endsWith("[]")?i:`${i}[]`;for(const d of l)r.push(`${encodeURIComponent(u)}=${encodeURIComponent(String(d))}`)}else r.push(`${encodeURIComponent(i)}=${encodeURIComponent(String(l))}`);return r.join("&")}}}),this.setupInterceptors()}setToken(e){typeof window<"u"&&localStorage.setItem(this.TOKEN_KEY,e)}getToken(){return typeof window<"u"?localStorage.getItem(this.TOKEN_KEY):null}removeToken(){typeof window<"u"&&localStorage.removeItem(this.TOKEN_KEY)}setupInterceptors(){this.client.interceptors.request.use(e=>{e.url?.startsWith("/api")&&e.baseURL==="/api"&&(e.baseURL="");const a=this.getToken();if(a&&e.headers&&(e.headers.Authorization=`Bearer ${a}`),typeof window<"u"&&e.headers){const i=localStorage.getItem("g7_locale");i&&(e.headers["Accept-Language"]=i)}const r=xd();if(r?.isEnabled()){const i=this.buildFullUrl(e),l=(e.method||"GET").toUpperCase(),u=r.trackRequest(i,l,{requestBody:e.data});e._devToolsRequestId=u}return e},e=>Promise.reject(e)),this.client.interceptors.response.use(e=>{const a=e.config,r=a._devToolsRequestId;if(r){const i=xd();i?.isEnabled()&&i.completeRequest(r,e.status,e.data),a._devToolsRequestId=null}return e},async e=>{const a=e.config,r=a?.url||"",l=["/auth/","/layouts/"].some(u=>r.includes(u));if(e.response?.status===401&&!a?._retry&&!l){if(a._retry=!0,await ha.getInstance().refreshToken()){const f=this.getToken();return f&&(a.headers.Authorization=`Bearer ${f}`),a._devToolsRequestId=null,this.client(a)}return this.completeDevToolsRequest(a,e),this.removeToken(),this.config.onUnauthorized&&this.config.onUnauthorized(),Promise.reject(e)}if(e.response?.status===401&&l)return this.completeDevToolsRequest(a,e),Promise.reject(e);if(St.isIdentityRequired(e.response?.status,e.response?.data)){this.completeDevToolsRequest(a,e);const u={},d=a?.headers??{};for(const[v,S]of Object.entries(d))typeof S=="string"&&(u[v]=S);let f;a?.data!==void 0&&a?.data!==null&&(f=typeof a.data=="string"?a.data:JSON.stringify(a.data),u["Content-Type"]=u["Content-Type"]??"application/json");const g=a?.identity_target,m=await St.handle(e.response.data,{headers:u,body:f,credentials:"same-origin"},g);if(!m)return Promise.reject(e);let b=null;try{b=await m.json()}catch{b=null}return m.ok?{...e.response,status:m.status,data:b}:Promise.reject(Object.assign(new Error("replay failed"),{response:{status:m.status,data:b}}))}return e.response?.status===403?(this.completeDevToolsRequest(a,e),this.callOnError(e),Promise.reject(e)):(this.completeDevToolsRequest(a,e),e.response&&this.callOnError(e),Promise.reject(e))})}async get(e,a){return(await this.client.get(e,a)).data}async post(e,a,r){return(await this.client.post(e,a,r)).data}async put(e,a,r){return(await this.client.put(e,a,r)).data}async patch(e,a,r){return(await this.client.patch(e,a,r)).data}async delete(e,a){return(await this.client.delete(e,a)).data}getInstance(){return this.client}setOnUnauthorized(e){this.config.onUnauthorized=e}setOnError(e){this.config.onError=e}completeDevToolsRequest(e,a){if(!e?._devToolsRequestId)return;const r=xd();if(!r?.isEnabled())return;const i=e._devToolsRequestId;e._devToolsRequestId=null,a.response?r.completeRequest(i,a.response.status,a.response.data):a.request?r.failRequest(i,a.message||"Network error"):r.failRequest(i,a.message||"Request error")}buildFullUrl(e){let a=e.url||"";if(e.baseURL&&!a.startsWith("http")&&(a=`${e.baseURL}${a.startsWith("/")?"":"/"}${a}`),e.params&&typeof e.params=="object"){const r=e.params,i=[];for(const[l,u]of Object.entries(r))if(u!=null)if(Array.isArray(u)){const d=l.endsWith("[]")?l:`${l}[]`;for(const f of u)i.push(`${encodeURIComponent(d)}=${encodeURIComponent(String(f))}`)}else i.push(`${encodeURIComponent(l)}=${encodeURIComponent(String(u))}`);i.length>0&&(a+=(a.includes("?")?"&":"?")+i.join("&"))}return a}callOnError(e){if(!this.config.onError)return;const a={status:e.response?.status||0,message:e.response?.data?.message||e.message||"Unknown error",data:e.response?.data,statusText:e.response?.statusText};try{this.config.onError(a)}catch(r){K0.error("Error in onError handler:",r)}}}let Td=null;function Na(){return Td||(Td=new W0),Td}const so=ct("AuthManager");function ns(o,e,a,r){try{window.G7Core?.devTools?.trackAuthEvent?.(o,e,a,r)}catch{}}const Fl={admin:{type:"admin",loginPath:"/admin/login",defaultPath:"/admin",userEndpoint:"/admin/auth/user",loginEndpoint:"/auth/admin/login",logoutEndpoint:"/admin/auth/logout",refreshEndpoint:"/admin/auth/refresh"},user:{type:"user",loginPath:"/login",defaultPath:"/",userEndpoint:"/auth/user",loginEndpoint:"/auth/login",logoutEndpoint:"/auth/logout",refreshEndpoint:"/auth/refresh"}},wa=class wa{constructor(){$(this,"state");$(this,"config");$(this,"isRefreshing",!1);$(this,"refreshPromise",null);$(this,"eventHandlers",new Map);this.state={isAuthenticated:!1,user:null,type:null},this.config=new Map,this.config.set("admin",Fl.admin),this.config.set("user",Fl.user)}static getInstance(){return wa.instance||(wa.instance=new wa),wa.instance}on(e,a){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(a)}emit(e,...a){const r=this.eventHandlers.get(e);r&&r.forEach(i=>i(...a))}isAuthenticated(){return this.state.isAuthenticated}getUser(){return this.state.user}getAuthType(){return this.state.type}async checkAuth(e){const a=Na();if(!a.getToken())return this.clearState(),!1;const i=this.config.get(e);if(!i)return so.error(`Unknown auth type: ${e}`),!1;try{const l=await a.get(i.userEndpoint);return l.success&&l.data?(this.state={isAuthenticated:!0,user:l.data,type:e},this.emit("authStateChange",this.state),!0):(this.clearState(),!1)}catch(l){return l.response?.status===401&&await this.refreshToken()?this.checkAuth(e):(this.clearState(),!1)}}async preloadAuth(e){try{return await this.checkAuth(e)}catch(a){return so.warn(`Preload auth failed for type ${e}:`,a),!1}}async login(e,a,r){const i=Na();if(!this.config.get(e))throw new Error(`Unknown auth type: ${e}`);const u=e==="admin"?Fl.admin.loginEndpoint:Fl.user.loginEndpoint;try{const d=r?.headers?{headers:r.headers}:void 0,f=await i.post(u,a,d);if(f.success&&f.data){i.setToken(f.data.token);const g=f.data.user.language,m=localStorage.getItem(wa.LOCALE_STORAGE_KEY),b=g&&g!==m;if(g)try{localStorage.setItem(wa.LOCALE_STORAGE_KEY,g)}catch(v){so.warn("Failed to save user language to localStorage:",v)}return this.state={isAuthenticated:!0,user:f.data.user,type:e},this.emit("login",this.state),this.emit("authStateChange",this.state),ns("login",!0,void 0,{userId:f.data.user.uuid,email:f.data.user.email,type:e}),b&&window.__templateApp&&window.__templateApp.changeLocale(g),f.data.user}throw new Error("Login failed")}catch(d){this.clearState();const f=d.response?.data?.message,g=new Error(f||d.message||"Login failed");throw g.response=d.response,g.status=d.response?.status,ns("login",!1,g.message,{type:e,status:d.response?.status}),g}}async logout(){const e=Na(),a=this.state.type?this.config.get(this.state.type):null;try{a&&await e.post(a.logoutEndpoint)}catch(r){so.warn("Logout API call failed:",r)}finally{e.removeToken();const r={...this.state};if(this.clearState(),this.emit("logout",r),this.emit("authStateChange",this.state),ns("logout",!0,void 0,{previousType:r.type}),a&&r.type){const i=window.location.pathname+window.location.search;window.location.href=this.getLoginRedirectUrl(r.type,i)}}}async refreshToken(){if(this.isRefreshing&&this.refreshPromise)return this.refreshPromise;this.isRefreshing=!0,this.refreshPromise=this.doRefreshToken();try{return await this.refreshPromise}finally{this.isRefreshing=!1,this.refreshPromise=null}}async doRefreshToken(){const e=this.state.type;if(!e)return!1;const a=this.config.get(e);if(!a)return!1;const r=Na();try{const i=await r.post(a.refreshEndpoint);return i.success&&i.data?.token?(r.setToken(i.data.token),this.emit("tokenRefreshed"),ns("token-refresh",!0,void 0,{type:e}),!0):(ns("token-refresh",!1,"No token in response",{type:e}),!1)}catch(i){return so.error("Token refresh failed:",i),ns("token-refresh",!1,i instanceof Error?i.message:"Unknown error",{type:e}),!1}}getLoginRedirectUrl(e,a,r){const i=this.config.get(e);if(!i)return"/login";const l=encodeURIComponent(a);let u=`${i.loginPath}?redirect=${l}`;return r&&(u+=`&reason=${encodeURIComponent(r)}`),u}updateConfig(e,a){if(a.loginPath!==void 0){const i=a.loginPath;if(!i.startsWith("/")||i.startsWith("//"))throw new Error(`AuthManager.updateConfig: loginPath must be a same-origin path starting with '/' (got: ${i})`)}const r=this.config.get(e);r&&this.config.set(e,{...r,...a})}getRedirectUrl(e){const r=this.config.get(e)?.defaultPath||"/",l=new URLSearchParams(window.location.search).get("redirect");if(l)try{const u=decodeURIComponent(l);if(u.startsWith("/"))return u}catch{}return r}getConfig(e){return this.config.get(e)}clearState(){this.state={isAuthenticated:!1,user:null,type:null}}static resetInstance(){wa.instance=null}};$(wa,"instance"),$(wa,"LOCALE_STORAGE_KEY","g7_locale");let ha=wa;const Wp={401:{handler:"navigate",params:{path:"{{auth.loginPath}}"}},403:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},404:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},422:{handler:"toast",params:{type:"error",message:"{{error.message}}"}},default:{handler:"toast",params:{type:"error",message:"{{error.message}}"}}},Qa={findHandler(o,e){if(o){if(o[e])return o[e];if(o[String(e)])return o[String(e)];if(o.default)return o.default}},normalizeOnError(o){return o?Array.isArray(o)?o:[o]:[]},injectErrorCode(o,e,a){if(o.handler!=="showErrorPage"||o.params?.errorCode!==void 0)return o;const r=typeof e=="string"?parseInt(e,10):e,i=isNaN(r)?a:r;return i===void 0?o:{...o,params:{...o.params,errorCode:i}}}},ga=ct("ErrorHandlingResolver"),kr=class kr{constructor(){$(this,"templateErrorHandling",null);$(this,"layoutErrorHandling",null);$(this,"executeAction",null)}static getInstance(){return kr.instance||(kr.instance=new kr),kr.instance}static resetInstance(){kr.instance=null}setTemplateConfig(e){this.templateErrorHandling=e,ga.log("Template config set:",e)}setLayoutConfig(e){this.layoutErrorHandling=e,ga.log("Layout config set:",e)}setActionExecutor(e){this.executeAction=e}resolve(e,a={}){const{errorHandling:r,onError:i}=a;ga.log("Resolving error:",e,{hasActionErrorHandling:!!r,hasActionOnError:!!i,hasLayoutConfig:!!this.layoutErrorHandling,hasTemplateConfig:!!this.templateErrorHandling});let l=Qa.findHandler(r,e);if(l){const u=this.getMatchedKey(r,e);return{handler:Qa.injectErrorCode(l,u,e),level:"action",matchedKey:u}}if(i){const u=Qa.normalizeOnError(i);if(u.length>0)return{handler:u.length===1?u[0]:{handler:"sequence",actions:u},level:"action",matchedKey:"onError"}}if(l=Qa.findHandler(this.layoutErrorHandling||void 0,e),l){const u=this.getMatchedKey(this.layoutErrorHandling,e);return{handler:Qa.injectErrorCode(l,u,e),level:"layout",matchedKey:u}}if(l=Qa.findHandler(this.templateErrorHandling||void 0,e),l){const u=this.getMatchedKey(this.templateErrorHandling,e);return{handler:Qa.injectErrorCode(l,u,e),level:"template",matchedKey:u}}if(l=Qa.findHandler(Wp,e),l){const u=this.getMatchedKey(Wp,e);return{handler:Qa.injectErrorCode(l,u,e),level:"system",matchedKey:u}}return{handler:null,level:null,matchedKey:null}}async execute(e,a){if(!this.executeAction){ga.error("Action executor is not set");return}ga.log("Executing handler:",e.handler,{errorContext:a});try{return await this.executeAction(e,{error:a})}catch(r){throw ga.error("Failed to execute handler:",r),r}}async resolveAndExecute(e,a,r={}){const i=this.resolve(e,r);if(ga.log("Resolve result:",{errorCode:e,handler:i.handler?.handler,level:i.level,matchedKey:i.matchedKey}),!i.handler)return ga.warn("No handler found for error:",e),{handled:!1};try{return{handled:!0,result:await this.execute(i.handler,a)}}catch(l){return ga.error("Handler execution failed:",l),{handled:!1}}}getMatchedKey(e,a){return e[a]?a:e[String(a)]?String(a):"default"}clearLayoutConfig(){this.layoutErrorHandling=null,ga.log("Layout config cleared")}clearAllConfig(){this.templateErrorHandling=null,this.layoutErrorHandling=null,ga.log("All config cleared")}getConfigStatus(){return{hasTemplate:this.templateErrorHandling!==null,hasLayout:this.layoutErrorHandling!==null,hasExecutor:this.executeAction!==null}}};$(kr,"instance",null);let Rd=kr;function pa(){return Rd.getInstance()}var kd={exports:{}},oo={};var Yp;function Y0(){if(Yp)return oo;Yp=1;var o=Symbol.for("react.transitional.element"),e=Symbol.for("react.fragment");function a(r,i,l){var u=null;if(l!==void 0&&(u=""+l),i.key!==void 0&&(u=""+i.key),"key"in i){l={};for(var d in i)d!=="key"&&(l[d]=i[d])}else l=i;return i=l.ref,{$$typeof:o,type:r,key:u,ref:i!==void 0?i:null,props:l}}return oo.Fragment=e,oo.jsx=a,oo.jsxs=a,oo}var Xp;function X0(){return Xp||(Xp=1,kd.exports=Y0()),kd.exports}var ut=X0();const J0=Ya({__proto__:null,default:Xa(ut)},[ut]),Dd=ct("ParentContextProvider"),Jp=N.createContext(null);let Kl=null;function Od(){Kl?(Dd.log("[triggerModalParentUpdate] 모달 부모 컨텍스트 업데이트 트리거"),Kl()):Dd.warn("[triggerModalParentUpdate] Provider가 아직 마운트되지 않음")}const Qp=({children:o})=>{const[e,a]=N.useState(0),r=N.useRef(!0),i=N.useCallback(()=>{const u=window.__g7LayoutContextStack||[];return u[u.length-1]?.dataContext},[]);N.useEffect(()=>(r.current=!0,Kl=()=>{r.current&&(a(u=>u+1),Dd.log("[ParentContextProvider] 버전 업데이트됨"))},()=>{r.current=!1,Kl=null}),[]);const l={version:e,getParentDataContext:i};return ut.jsx(Jp.Provider,{value:l,children:o})};function Q0(){return N.useContext(Jp)}const H=ct("ActionDispatcher");function Z0(o,e,a,r){return a||(Kg(o)||td(o)?"$t:core.errors.network_request_failed":r||`Failed to execute action: ${e}`)}const Zp=new Set(["navigate","navigateBack","navigateForward","replaceUrl","refresh","logout"]),eC=new Set(["redirect"]);function Un(){try{return window.G7Core?.devTools}catch{return}}class It extends Error{constructor(a,r,i){super(a);$(this,"action");$(this,"originalError");$(this,"unknownHandler",!1);this.action=r,this.originalError=i,this.name="ActionError"}}const ds=class ds{constructor(e={},a,r){$(this,"bindingEngine");$(this,"translationEngine");$(this,"translationContext");$(this,"customHandlers",new Map);$(this,"defaultContext");$(this,"globalStateUpdater");$(this,"errorHandlingSetup",!1);$(this,"debounceTimers",new Map);$(this,"pendingDebounceFlushers",new Map);$(this,"debounceAccumulatedValues",new Map);$(this,"globalHeaders",[]);$(this,"namedActions",{});$(this,"previewMode",!1);$(this,"intervals",new Map);this.bindingEngine=new wn,this.translationEngine=a,this.translationContext=r,this.defaultContext=e,this.registerDefaultHandlers(),this.setupErrorHandling()}setGlobalHeaders(e){this.globalHeaders=e||[]}setNamedActions(e){this.namedActions=e||{};const a=Un();a?.isEnabled?.()&&a.setNamedActionDefinitions?.(this.namedActions),H.log("[setNamedActions] registered:",Object.keys(this.namedActions))}getNamedActions(){return this.namedActions}setPreviewMode(e){this.previewMode=e,H.log("Preview mode:",e?"enabled":"disabled")}isPreviewMode(){return this.previewMode}static getPreviewSuppressedHandlers(){return Zp}static getPreviewSuppressedLayoutFeatures(){return eC}resolveActionRef(e){if(!e.actionRef)return e;const a=this.namedActions[e.actionRef];if(!a)return H.warn(`[resolveActionRef] named action not found: "${e.actionRef}"`),e;const r=Un();r?.isEnabled?.()&&r.trackNamedActionRef?.({actionRefName:e.actionRef,resolvedHandler:a.handler,timestamp:Date.now()});const{actionRef:i,type:l,key:u,event:d,...f}=e,g={...a,...Object.fromEntries(Object.entries(f).filter(([,m])=>m!==void 0)),type:l??a.type};return u!==void 0&&(g.key=u),d!==void 0&&(g.event=d),g}matchesPattern(e,a){if(a==="*")return!0;const r=a.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}getMatchingGlobalHeaders(e,a){const r={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,u])=>{const d=Qi(u,a,{skipCache:!0});d!=null&&d!==""&&(r[l]=String(d))});return r}setupErrorHandling(){if(this.errorHandlingSetup)return;pa().setActionExecutor(async(a,r)=>{const i={type:"click",handler:a.handler,target:a.target,params:a.params,actions:a.actions},l={data:{...this.defaultContext.data,error:r.error}};return await this.dispatchAction(i,l)}),this.errorHandlingSetup=!0}registerDefaultHandlers(){this.registerHandler("refetchDataSource",async(a,r)=>{const i=a.params?.dataSourceId,l=a.params?.sync;if(!i){H.warn("refetchDataSource: dataSourceId is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.refetch){const u=window.G7Core?.state?.get?.()||{},d={...r.state?._global,...u},f=window.G7Core?.state?.getLocal?.()||{},g=r.data?._local||r.state||{},m={...f,...g},b=r.isolatedContext?.state;H.log("[refetchDataSource] localStateOverride:",m),await window.G7Core.dataSource.refetch(i,{...l?{sync:!0}:{},...d?{globalStateOverride:d}:{},...m?{localStateOverride:m}:{},...b?{isolatedStateOverride:b}:{}})}else H.warn("refetchDataSource: G7Core.dataSource.refetch is not available")}),this.registerHandler("appendDataSource",async(a,r)=>{const{dataSourceId:i,dataPath:l,newData:u}=a.params||{};if(!i){H.warn("appendDataSource: dataSourceId is required");return}if(u===void 0){H.warn("appendDataSource: newData is required");return}if(typeof window<"u"&&window.G7Core?.dataSource?.updateData){if(!Array.isArray(u)){H.warn("appendDataSource: newData must be an array, got:",typeof u,u);return}await window.G7Core.dataSource.updateData(i,l||null,u,"append")}else H.warn("appendDataSource: G7Core.dataSource.updateData is not available")}),this.registerHandler("updateDataSource",async(a,r)=>{const{dataSourceId:i,data:l,merge:u=!1}=a.params||{};if(!i){H.warn("updateDataSource: dataSourceId is required");return}if(l===void 0){H.warn("updateDataSource: data is required");return}typeof window<"u"&&window.G7Core?.dataSource?.set?(H.log(`[updateDataSource] Updating dataSource '${i}' with:`,l),window.G7Core.dataSource.set(i,l,{merge:u})):H.warn("updateDataSource: G7Core.dataSource.set is not available")}),this.registerHandler("scrollIntoView",async(a,r)=>{const{selector:i,behavior:l="smooth",block:u="nearest",inline:d="nearest",waitForElement:f=!1,timeout:g=2e3,delay:m=0,retryCount:b=0,retryInterval:v=50,scrollContainer:S}=a.params||{};if(!i){H.warn("scrollIntoView: selector is required");return}if(typeof window>"u"||typeof document>"u"){H.warn("scrollIntoView: window/document is not available");return}let E=null;if(f){if(E=await new Promise(A=>{const T=document.querySelector(i);if(T){A(T);return}const L=setTimeout(()=>{M.disconnect(),A(null)},g),M=new MutationObserver(()=>{const R=document.querySelector(i);R&&(clearTimeout(L),M.disconnect(),A(R))});M.observe(document.body,{childList:!0,subtree:!0})}),!E){H.warn(`scrollIntoView: element not found for selector "${i}" after ${g}ms timeout`);return}}else{m>0&&await new Promise(T=>setTimeout(T,m)),E=document.querySelector(i);let A=0;for(;!E&&AsetTimeout(T,v)),E=document.querySelector(i),A++;if(!E){H.warn(`scrollIntoView: element not found for selector "${i}" after ${A+1} attempts`);return}}if(S){const A=document.querySelector(S);if(!A){H.warn(`scrollIntoView: container not found for selector "${S}"`);return}const T=E.getBoundingClientRect(),L=A.getBoundingClientRect(),M=T.top-L.top+A.scrollTop,R=M+T.height;let D;switch(u){case"start":D=M;break;case"center":D=M-A.clientHeight/2+T.height/2;break;case"end":D=R-A.clientHeight;break;default:const U=T.topL.bottom;if(U)D=M;else if(j)D=R-A.clientHeight;else return;break}A.scrollTo({top:Math.max(0,D),behavior:l});return}E.scrollIntoView({behavior:l,block:u,inline:d})}),this.registerHandler("reloadExtensions",async(a,r)=>{if(typeof window>"u"){H.warn("reloadExtensions: window is not available");return}const i=window.__templateApp;if(!i){H.warn("reloadExtensions: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState()}catch(d){throw H.error("reloadExtensions: reloadExtensionState failed",d),d}else H.warn("reloadExtensions: TemplateApp.reloadExtensionState unavailable");const{moduleInfo:l,pluginInfo:u}=a.params||{};if(l)try{await this.executeAction({handler:"reloadModuleHandlers",params:a.params},r)}catch(d){H.error("reloadExtensions: reloadModuleHandlers failed",d)}if(u)try{await this.executeAction({handler:"reloadPluginHandlers",params:a.params},r)}catch(d){H.error("reloadExtensions: reloadPluginHandlers failed",d)}H.log("reloadExtensions: done")}),this.registerHandler("reloadRoutes",async(a,r)=>{if(typeof window>"u"){H.warn("reloadRoutes: window is not available");return}const i=window.__templateApp;if(!i){H.warn("reloadRoutes: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),H.log("reloadRoutes: delegated to reloadExtensionState")}catch(l){throw H.error("reloadRoutes: Failed to reload routes",l),l}else{const l=i.getRouter?.();l&&(await l.loadRoutes(),H.log("reloadRoutes: Routes reloaded (legacy fallback)"))}}),this.registerHandler("refresh",async(a,r)=>{if(typeof window>"u"){H.warn("refresh: window is not available");return}const i=Number(a.params?.delayMs??0);i>0&&await new Promise(l=>setTimeout(l,i)),window.location.reload()}),this.registerHandler("remount",async(a,r)=>{const{componentId:i}=a.params||{};if(!i){H.warn("remount: componentId parameter is required");return}if(!this.globalStateUpdater){H.warn("remount: globalStateUpdater is not set");return}const d=(window.G7Core?.state?.get()||{})._global?._remountKeys||{},f=d[i]||0;this.globalStateUpdater({_remountKeys:{...d,[i]:f+1}}),H.log(`remount: ${i} key incremented to ${f+1}`)}),this.registerHandler("reloadTranslations",async(a,r)=>{if(typeof window>"u"){H.warn("reloadTranslations: window is not available");return}const i=window.__templateApp;if(!i){H.warn("reloadTranslations: TemplateApp not initialized");return}if(typeof i.reloadExtensionState=="function")try{await i.reloadExtensionState(),H.log("reloadTranslations: delegated to reloadExtensionState")}catch(l){throw H.error("reloadTranslations: Failed to reload translations",l),l}}),this.registerHandler("reloadModuleHandlers",async(a,r)=>{if(typeof window>"u"){H.warn("reloadModuleHandlers: window is not available");return}const{moduleInfo:i,action:l}=a.params||{};if(!i){H.warn("reloadModuleHandlers: moduleInfo is required");return}const u=i.data||i;if(!u.identifier){H.warn("reloadModuleHandlers: moduleInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){H.warn('reloadModuleHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){H.warn("reloadModuleHandlers: G7Config not available");return}const f=u.identifier;try{if(l==="add"){if(u.assets&&(d.moduleAssets=d.moduleAssets||{},d.moduleAssets[f]=u.assets,H.log(`reloadModuleHandlers: Added assets for ${f}`),u.assets.js)){const g=u.assets.js,m=`module-${f}`;if(document.getElementById(m)){H.warn(`reloadModuleHandlers: Script ${m} already loaded`);return}const b=document.createElement("script");if(b.id=m,b.src=g,b.async=!0,await new Promise((v,S)=>{b.onload=()=>{H.log(`reloadModuleHandlers: Script loaded successfully for ${f}`),v()},b.onerror=()=>{H.error(`reloadModuleHandlers: Failed to load script for ${f}`),S(new Error(`Failed to load module script: ${g}`))},document.head.appendChild(b)}),u.assets.css){const v=u.assets.css,S=`module-css-${f}`;if(!document.getElementById(S)){const E=document.createElement("link");E.id=S,E.rel="stylesheet",E.href=v,document.head.appendChild(E),H.log(`reloadModuleHandlers: CSS loaded for ${f}`)}}}}else if(l==="remove"){d.moduleAssets&&d.moduleAssets[f]&&(delete d.moduleAssets[f],H.log(`reloadModuleHandlers: Removed assets for ${f}`));const g=`module-${f}`,m=document.getElementById(g);m&&(m.remove(),H.log(`reloadModuleHandlers: Removed script for ${f}`));const b=`module-css-${f}`,v=document.getElementById(b);v&&(v.remove(),H.log(`reloadModuleHandlers: Removed CSS for ${f}`))}}catch(g){throw H.error(`reloadModuleHandlers: Failed to ${l} module assets`,g),g}}),this.registerHandler("reloadPluginHandlers",async(a,r)=>{if(typeof window>"u"){H.warn("reloadPluginHandlers: window is not available");return}const{pluginInfo:i,action:l}=a.params||{};if(!i){H.warn("reloadPluginHandlers: pluginInfo is required");return}const u=i.data||i;if(!u.identifier){H.warn("reloadPluginHandlers: pluginInfo with identifier is required");return}if(!l||l!=="add"&&l!=="remove"){H.warn('reloadPluginHandlers: action must be "add" or "remove"');return}const d=window.G7Config;if(!d){H.warn("reloadPluginHandlers: G7Config not available");return}const f=u.identifier;try{if(l==="add"){if(u.assets&&(d.pluginAssets=d.pluginAssets||{},d.pluginAssets[f]=u.assets,H.log(`reloadPluginHandlers: Added assets for ${f}`),u.assets.js)){const g=u.assets.js,m=`plugin-${f}`;if(document.getElementById(m)){H.warn(`reloadPluginHandlers: Script ${m} already loaded`);return}const b=document.createElement("script");if(b.id=m,b.src=g,b.async=!0,await new Promise((v,S)=>{b.onload=()=>{H.log(`reloadPluginHandlers: Script loaded successfully for ${f}`),v()},b.onerror=()=>{H.error(`reloadPluginHandlers: Failed to load script for ${f}`),S(new Error(`Failed to load plugin script: ${g}`))},document.head.appendChild(b)}),u.assets.css){const v=u.assets.css,S=`plugin-css-${f}`;if(!document.getElementById(S)){const E=document.createElement("link");E.id=S,E.rel="stylesheet",E.href=v,document.head.appendChild(E),H.log(`reloadPluginHandlers: CSS loaded for ${f}`)}}}}else if(l==="remove"){d.pluginAssets&&d.pluginAssets[f]&&(delete d.pluginAssets[f],H.log(`reloadPluginHandlers: Removed assets for ${f}`));const g=`plugin-${f}`,m=document.getElementById(g);m&&(m.remove(),H.log(`reloadPluginHandlers: Removed script for ${f}`));const b=`plugin-css-${f}`,v=document.getElementById(b);v&&(v.remove(),H.log(`reloadPluginHandlers: Removed CSS for ${f}`))}}catch(g){throw H.error(`reloadPluginHandlers: Failed to ${l} plugin assets`,g),g}});let e=!1;this.registerHandler("showErrorPage",async(a,r)=>{if(e){H.log("showErrorPage: Already active, skipping duplicate call");return}if(e=!0,typeof window>"u"){e=!1,H.warn("showErrorPage: window is not available");return}const i=window.__templateApp;if(!i){e=!1,H.warn("showErrorPage: TemplateApp not initialized");return}const l=i.getErrorPageHandler?.();if(!l){e=!1,H.warn("showErrorPage: ErrorPageHandler not available");return}const u=a.params?.errorCode||500,d=a.params?.target||"content";let f=a.params?.containerId;f||(f=d==="full"?"app":"main_content"),H.log("showErrorPage:",{errorCode:u,target:d,containerId:f});const g=(m,b=6e4)=>new Promise((v,S)=>{const E=document.getElementById(m);if(E){v(E);return}H.log(`showErrorPage: Waiting for container #${m}...`);let A;const T=new MutationObserver((L,M)=>{const R=document.getElementById(m);R&&(H.log(`showErrorPage: Container #${m} found`),M.disconnect(),clearTimeout(A),v(R))});T.observe(document.body,{childList:!0,subtree:!0}),A=setTimeout(()=>{T.disconnect(),S(new Error(`Container #${m} not found within ${b}ms`))},b)});try{await g(f);const m=pa(),b=m.layoutErrorHandling;m.clearLayoutConfig();try{await l.renderError(u,f)||H.warn(`showErrorPage: Failed to render error page for code ${u}`)}finally{m.setLayoutConfig(b)}}catch(m){throw H.error("showErrorPage: Error rendering error page:",m),m}finally{e=!1}}),this.registerHandler("emitEvent",async(a,r)=>{const i=a.params?.event,l=a.params?.data;if(!i){H.warn("emitEvent: event parameter is required");return}if(typeof window>"u"){H.warn("emitEvent: window is not available");return}const u=window.G7Core;if(!u?.componentEvent?.emit){H.warn("emitEvent: G7Core.componentEvent is not available");return}try{H.log(`emitEvent: Emitting "${i}"`,l);const d={...l,_context:{data:r.data,state:r.state}},f=await u.componentEvent.emit(i,d);if(!f||f.length===0?H.warn(`emitEvent: No listeners found for "${i}"`):H.log(`emitEvent: Event "${i}" completed with ${f.length} listener(s)`,f),this.globalStateUpdater){const g=(u?.state?.get()||{})._local||{},m={event:i,success:!0,data:f.length===1?f[0]:f,listeners:f.length};this.globalStateUpdater({_local:{...g,_eventResult:m}});const b=r.state&&typeof r.state=="object"&&!Array.isArray(r.state)?r.state:g;window.__g7SequenceLocalSync={...b,_eventResult:m}}}catch(d){if(H.error(`emitEvent: Event "${i}" failed`,d),this.globalStateUpdater){const f=u?.state?.get()||{};this.globalStateUpdater({_local:{...f._local,_eventResult:{event:i,success:!1,error:d instanceof Error?d.message:String(d)}}})}throw d}}),this.registerHandler("updateProductField",async(a,r)=>{const{productId:i,field:l,value:u}=a.params||{};if(!i||!l){H.warn("updateProductField: productId and field are required");return}if(typeof window>"u"){H.warn("updateProductField: window is not available");return}const d=window.G7Core;if(!d?.state?.get||!this.globalStateUpdater){H.warn("updateProductField: G7Core.state or globalStateUpdater is not available");return}try{const f=d.state.get()||{},m=(f.products?.data?.data||[]).map(v=>v.id===i?{...v,[l]:u,_modified:!0}:v),b=new Set(f._local?.modifiedProductIds||[]);b.add(i),this.globalStateUpdater({products:{...f.products,data:{...f.products?.data,data:m}},_local:{...f._local,modifiedProductIds:Array.from(b)}}),H.log(`updateProductField: Updated product ${i}, field: ${l}, value:`,u)}catch(f){throw H.error("updateProductField: Error updating product field",f),f}}),this.registerHandler("updateOptionField",async(a,r)=>{const{productId:i,optionId:l,field:u,value:d}=a.params||{};if(!i||!l||!u){H.warn("updateOptionField: productId, optionId and field are required");return}if(typeof window>"u"){H.warn("updateOptionField: window is not available");return}const f=window.G7Core;if(!f?.state?.get||!this.globalStateUpdater){H.warn("updateOptionField: G7Core.state or globalStateUpdater is not available");return}try{const g=f.state.get()||{},b=(g.products?.data?.data||[]).map(S=>{if(S.id===i&&S.options){const E=S.options.map(A=>A.id===l?{...A,[u]:d,_modified:!0}:A);return{...S,options:E,_modified:!0}}return S}),v=new Set(g._local?.modifiedProductIds||[]);v.add(i),this.globalStateUpdater({products:{...g.products,data:{...g.products?.data,data:b}},_local:{...g._local,modifiedProductIds:Array.from(v)}}),H.log(`updateOptionField: Updated product ${i} option ${l}, field: ${u}, value:`,d)}catch(g){throw H.error("updateOptionField: Error updating option field",g),g}}),this.registerHandler("setLocale",async(a,r)=>{const i=a.target;if(!i||typeof i!="string"){H.warn("setLocale: Invalid locale:",i);return}const l=window.__templateApp;if(l&&typeof l.changeLocale=="function")try{await l.changeLocale(i),H.log("setLocale: Locale changed to",i)}catch(u){H.error("setLocale: Failed to change locale:",u),window.location.reload()}else{H.warn("setLocale: TemplateApp not found, falling back to page reload");try{localStorage.setItem("g7_locale",i)}catch{}window.location.reload()}}),this.registerHandler("suppress",async()=>{H.log("suppress: Error intentionally suppressed")}),this.registerBuiltInHandlerMetadata()}registerBuiltInHandlerMetadata(){const e=Un();if(!e?.isEnabled())return;const a=[{name:"refetchDataSource",description:"데이터 소스를 다시 fetch합니다"},{name:"appendDataSource",description:"데이터 소스에 새 데이터를 병합합니다 (무한 스크롤용)"},{name:"updateDataSource",description:"API 응답으로 데이터 소스를 직접 업데이트합니다 (refetch 대체)"},{name:"reloadRoutes",description:"라우트를 다시 로드합니다"},{name:"refresh",description:"현재 페이지를 새로고침합니다"},{name:"remount",description:"컴포넌트를 리마운트합니다"},{name:"reloadTranslations",description:"다국어 파일을 다시 로드합니다"},{name:"showErrorPage",description:"에러 페이지를 표시합니다"},{name:"emitEvent",description:"이벤트를 발생시킵니다"},{name:"updateProductField",description:"상품 필드를 인라인 수정합니다"},{name:"updateOptionField",description:"상품 옵션 필드를 인라인 수정합니다"},{name:"setLocale",description:"언어를 변경합니다 (DB 저장 + UI 리렌더링)"}];for(const r of a)e.trackHandlerRegistration(r.name,"built-in",r.description)}createHandler(e,a,r){H.log("createHandler called for:",e.handler,e.type);const i=window.__g7DevTools,l=Date.now(),u=r?.state?{...r.state}:null,d=`handler_${e.handler}_${l}`;return async f=>{if(H.log("Handler invoked for:",e.handler,"event:",f.type),i?.isEnabled?.()&&u){const m=window.G7Core?.state?.getLocal?.()??r?.state??{},b=Date.now()-l;for(const v of Object.keys(u)){const S=u[v],E=m[v];S!==E&&b>100&&(i.trackStaleClosureWarning?.({type:"event-handler-stale",location:`createHandler(${e.handler})`,capturedPath:`_local.${v}`,capturedValue:S,capturedAt:l,currentValue:E,actionId:d,stackTrace:new Error().stack}),H.warn(`[Stale Closure] _local.${v} changed after handler creation:`,`captured="${S}" → current="${E}" (${b}ms ago)`))}}try{f.type!=="change"&&f.preventDefault();let g={};if(f.type==="submit"&&f.target instanceof HTMLFormElement){const v=f.target;new FormData(v).forEach((E,A)=>{g[A]=E})}const m={...this.defaultContext,data:{...a,form:g,_local:r?.state||{},$event:f},event:f,...r&&{state:r.state,setState:r.setState},isolatedContext:r?.isolatedContext};if(e.confirm){let v=this.resolveValue(e.confirm,m.data);if(this.translationEngine&&this.translationContext&&v.startsWith("$t:")&&(v=this.translationEngine.resolveTranslations(v,this.translationContext,m.data)),!confirm(v))return}const b=window.__g7ActionContext;window.__g7ActionContext={...r,data:m.data};try{await this.executeAction(e,m)}finally{window.__g7ActionContext=b}}catch(g){if(H.error("Action execution failed:",g),e.onError){const m=g instanceof It&&g.originalError?g.originalError:g,b={...this.defaultContext,data:{...a,error:m,_local:r?.state||{},$event:f},event:f,...r&&{state:r.state,setState:r.setState}},v=Array.isArray(e.onError)?e.onError:[e.onError];for(const S of v)await this.executeAction(S,b)}}}}generateActionId(e,a){const r=e.handler,i=a||"no-target",l=Date.now(),u=Math.random().toString(36).substring(2,9);return`${r}_${i.replace(/[^a-zA-Z0-9]/g,"_")}_${l}_${u}`}async executeAction(e,a){if(e=this.resolveActionRef(e),typeof e.handler=="string"&&e.handler.includes("{{")){const f=this.evaluateExpression(e.handler,a.data);e={...e,handler:f==null?"":String(f)}}const r=Un(),i=r?.isEnabled()?`action_${Date.now()}_${Math.random().toString(36).substring(2,11)}`:void 0,l=performance.now();let u;if(i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),context:this.sanitizeForDevTools({hasState:!!a.state,hasSetState:!!a.setState,dataKeys:a.data?Object.keys(a.data):[]}),startTime:l,status:"started"}),e.if!==void 0)try{const f=this.resolveValue(e.if,a.data),g=f===!0||f==="true"||f&&f!=="false"&&f!=="0"&&f!==!1;if(H.log("[executeAction] if condition result:",e.handler,g),!g)return i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:{skipped:!0,reason:"if condition false"}}),{success:!0,data:void 0}}catch(f){return H.error("[executeAction] if condition error:",e.handler,f),i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:"ConditionEvaluationError",message:f instanceof Error?f.message:String(f),stack:f instanceof Error?f.stack:void 0}}),{success:!0,data:void 0}}let d;try{const f=this.resolveParams(e.params,a.data);i&&(u=this.sanitizeForDevTools(f));const g=e.target?this.resolveValue(e.target,a.data):void 0;if(e.handler==="apiCall"&&a.setState){d=this.generateActionId(e,g);const b=a.state?.loadingActions||{};a.setState({loadingActions:{...b,[d]:!0}})}if(this.previewMode&&Zp.has(e.handler))return H.warn(`Preview mode: "${e.handler}" suppressed`,g||f),i&&r&&r.logAction({id:i,type:e.handler,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"skipped",metadata:{reason:"preview_mode_suppressed"}}),{success:!0,data:void 0};let m;switch(e.handler){case"ensureIdentityVerified":m=await this.handleEnsureIdentityVerified(f);break;case"resolveIdentityChallenge":m=this.handleResolveIdentityChallenge(f);break;case"navigate":const b=f.path||g;m=await this.handleNavigate(b,f,a);break;case"navigateBack":m=await this.handleNavigateBack();break;case"navigateForward":m=await this.handleNavigateForward();break;case"openWindow":{const E=f.path||g;m=await this.handleOpenWindow(E,f);break}case"replaceUrl":{const E=f.path||g||window.location.pathname;m=await this.handleReplaceUrl(E,f);break}case"apiCall":if(e.onSuccess&&r?.isEnabled()&&i){const E=window.G7Core,A={};if(E?.state?.get){const T=E.state.get();A._global=T}a.state&&(A._local=a.state),r.registerStateCaptureForHandler?.(i,["_global","_local"],A)}const v=e.auth_mode??(e.auth_required?"required":"none"),S=e.identity_target?this.resolveParams(e.identity_target,a.data):void 0;m=await this.handleApiCall(g,f,a,v,S);break;case"login":m=await this.handleLogin(g,f,a);break;case"logout":m=await this.handleLogout(g,a);break;case"setState":H.log("[executeAction] setState resolvedParams:",f),m=await this.handleSetState(e.render!==void 0?{...f,__render:e.render}:f,a);break;case"setError":m=await this.handleSetError(g,f,a);break;case"openModal":m=await this.handleOpenModal(g,a);break;case"closeModal":m=await this.handleCloseModal(a);break;case"showAlert":m=await this.handleShowAlert(g,a);break;case"toast":m=await this.handleToast(f,a);break;case"switch":m=await this.handleSwitch(e,a);break;case"conditions":m=await this.handleConditions(e,a);break;case"sequence":m=await this.handleSequence(e,a);break;case"parallel":m=await this.handleParallel(e,a);break;case"startInterval":m=this.handleStartInterval(f,a);break;case"stopInterval":m=this.handleStopInterval(f);break;case"loadScript":m=await this.handleLoadScript(f,e,a);break;case"callExternal":m=await this.handleCallExternal(f,e,a);break;case"callExternalEmbed":m=await this.handleCallExternalEmbed(f,e,a);break;case"saveToLocalStorage":m=await this.handleSaveToLocalStorage(f,a);break;case"loadFromLocalStorage":m=await this.handleLoadFromLocalStorage(f,a);break;default:m=await this.handleCustomAction({...e,target:g,params:f},a);break}if(e.resultTo&&m!==void 0){const{target:b,key:v,merge:S}=e.resultTo,E=this.resolveValue(v,a.data),A=S==="replace"?"replace":S==="shallow"?"shallow":"deep";if(b==="_local"&&a.setState){const T=this.buildNestedUpdate(E,m),L=A!=="deep"?{...T,__mergeMode:A}:T;a.setState(L),H.log(`[resultTo] Saved to _local.${E} (merge=${A}):`,m)}else if(b==="_local"&&this.globalStateUpdater){const M=(window.G7Core?.state?.get()||{})._local||{},R=this.buildNestedUpdate(E,m);let D;A==="replace"?D=R:A==="shallow"?D={...M,...R}:D=this.deepMergeWithState(R,M),this.globalStateUpdater({_local:D}),H.log(`[resultTo] Saved to _local.${E} via globalStateUpdater (merge=${A}):`,m)}else if(b==="_global"&&this.globalStateUpdater){const T=this.buildNestedUpdate(E,m);this.globalStateUpdater(T),H.log(`[resultTo] Saved to _global.${E}:`,m)}else if(b==="_isolated"&&a.isolatedContext){const T=this.buildNestedUpdate(E,m);a.isolatedContext.mergeState(T,A),H.log(`[resultTo] Saved to _isolated.${E} (merge=${A}):`,m)}else H.warn(`[resultTo] Cannot save result: target=${b}, setState=${!!a.setState}, globalStateUpdater=${!!this.globalStateUpdater}, isolatedContext=${!!a.isolatedContext}`)}if(e.onSuccess){if(r?.isEnabled()&&i&&e.handler==="apiCall"){const S=window.G7Core,E={};S?.state?.get&&(E._global=S.state.get()),a.state&&(E._local=a.state),r.detectStaleClosure?.(i,`${e.handler} → onSuccess`,E,"callback-state-capture",i)}const b={...a,data:{...a.data,result:m,response:m}},v=Array.isArray(e.onSuccess)?e.onSuccess:[e.onSuccess];v.length>1?await this.handleSequence({handler:"sequence",type:"click",actions:v},b):v.length===1&&await this.executeAction(v[0],b)}return i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:u,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"success",result:this.sanitizeForDevTools(m)}),{success:!0,data:m}}catch(f){const g=f instanceof It?f:new It(`Failed to execute action: ${e.handler}`,e,f instanceof Error?f:void 0),m=g.originalError?.response||{},b=m.data||{},v=g.originalError?.status||m.status||500;let S=Z0(g.originalError??f,e.handler,b.message,g.message);this.translationEngine&&this.translationContext&&S.startsWith("$t:")&&(S=this.translationEngine.resolveTranslations(S,this.translationContext));const E={status:v,message:S,errors:b.errors||m.errors,data:b,statusText:g.originalError?.statusText,error_code:b.error_code??m.error_code};if(g.unknownHandler)throw H.warn(`Unknown action handler "${e.handler}" — skipped (extension not loaded?). Not surfaced to the user.`),g;const T=pa().resolve(v,{errorHandling:e.errorHandling,onError:e.onError});if(T.handler){if(r?.isEnabled()&&i&&e.handler==="apiCall"){const M=window.G7Core,R={};M?.state?.get&&(R._global=M.state.get()),a.state&&(R._local=a.state),r.detectStaleClosure?.(i,`${e.handler} → onError`,R,"callback-state-capture",i)}const L={...a,data:{...a.data,error:E}};try{const M={type:"click",handler:T.handler.handler,target:T.handler.target,params:T.handler.params,actions:T.handler.actions};return await this.executeAction(M,L),{success:!1,error:g}}catch(M){return H.error("Error executing error handler:",M),{success:!1,error:g}}}throw i&&r&&r.logAction({id:i,type:e.handler,params:this.sanitizeForDevTools(e.params),resolvedParams:u,startTime:l,endTime:performance.now(),duration:performance.now()-l,status:"error",error:{name:g.name,message:g.message,stack:g.stack}}),g}finally{if(e.handler==="apiCall"&&a.setState&&d){const f=a.state?.loadingActions||{},{[d]:g,...m}=f;a.setState({loadingActions:m})}}}async handleNavigate(e,a,r){let i=e;if(a.query||a.mergeQuery===!0)if(a.mergeQuery===!0)i=this.buildMergedQueryPath(e,a.query??{});else{const u=new URLSearchParams;for(const[f,g]of Object.entries(a.query))if(g!=null&&g!=="")if(Array.isArray(g)){const m=f.endsWith("[]")?f:`${f}[]`;for(const b of g)b!=null&&b!==""&&u.append(m,String(b))}else u.set(f,String(g));const d=u.toString();d&&(i=`${e}?${d}`)}if(H.log("handleNavigate:",{target:e,params:a,finalPath:i,replace:a.replace,windowLocationSearch:window.location.search}),a.replace===!0){const u=window.G7Core;if(u?.updateQueryParams){const d=typeof a.transition_overlay_target=="string"?a.transition_overlay_target:void 0;await u.updateQueryParams(i,d?{transitionOverlayTarget:d}:void 0),H.log("handleNavigate: Used updateQueryParams for replace mode",d?{transitionOverlayTarget:d}:void 0),this.applyScrollOption(a.scroll,a.scrollBehavior,"top");return}H.warn("handleNavigate: G7Core.updateQueryParams not available, falling back to React Router")}const l=this.resolveNavigateFallbackAction(i,a);if(l){H.warn(`handleNavigate: No route matched, falling back to "${l.handler}"`),await this.dispatchAction({type:"click",handler:l.handler,params:l.params},r),this.applyScrollOption(a.scroll,a.scrollBehavior,"top");return}if(!r.navigate)throw new It("Navigate function is not provided in context");r.navigate(i,{replace:a.replace===!0}),this.applyScrollOption(a.scroll,a.scrollBehavior,"top")}resolveNavigateFallbackAction(e,a){const r=a.fallback;if(r===!1||a.replace===!0)return null;const l=window.__templateApp?.getRouter?.();if(!l||typeof l.match!="function"||typeof l.getRoutes=="function"&&l.getRoutes().length===0)return null;const u=e.split("?")[0];return l.match(u)?null:this.resolveNavigateFallback(r,e,a)}resolveNavigateFallback(e,a,r){return e==null?{handler:"openWindow",params:{path:a,target:"_self"}}:typeof e=="string"?{handler:e,params:{path:a}}:typeof e=="object"&&typeof e.handler=="string"?{handler:e.handler,params:{path:a,...e.params||{}}}:(H.warn("resolveNavigateFallback: unrecognized fallback option, using openWindow",e),{handler:"openWindow",params:{path:a,target:"_blank"}})}applyScrollOption(e,a,r){const i=e===void 0?r:e;if(i==="preserve")return;const l=a==="smooth"?"smooth":"instant",u=b=>{if(typeof b!="object"||b===null)return!1;const v=b;return"container"in v||"to"in v||"block"in v||"offset"in v},d=(b,v,S=0)=>{b===window?window.scrollTo({top:v,left:S,behavior:l}):b.scrollTo({top:v,left:S,behavior:l})},f=(b,v,S,E)=>{if(v===window){b.scrollIntoView({behavior:l,block:S}),E&&window.scrollBy({top:-E,left:0,behavior:l});return}const A=v,T=b.getBoundingClientRect(),L=A.getBoundingClientRect(),M=A.scrollTop+(T.top-L.top);let R;S==="center"?R=M-(A.clientHeight-b.clientHeight)/2:S==="end"?R=M-(A.clientHeight-b.clientHeight):R=M,R-=E,A.scrollTo({top:Math.max(0,R),left:0,behavior:l})},g=()=>{window.scrollTo({top:0,left:0,behavior:l});const b=document.getElementById("app")??document.body;if(!b)return;b.querySelectorAll("*").forEach(S=>{if(S.scrollTop===0&&S.scrollLeft===0)return;const E=window.getComputedStyle(S),A=E.overflowY,T=E.overflowX;(A==="auto"||A==="scroll"||T==="auto"||T==="scroll")&&S.scrollTo({top:0,left:0,behavior:l})})},m=()=>{try{if(u(i)){const b=i,v=b.block??"start",S=typeof b.offset=="number"?b.offset:0;let E=window;if(typeof b.container=="string"&&b.container.length>0){const T=document.querySelector(b.container);if(!T){H.warn(`applyScrollOption: container not found: ${b.container}`);return}E=T}const A=b.to??"top";if(A==="top"){E===window?g():d(E,0);return}if(typeof A=="number"){d(E,A-S);return}if(typeof A=="object"&&A!==null&&("x"in A||"y"in A)){const{x:T=0,y:L=0}=A;d(E,L-S,T);return}if(typeof A=="string"&&(A.startsWith("#")||A.startsWith("."))){const T=document.querySelector(A);T?f(T,E,v,S):H.warn(`applyScrollOption: target element not found: ${A}`);return}H.warn('applyScrollOption: invalid "to" value in extended form',A);return}if(i==="top"){g();return}if(typeof i=="number"){window.scrollTo({top:i,left:0,behavior:l});return}if(typeof i=="object"&&i!==null&&("x"in i||"y"in i)){const{x:b=0,y:v=0}=i;window.scrollTo({top:v,left:b,behavior:l});return}if(typeof i=="string"&&(i.startsWith("#")||i.startsWith("."))){const b=document.querySelector(i);b&&b.scrollIntoView({behavior:l,block:"start"});return}}catch(b){H.warn("applyScrollOption: failed to apply scroll",b)}};typeof window.requestAnimationFrame=="function"?window.requestAnimationFrame(m):m()}async handleOpenWindow(e,a){let r=e;if(a.query){const l=new URLSearchParams;for(const[d,f]of Object.entries(a.query))f!=null&&f!==""&&l.set(d,String(f));const u=l.toString();u&&(r=`${e}?${u}`)}const i=a.target==="_self"?"_self":"_blank";H.log("handleOpenWindow:",{target:e,params:a,finalPath:r,windowTarget:i}),i==="_self"?window.location.assign(r):window.open(r,"_blank")}async handleNavigateBack(){H.log("handleNavigateBack"),window.history.back()}async handleNavigateForward(){H.log("handleNavigateForward"),window.history.forward()}async handleReplaceUrl(e,a){let r=e;if(a.query||a.mergeQuery===!0)if(a.mergeQuery===!0)r=this.buildMergedQueryPath(e,a.query??{});else{const i=new URLSearchParams;for(const[u,d]of Object.entries(a.query))if(d!=null&&d!=="")if(Array.isArray(d)){const f=u.endsWith("[]")?u:`${u}[]`;for(const g of d)g!=null&&g!==""&&i.append(f,String(g))}else i.set(u,String(d));const l=i.toString();l&&(r=`${e}?${l}`)}H.log("handleReplaceUrl:",{target:e,params:a,finalPath:r}),window.history.replaceState(null,"",r),this.applyScrollOption(a.scroll,a.scrollBehavior,"preserve")}buildMergedQueryPath(e,a){const r=new URLSearchParams(window.location.search);for(const[u,d]of Object.entries(a))if(d==null||d==="")r.delete(u);else if(Array.isArray(d)){const f=u.endsWith("[]")?u:`${u}[]`;r.delete(u),r.delete(f);for(const g of d)g!=null&&g!==""&&r.append(f,String(g))}else r.set(u,String(d));const i=r.toString(),l=e.split("?")[0];return i?`${l}?${i}`:l}async ensureCsrfToken(){try{await fetch("/sanctum/csrf-cookie",{credentials:"include"})}catch(e){throw new It("Failed to fetch CSRF token",void 0,e instanceof Error?e:void 0)}}getCsrfTokenFromCookie(){const r=`; ${document.cookie}`.split("; XSRF-TOKEN=");if(r.length===2){const i=r.pop()?.split(";").shift();return i?decodeURIComponent(i):null}return null}buildQueryString(e){const a=new URLSearchParams;for(const[r,i]of Object.entries(e))if(!(i==null||i===""))if(Array.isArray(i))for(const l of i)l!=null&&l!==""&&a.append(`${r}[]`,String(l));else typeof i=="object"?a.append(r,JSON.stringify(i)):a.append(r,String(i));return a.toString()}async handleEnsureIdentityVerified(e){const a=typeof e.purpose=="string"?e.purpose:"sensitive_action",r=e.target&&typeof e.target=="object"?e.target:void 0;return await St.handle({success:!1,error_code:"identity_verification_required",message:"",verification:{policy_key:e.policy_key??"",purpose:a,provider_id:e.provider_id??null,render_hint:e.render_hint??null,return_request:null}},void 0,r)!==null}handleResolveIdentityChallenge(e){const a=typeof e.result=="string"?e.result:"cancelled";let r;switch(a){case"verified":{const i=typeof e.token=="string"?e.token:"";if(!i){H.warn("resolveIdentityChallenge: result=verified 인데 token 이 비어있습니다. failed 로 강등합니다."),r={status:"failed",failureCode:"MISSING_TOKEN"};break}r={status:"verified",token:i,providerData:e.providerData&&typeof e.providerData=="object"?e.providerData:void 0};break}case"pending":{const i=typeof e.pollUrl=="string"?e.pollUrl:"",l=typeof e.expiresAt=="string"?e.expiresAt:"";if(!i||!l){H.warn("resolveIdentityChallenge: result=pending 인데 pollUrl/expiresAt 누락 — failed 로 강등합니다."),r={status:"failed",failureCode:"MALFORMED_PENDING"};break}r={status:"pending",pollUrl:i,pollIntervalMs:typeof e.pollIntervalMs=="number"?e.pollIntervalMs:void 0,expiresAt:l};break}case"failed":r={status:"failed",failureCode:typeof e.failureCode=="string"?e.failureCode:"UNKNOWN",reason:typeof e.reason=="string"?e.reason:void 0};break;default:r={status:"cancelled"};break}return St.resolveDeferred(r),!0}async handleApiCall(e,a,r,i="none",l){const{method:u="GET",body:d,headers:f,contentType:g}=a;u!=="GET"&&u!=="HEAD"&&await this.ensureCsrfToken();const m=this.getCsrfTokenFromCookie();let b={};if(i==="required"||i==="optional"){const X=Na().getToken();X&&(b={Authorization:`Bearer ${X}`})}let v=e;const S=a.query||(u==="GET"?d:null);if(S&&typeof S=="object"){const P=this.buildQueryString(S);if(P){const X=e.includes("?")?"&":"?";v=`${e}${X}${P}`}}const E=window.G7Core?.state?.getGlobal?.()||r.state?._global||{},A=window.G7Core?.state?.getLocal?.()||r.state?._local||{},T={_global:E,_local:A},L=this.getMatchingGlobalHeaders(e,T),M={};if(typeof window<"u"){const P=localStorage.getItem("g7_locale");P&&(M["Accept-Language"]=P)}const R=g==="multipart/form-data",D={method:u,headers:{...R?{}:{"Content-Type":"application/json"},Accept:"application/json",...M,...m&&{"X-XSRF-TOKEN":m},...b,...L,...f},credentials:"include"};if(d&&u!=="GET")if(R){const P=new FormData;for(const[X,ge]of Object.entries(d))ge instanceof File||ge instanceof Blob?P.append(X,ge):ge!=null&&P.append(X,typeof ge=="object"?JSON.stringify(ge):String(ge));D.body=P}else D.body=JSON.stringify(d);const U=Un();let j=null;U?.isEnabled()&&(j=U.trackRequest(v,u));try{let P=await fetch(v,D),X;try{X=await P.json()}catch{X=null}if(St.isIdentityRequired(P.status,X)){const ge=await St.handle(X,{body:D.body,headers:D.headers,credentials:D.credentials},l);if(ge){P=ge;try{X=await P.json()}catch{X=null}}}if(j&&U?.isEnabled()&&(U.completeRequest(j,P.status,X),j=null),!P.ok){const ge=X||{},Ee=new Error(ge.message||`API call failed: ${P.statusText}`);throw Ee.response=ge,Ee.status=P.status,Ee.statusText=P.statusText,new It(ge.message||`API call failed: ${P.statusText}`,void 0,Ee)}return X}catch(P){throw j&&U?.isEnabled()&&U.failRequest(j,P instanceof Error?P.message:String(P)),P}}async handleLogin(e,a,r){const{body:i}=a;if(!i||!i.email||!i.password)throw new It("Login requires email and password in body params");const l=e==="user"?"user":"admin",u=l==="admin"?"/api/auth/admin/login":"/api/auth/login",d=window.G7Core?.state?.getGlobal?.()||r.state?._global||{},f=window.G7Core?.state?.getLocal?.()||r.state?._local||{},g={_global:d,_local:f},m=this.getMatchingGlobalHeaders(u,g),b=ha.getInstance();try{const v=Object.keys(m).length>0?{headers:m}:void 0;return{user:await b.login(l,{email:i.email,password:i.password},v)}}catch(v){const S=v.message||"Login failed",E=new Error(S);throw E.response=v.response?.data||{},E.status=v.status||v.response?.status||500,new It(S,void 0,E)}}async handleLogout(e,a){await ha.getInstance().logout()}async handleSetState(e,a){H.log("[handleSetState] START, params:",e);const{target:r="component",scope:i,merge:l,__render:u,...d}=e,f=Un(),g=window.G7Core;if(typeof r=="string"&&(r.startsWith("$parent.")||r.startsWith("$root.")))return this.handleParentScopeSetState(r,d,l,a);const m=a.state&&Object.keys(a.state).length>0?a.state:g?.state?.get()||{},b=r==="global"?"_global":r.startsWith("_local.")?r:"_local",v=b==="_global"?m._global:b.startsWith("_local.")?this.getNestedProperty(m._local||{},b.slice(7)):m._local,S=f?.isEnabled()?f.startStateChange(b,v,d,{actionId:a.actionId,handlerType:"setState",source:`target=${r}`}):void 0,E=l==="replace"?"replace":l==="shallow"?"shallow":"deep",A=E!=="deep"?{...d,__mergeMode:E,...S?{__setStateId:S}:{}}:{...d,...S?{__setStateId:S}:{}};if(r==="global"){if(!this.globalStateUpdater){H.warn("Global state updater is not set"),S&&f&&f.completeStateChange(S);return}const T=g?.state?.get()||{},{__setStateId:L,__mergeMode:M,...R}=A,D=E==="deep"?this.deepMergeWithState(R,T):R,U={...D,...S?{__setStateId:S}:{},...E!=="deep"?{__mergeMode:E}:{}};return H.log("setState global:",U),this.globalStateUpdater(U,{render:u}),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"global",...E!=="deep"?{__mergeMode:E}:{},...D}}else if(r==="isolated"){const T=a.isolatedContext;if(T){const{__mergeMode:L,__setStateId:M,...R}=A;return T.mergeState(R,E),H.log("[handleSetState] isolated state updated:",R,"mergeMode:",E),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"isolated",...R}}else if(H.warn('[handleSetState] isolated target used but no IsolatedStateContext found. Make sure the component has "isolatedState" attribute. Falling back to local state.'),a.setState){const M=window.__g7PendingLocalState||a.state||{},R=E==="deep"?this.deepMergeWithState(A,M):A;return a.setState(R),S&&f&&setTimeout(()=>f.completeStateChange(S),0),R}else throw S&&f&&f.completeStateChange(S),new It("isolated target used but no IsolatedStateContext found and no setState function in context")}else if(r==="local"||r==="component"||r==="_local"){if(i==="parent"||i==="root"){const L=window.__g7LayoutContextStack||[];if(L.length>0){const M=i==="parent"?L[L.length-1]:L[0];if(M?.setState){const{__mergeMode:R,__setStateId:D,...U}=A,j=E==="deep"?this.deepMergeWithState(U,M.state||{}):U;return H.log(`[handleSetState] scope=${i}: 타겟 컨텍스트에 상태 업데이트`,j),M.setState(j),S&&f&&setTimeout(()=>f.completeStateChange(S),0),j}}H.warn(`[handleSetState] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const T=a.setState&&!a._isDispatchFallbackContext;if(H.log("[handleSetState] isRealComponentContext:",T,"context.setState:",!!a.setState,"_isDispatchFallbackContext:",a._isDispatchFallbackContext),T){H.log("[handleSetState] Using COMPONENT setState path"),H.log("[handleSetState] resolvedPayload:",A),H.log("[handleSetState] context.state:",a.state),H.log("[handleSetState] mergeMode:",E);const M=window.__g7PendingLocalState||a.state||{},R=E==="deep"?this.deepMergeWithState(A,{}):A;H.log("[handleSetState] convertedPayload (변경 필드만, dot notation 변환):",R),a.setState(R);const D=E==="deep"?this.deepMergeWithState(A,M):E==="shallow"?{...a.state||{},...A}:A,{__mergeMode:U,__setStateId:j,...P}=D;window.__g7PendingLocalState=P,H.log("[handleSetState] __g7PendingLocalState updated:",P),this.globalStateUpdater&&(this.globalStateUpdater({_local:P},{render:!1}),H.log("[handleSetState] _global._local synced (render:false):",P));const{__mergeMode:X,__setStateId:ge,...Ee}=A;if(E==="replace")window.__g7ForcedLocalFields=Ee;else{const Ce=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=this.deepMergeWithState(Ee,Ce)}return H.log("[handleSetState] __g7ForcedLocalFields updated:",Ee),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"local",...D}}else if(this.globalStateUpdater){H.log("[handleSetState] Using GLOBAL STATE UPDATER path for _local");const M=window.__g7PendingLocalState||m._local||{};H.log("[handleSetState] currentLocal (with pending):",M);const R=E==="deep"?this.deepMergeWithState(A,M):A;return H.log("[handleSetState] finalLocal (merged):",R),this.globalStateUpdater({_local:R},{render:u}),window.__g7PendingLocalState=R,H.log("[handleSetState] __g7PendingLocalState updated for globalStateUpdater path"),S&&f&&setTimeout(()=>f.completeStateChange(S),0),{__target:"local",...R}}else throw S&&f&&f.completeStateChange(S),new It("setState function is not provided in context and globalStateUpdater is not set")}else if(r.startsWith("_local.")){const T=r.slice(7),{__mergeMode:L,__setStateId:M,...R}=A,D=R.value!==void 0?R.value:Object.values(R)[0];if(a.setState){const j=window.__g7PendingLocalState||a.state||{},P=this.createNestedUpdate(T,D,j),X={...j,...P};return a.setState(P),S&&f&&setTimeout(()=>f.completeStateChange(S),0),X}else throw S&&f&&f.completeStateChange(S),new It("setState function is not provided in context")}else{if(!a.setState)throw S&&f&&f.completeStateChange(S),new It("setState function is not provided in context");const L=window.__g7PendingLocalState||a.state||{},M=E==="deep"?this.deepMergeWithState(A,L):A;return a.setState(M),S&&f&&setTimeout(()=>f.completeStateChange(S),0),M}}async handleParentScopeSetState(e,a,r,i){const l=Un(),u=window.G7Core,d=r==="replace"?"replace":r==="shallow"?"shallow":"deep",f=e.startsWith("$parent."),g=f?"$parent.":"$root.",m=e.slice(g.length);let b,v;if(m==="_local"||m==="_global")b=m,v=void 0;else if(m.startsWith("_local."))b="_local",v=m.slice(7);else if(m.startsWith("_global."))b="_global",v=m.slice(8);else{H.warn(`[handleParentScopeSetState] 지원하지 않는 타겟 형식: ${e}. _local 또는 _global으로 시작해야 합니다.`);return}const S=window.__g7LayoutContextStack||[];if(S.length===0){H.warn(`[handleParentScopeSetState] 레이아웃 컨텍스트 스택이 비어있습니다. target=${e}`);return}const E=f?S[S.length-1]:S[0];if(!E){H.warn(`[handleParentScopeSetState] 타겟 컨텍스트를 찾을 수 없습니다. target=${e}`);return}const A=b==="_global"?E.state?._global:E.state?._local,T=l?.isEnabled()?l.startStateChange(e,A,a,{actionId:i.actionId,handlerType:"setState",source:`target=${e}`}):void 0,{__mergeMode:L,__setStateId:M,...R}=a;if(b==="_global"){if(!this.globalStateUpdater){H.warn("[handleParentScopeSetState] globalStateUpdater가 설정되지 않았습니다."),T&&l&&l.completeStateChange(T);return}const D=u?.state?.get()||{};if(v){const U=R.value!==void 0?R.value:Object.values(R)[0],j=this.createNestedUpdate(v,U,D),P={...D,...j};H.log(`[handleParentScopeSetState] ${e}: 전역 상태 중첩 경로 업데이트`,P),this.globalStateUpdater(P)}else{const U=d==="deep"?this.deepMergeWithState(R,D):R;H.log(`[handleParentScopeSetState] ${e}: 전역 상태 업데이트 (mergeMode=${d})`,U),this.globalStateUpdater(d!=="deep"?{...U,__mergeMode:d}:U)}return T&&l&&setTimeout(()=>l.completeStateChange(T),0),{__target:e,...R}}else{if(!E.setState){H.warn(`[handleParentScopeSetState] 타겟 컨텍스트에 setState가 없습니다. target=${e}`),T&&l&&l.completeStateChange(T);return}const D=E.state||{};if(v){const U=R.value!==void 0?R.value:Object.values(R)[0],j=this.createNestedUpdate(v,U,D),P={...D,...j};return H.log(`[handleParentScopeSetState] ${e}: 로컬 상태 중첩 경로 업데이트`,j),E.setState(j),E.state=P,E.dataContext&&(E.dataContext._local=P),Od(),T&&l&&setTimeout(()=>l.completeStateChange(T),0),P}else{const U=d!=="deep"?{...R,__mergeMode:d}:R,j=d==="replace"?R:d==="shallow"?{...D,...R}:this.deepMergeWithState(R,D);return H.log(`[handleParentScopeSetState] ${e}: 로컬 상태 업데이트 (mergeMode=${d})`,j),E.setState(U),E.state=j,E.dataContext&&(E.dataContext._local=j),Od(),T&&l&&setTimeout(()=>l.completeStateChange(T),0),j}}}deepMergeWithState(e,a){const r=["errors"];let i=e;if("..."in e){const u=e["..."];if(u&&typeof u=="object"&&!Array.isArray(u)){const{"...":d,...f}=e;i={...u,...f}}else{const{"...":d,...f}=e;i=f}}const l={...a};for(const[u,d]of Object.entries(i)){if(r.includes(u)){l[u]=d;continue}if(u.includes(".")&&u!=="..."){const f=this.createNestedUpdate(u,d,l);this.deepMergeInto(l,f,l);continue}if(d!==null&&typeof d=="object"&&!Array.isArray(d)&&Object.getPrototypeOf(d)!==Object.prototype&&Object.getPrototypeOf(d)!==null){l[u]=d;continue}d!==null&&typeof d=="object"&&!Array.isArray(d)&&a[u]!==null&&typeof a[u]=="object"&&!Array.isArray(a[u])?l[u]=this.deepMergeWithState(d,a[u]):l[u]=d}return l}deepMergeInto(e,a,r){for(const[i,l]of Object.entries(a)){if(l===null){e[i]=l;continue}if(e[i]===null){e[i]=l;continue}typeof l=="object"&&!Array.isArray(l)&&e[i]!==void 0&&e[i]!==null&&typeof e[i]=="object"&&!Array.isArray(e[i])?(e[i]===r[i]&&(e[i]={...e[i]}),this.deepMergeInto(e[i],l,r[i]||{})):typeof l=="object"&&!Array.isArray(l)&&e[i]===void 0&&r[i]!==null&&typeof r[i]=="object"&&!Array.isArray(r[i])?e[i]={...r[i],...l}:e[i]=l}}createNestedUpdate(e,a,r){const i=e.split(".");if(i.length===1)return{[i[0]]:a};const l={};let u=l,d=r;for(let f=0;f{this.globalStateUpdater({_local:S})};m.push({state:b,setState:v,dataContext:{_local:b||a.data?._local,_global:l?a.data?._global:a.data?._global||i?.state?.get?.(),_computed:a.data?._computed}}),window.__g7LayoutContextStack=m,H.log(`[handleOpenModal] 레이아웃 컨텍스트 스택에 push, 스택 크기: ${m.length}, state:`,b,l?"(direct)":"(fallback via G7Core)")}else H.warn(`[handleOpenModal] 컨텍스트 스택에 push 실패 - setState: ${!!a.setState}, state: ${a.state!==void 0}, G7Core: ${!!i?.state?.getLocal}`);const f=[...r,e];this.globalStateUpdater({modalStack:f,activeModal:e});const g=Un();if(g?.isEnabled()){const m=r.length>0?r[r.length-1]:void 0;g.trackModalOpen?.({modalId:e,modalName:e,scopeType:"isolated",parentModalId:m,initialState:a.state?{...a.state}:{}})}}async handleCloseModal(e){if(!this.globalStateUpdater){H.warn("Global state updater is not set for closeModal");return}const a=e?.data?._global?.modalStack||[],r=a.length>0?a[a.length-1]:null,i=a.slice(0,-1),l=i.length>0?i[i.length-1]:null,u=window.__g7LayoutContextStack||[];if(u.length>0){const d=u[u.length-1];d&&(d.dataContext=void 0),u.pop(),window.__g7LayoutContextStack=u,H.log(`[handleCloseModal] 레이아웃 컨텍스트 스택에서 pop, 스택 크기: ${u.length}`)}if(r){const d=Un();d?.isEnabled()&&d.trackModalClose?.(r,e?.state)}this.globalStateUpdater({modalStack:i,activeModal:l})}async handleShowAlert(e,a){let r=e;this.translationEngine&&this.translationContext&&e.startsWith("$t:")&&(r=this.translationEngine.resolveTranslations(e,this.translationContext,a.data)),alert(r)}async handleToast(e,a){const{type:r="info",message:i,icon:l,duration:u}=e;if(r==="error"){const f=a.data?.error;if((f?.error_code==="identity_verification_required"||f?.data?.error_code==="identity_verification_required")&&St.consumeDomainNoticeShown()){H.log("[Toast] IDV 도메인 안내 표출됨 — 중복 가드 토스트 1건 skip");return}}let d=i;if(this.translationEngine&&this.translationContext&&i?.startsWith("$t:")&&(d=this.translationEngine.resolveTranslations(i,this.translationContext,a.data)),this.globalStateUpdater){const g={id:`toast_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,type:r,message:d,...l&&{icon:l},...u&&{duration:u}},S=[...window.G7Core?.state?.get()?.toasts||[],g];this.globalStateUpdater({toasts:S})}else H.log(`[Toast ${r}] ${d}`)}async handleSwitch(e,a){const{cases:r}=e;if(!r){H.warn("switch handler requires cases property");return}const i=this.resolveParams(e.params,a.data);let l;if(i?.value!==void 0?(l=i.value,H.log("switch handler: using params.value as case key:",l)):(l=a.data?.$args?.[0],H.log("switch handler: using $args[0] as case key:",l)),l==null){if(r.default)return H.log("switch handler: case key is undefined, using default case"),await this.executeAction(r.default,a);H.warn("switch handler: case key is not defined and no default case");return}const u=String(l);let d=r[u];if(!d&&r.default&&(H.log(`switch handler: no case found for key "${u}", using default case`),d=r.default),!d){H.warn(`switch handler: no case found for key "${u}" and no default case`);return}return await this.executeAction(d,a)}async handleConditions(e,a){const{conditions:r}=e;if(!r||!Array.isArray(r)){H.warn("conditions handler requires conditions array");return}const i=a.data||{},l=lp(r,i,this.bindingEngine);if(!l.matched){H.log("conditions handler: no matching branch found");return}const u=r[l.branchIndex];if(H.log(`conditions handler: matched branch ${l.branchIndex}`),!u.then){H.warn('conditions handler: matched branch has no "then" action');return}return Array.isArray(u.then)?await this.handleSequence({...e,actions:u.then},a):await this.executeAction(u.then,a)}handleStartInterval(e,a){const r=typeof e.id=="string"?e.id:"",i=Number(e.intervalMs??1e3),l=Array.isArray(e.actions)?e.actions:[];if(r===""||!l.length||i<=0)return H.warn("startInterval requires non-empty id, positive intervalMs, and actions array"),{success:!1};const u=this.intervals.get(r);u&&(clearInterval(u),this.intervals.delete(r));const d=setInterval(()=>{for(const f of l)this.executeAction(f,a).catch(g=>{H.error(`[startInterval:${r}] tick action failed`,g)})},i);return this.intervals.set(r,d),{success:!0,id:r}}handleStopInterval(e){const a=typeof e.id=="string"?e.id:"";if(a==="")return H.warn("stopInterval requires id parameter"),{success:!1};const r=this.intervals.get(a);return r&&(clearInterval(r),this.intervals.delete(a)),{success:!0,id:a}}stopAllIntervals(){for(const e of this.intervals.values())clearInterval(e);this.intervals.clear()}async handleSequence(e,a){const r=e.actions||e.params?.actions;if(!r||!Array.isArray(r)||r.length===0)return H.warn("sequence handler requires non-empty actions array"),[];const i=Un(),l=i?.isEnabled()&&i.startSequenceExecution?.({eventType:e.type})||"";window.__g7SequenceLocalSync=void 0;const u=[];let d,f=a.state?{...a.state}:{},g=a.isolatedContext?.state?{...a.isolatedContext.state}:{},m=a.data?._computed||{};const b=window.G7Core,v=()=>({_global:b?.state?.get()||{},_local:{...f},_isolated:Object.keys(g).length>0?{...g}:void 0});for(let S=0;S0){const R={},D={...a.data,_local:f,_computed:R,$computed:R,_isolated:g};for(const[U,j]of Object.entries(M))if(typeof j=="string"){const P=j.trim();if(P.startsWith("{{")&&P.endsWith("}}"))try{const X=P.slice(2,-2).trim();R[U]=this.bindingEngine.evaluateExpression(X,D,{skipCache:!0})}catch{R[U]=m[U]}}m=R,H.log("[handleSequence] _computed recalculated after setState:",m)}}if(E.handler!=="setState"){const M=window.__g7SequenceLocalSync;if(M&&M!==f){f=M,window.__g7SequenceLocalSync=void 0,H.log("[handleSequence] currentState synchronized from __g7SequenceLocalSync after custom handler:",E.handler);const R=a.data?._computedDefinitions;if(R&&Object.keys(R).length>0){const D={},U={...a.data,_local:f,_computed:D,$computed:D,_isolated:g};for(const[j,P]of Object.entries(R))if(typeof P=="string"){const X=P.trim();if(X.startsWith("{{")&&X.endsWith("}}"))try{const ge=X.slice(2,-2).trim();D[j]=this.bindingEngine.evaluateExpression(ge,U,{skipCache:!0})}catch{D[j]=m[j]}}m=D,H.log("[handleSequence] _computed recalculated after custom handler:",m)}}}l&&i?.isEnabled()&&i.captureSequenceActionAfter?.(l,S,v(),Date.now()-A,L.data)}catch(L){throw l&&i?.isEnabled()&&(i.captureSequenceActionAfter?.(l,S,v(),Date.now()-A,void 0,L),i.endSequenceExecution?.(l,L)),H.error(`sequence handler: action[${S}] failed:`,L),L}}return window.__g7SequenceLocalSync=void 0,l&&i?.isEnabled()&&i.endSequenceExecution?.(l),u}async handleParallel(e,a){const r=e.actions||e.params?.actions;if(!r||!Array.isArray(r)||r.length===0)return H.warn("parallel handler requires non-empty actions array"),[];H.log(`parallel handler: executing ${r.length} actions in parallel`);const i=a.isolatedContext?{...a.isolatedContext,state:{...a.isolatedContext.state}}:null,l=r.map((f,g)=>{const m={...a,isolatedContext:i};return this.executeAction(f,m).catch(b=>{throw H.error(`parallel handler: action[${g}] failed:`,b),b})}),u=await Promise.allSettled(l),d=u.filter(f=>f.status==="rejected").length;return d>0&&H.warn(`parallel handler: ${d}/${r.length} actions failed`),u}async handleLoadScript(e,a,r){const{src:i,id:l,async:u=!0,defer:d=!1}=e;if(!i)throw new It('loadScript handler requires "src" parameter',a);const f=l||`script_${i.replace(/[^a-zA-Z0-9]/g,"_")}`;return ds.loadedScripts.has(f)?(H.log(`loadScript: script already loaded, skipping: ${f}`),a.onLoad&&await this.executeAction(a.onLoad,r),!0):document.getElementById(f)?(H.log(`loadScript: script element already exists: ${f}`),ds.loadedScripts.add(f),a.onLoad&&await this.executeAction(a.onLoad,r),!0):(H.log(`loadScript: loading script: ${i}`),new Promise((m,b)=>{const v=document.createElement("script");v.id=f,v.src=i,v.async=u,v.defer=d,v.onload=async()=>{if(H.log(`loadScript: script loaded successfully: ${f}`),ds.loadedScripts.add(f),a.onLoad)try{await this.executeAction(a.onLoad,r)}catch(S){H.error("loadScript: onLoad action failed:",S)}m(!0)},v.onerror=S=>{H.error(`loadScript: failed to load script: ${i}`,S),b(new It(`Failed to load script: ${i}`,a))},document.head.appendChild(v)}))}async handleCallExternal(e,a,r){const i=e.constructor,l=e.args||{},u=e.method,d=e.methodArgs||[],f=e.callbackEvent,g=e.embedTarget,m=e.callbackSetState,b=e.callbackAction;if(!i)throw new It('callExternal handler requires "constructor" parameter',a);const v=this.getNestedProperty(window,i);if(!v||typeof v!="function")throw new It(`Constructor not found or not a function: ${i}. Make sure the script is loaded first using loadScript handler.`,a);H.log(`callExternal: calling constructor ${i}`);const S={...l};for(const[A,T]of Object.entries(l))T===!0&&(S[A]=L=>{if(H.log(`callExternal: callback triggered for ${A}`,L),f&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(f,L),m&&r.setState){const M=U=>{const j={};for(const[P,X]of Object.entries(U))typeof X=="string"?j[P]=this.getNestedProperty(L,X):typeof X=="object"&&X!==null&&(j[P]=M(X));return j},R=M(m);H.log("callExternal: callbackSetState mapping result",R);const D=this.deepMergeWithState(R,r.state||{});H.log("callExternal: merged with existing state",D),r.setState(D)}else f&&r.setState&&r.setState({[`${f.replace(/:/g,"_")}_result`]:L});if(b){const M={...r,data:{...r.data,$event:L}},R=Array.isArray(b)?b:[b];for(const D of R)try{this.executeAction(D,M)}catch(U){H.error("callExternal: callbackAction failed:",U)}}});const E=new v(S);if(u&&typeof E[u]=="function"){if(H.log(`callExternal: calling method ${u}`),u==="embed"&&g){const A=document.querySelector(g);if(A)return E[u](A,...d);throw new It(`Embed target element not found: ${g}`,a)}return E[u](...d)}return E}async handleCallExternalEmbed(e,a,r){const i=e.constructor,l=e.args||{},u=e.callbackSetState,d=e.callbackEvent,f=e.layerClassName,g=e.callbackAction;if(!i)throw new It('callExternalEmbed handler requires "constructor" parameter',a);const m=this.getNestedProperty(window,i);if(!m||typeof m!="function")throw new It(`Constructor not found or not a function: ${i}. Make sure the script is loaded first.`,a);H.log(`callExternalEmbed: creating layer for ${i}`);const{layer:b,closeLayer:v}=this.createEmbedLayer(f),S={...l};for(const[A,T]of Object.entries(l))T===!0&&(S[A]=L=>{if(H.log(`callExternalEmbed: callback triggered for ${A}`,L),v(),d&&typeof window<"u"&&window.G7Core?.componentEvent&&window.G7Core.componentEvent.emit(d,L),u&&r.setState){const M=U=>{const j={};for(const[P,X]of Object.entries(U))typeof X=="string"?j[P]=this.getNestedProperty(L,X):typeof X=="object"&&X!==null&&(j[P]=M(X));return j},R=M(u);H.log("callExternalEmbed: callbackSetState mapping result",R);const D=this.deepMergeWithState(R,r.state||{});H.log("callExternalEmbed: merged with existing state",D),r.setState(D)}if(g){const M={...r,data:{...r.data,$event:L}},R=Array.isArray(g)?g:[g];for(const D of R)try{this.executeAction(D,M)}catch(U){H.error("callExternalEmbed: callbackAction failed:",U)}}});const E=new m(S);if(typeof E.embed=="function")E.embed(b),H.log("callExternalEmbed: embedded in layer");else throw v(),new It(`Constructor ${i} does not have embed method`,a);return E}isImeComposing(e){return e?.isComposing===!0||e?.keyCode===229}createEmbedLayer(e){const a=document.createElement("div");a.id="g7-embed-overlay",a.style.cssText=` position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.5); @@ -47,20 +47,20 @@ Error generating stack: `+c.message+` justify-content: center; color: #666; transition: background-color 0.2s; - `,i.onmouseenter=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.2)"},i.onmouseleave=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.1)"};const l=()=>{n.parentNode&&n.parentNode.removeChild(n)};i.onclick=l;const u=d=>{this.isImeComposing(d)||d.key==="Escape"&&(l(),document.removeEventListener("keydown",u))};return document.addEventListener("keydown",u),n.onclick=d=>{d.target===n&&l()},r.appendChild(i),n.appendChild(r),document.body.appendChild(n),{overlay:n,layer:r,closeLayer:l}}getNestedProperty(e,n){return n.split(".").reduce((r,i)=>r&&r[i]!==void 0?r[i]:void 0,e)}async handleSaveToLocalStorage(e,n){const{key:r,value:i}=e;if(!r)return z.error("saveToLocalStorage: key parameter is required"),!1;try{const l=typeof i=="string"?i:JSON.stringify(i);return localStorage.setItem(r,l),z.log(`saveToLocalStorage: saved ${r}`,i),!0}catch(l){return z.error("saveToLocalStorage: failed to save",{key:r,error:l}),!1}}async handleLoadFromLocalStorage(e,n){const{key:r,target:i,stateKey:l,defaultValue:u}=e;if(!r)return z.error("loadFromLocalStorage: key parameter is required"),u;try{const d=localStorage.getItem(r);if(d===null)return z.log(`loadFromLocalStorage: no value found for ${r}, using default`),l&&n.setState&&n.setState({[l]:u}),u;let f;try{f=JSON.parse(d)}catch{f=d}return z.log(`loadFromLocalStorage: loaded ${r}`,f),l&&n.setState&&n.setState({[l]:f}),f}catch(d){return z.error("loadFromLocalStorage: failed to load",{key:r,error:d}),u}}async handleCustomAction(e,n){const r=this.customHandlers.get(e.handler);if(!r){if(this.previewMode){z.warn(`[Preview] Unknown action handler "${e.handler}" — skipped (preview mode)`);return}const i=new Ut(`Unknown action handler: ${e.handler}`,e);throw i.unknownHandler=!0,i}return await r(e,n)}resolveParams(e,n){if(!e)return{};const r={};for(const[i,l]of Object.entries(e))i.includes("{{")&&z.warn(`[resolveParams] setState params의 키에 표현식이 포함되어 있습니다: "${i}". 키는 해석되지 않습니다. 배열 항목 수정은 .map()/.filter() 패턴을 사용하세요.`),typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):typeof l=="string"?r[i]=l:Array.isArray(l)?r[i]=l.map(u=>typeof u=="string"&&u.includes("{{")?this.evaluateExpression(u,n):u):typeof l=="object"&&l!==null&&!Array.isArray(l)&&Object.getPrototypeOf(l)!==Object.prototype&&Object.getPrototypeOf(l)!==null?r[i]=l:typeof l=="object"&&l!==null?r[i]=this.resolveParams(l,n):r[i]=l;return r}resolveValue(e,n){return n?this.bindingEngine.resolveBindings(e,n,{skipCache:!0}):e}evaluateExpressions(e,n){let r={};for(const[i,l]of Object.entries(e))if(i==="..."){let u=l;typeof l=="string"&&l.includes("{{")&&(u=this.evaluateExpression(l,n)),u&&typeof u=="object"&&!Array.isArray(u)&&(r={...r,...u})}else typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):typeof l=="object"&&l!==null&&!Array.isArray(l)?r[i]=this.evaluateExpressions(l,n):r[i]=l;return r}evaluateExpressionsIfNeeded(e,n){const r={};for(const[i,l]of Object.entries(e))typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,n):r[i]=l;return r}evaluateExpression(e,n){if(!n)return e;const r=e.match(/^\{\{(.+)\}\}$/);if(!(r&&!r[1].includes("}}")&&!r[1].includes("{{"))){let d=n;if(e.includes("_global")){const g=window.G7Core?.state?.get()?._global;g&&(d={...n,_global:g})}if(e.includes("_computed")||e.includes("$computed")){const g=window.__g7ActionContext?.computedRef?.current;g&&Object.keys(g).length>0&&(d={...d,_computed:g,$computed:g})}return this.bindingEngine.resolveBindings(e,d,{skipCache:!0})}let l=r[1].trim();l=l.replace(/\$args\.(\d+)/g,"$args[$1]");let u=n;if(l.includes("_global")){const f=window.G7Core?.state?.get()?._global;f&&(u={...n,_global:f})}if(l.includes("_computed")||l.includes("$computed")){const f=window.__g7ActionContext?.computedRef?.current;f&&Object.keys(f).length>0&&(u={...u,_computed:f,$computed:f})}l.includes("_global.modules")&&(z.log("[evaluateExpression] expression:",l),z.log("[evaluateExpression] dataContext._global:",u._global),z.log("[evaluateExpression] dataContext._global?.modules:",u._global?.modules));try{const d=this.bindingEngine.evaluateExpression(l,u);return l.includes("_global.modules")&&z.log("[evaluateExpression] result:",d),typeof d=="string"&&d.startsWith("$t:")&&this.translationEngine&&this.translationContext?this.translationEngine.resolveTranslations(d,this.translationContext,n):d}catch(d){return z.error("Expression evaluation failed:",e,d),e}}setDefaultContext(e){this.defaultContext={...this.defaultContext,...e}}setGlobalStateUpdater(e){this.globalStateUpdater=e}getGlobalStateUpdater(){return this.globalStateUpdater}async dispatchAction(e,n){const r={...this.defaultContext,...n,data:{...this.defaultContext.data,...n?.data}};if(e.confirm){let i=this.resolveValue(e.confirm,r.data);if(this.translationEngine&&this.translationContext&&i.startsWith("$t:")&&(i=this.translationEngine.resolveTranslations(i,this.translationContext,r.data)),!confirm(i))return{success:!1}}try{return await this.executeAction(e,r)}catch(i){if(z.error("dispatchAction failed:",i),e.onError){const l=i instanceof Ut&&i.originalError?{message:i.originalError.message,response:i.originalError.response}:{message:i instanceof Error?i.message:String(i)},u={...r,data:{...r.data,error:l}},d=Array.isArray(e.onError)?e.onError:[e.onError];for(const f of d)await this.executeAction(f,u)}return{success:!1,error:i instanceof Error?i:new Error(String(i))}}}registerHandler(e,n,r){this.customHandlers.set(e,n);const i=qn();i?.isEnabled()&&i.trackHandlerRegistration(e,r?.category??"custom",r?.description,r?.source)}unregisterHandler(e){this.customHandlers.delete(e);const n=qn();n?.isEnabled()&&n.trackHandlerUnregistration(e)}getRegisteredHandlers(){return Array.from(this.customHandlers.keys())}getHandler(e){return this.customHandlers.get(e)}normalizeDebounceConfig(e){return typeof e=="number"?{delay:e,leading:!1,trailing:!0}:{delay:e.delay,leading:e.leading??!1,trailing:e.trailing??!0}}extractEventData(e){const n=e.target;if(n&&!("tagName"in n)){const l={type:e.type??"custom",target:{value:n.value,name:n.name??""}};return e._changedKeys&&(l._changedKeys=e._changedKeys),l}const i=n?{value:n.value,name:n.name??"",checked:n.checked??!1,type:n.type??"",tagName:n.tagName}:{};if(e.type==="scroll"&&n){const l=n;i.scrollHeight=l.scrollHeight??0,i.scrollTop=l.scrollTop??0,i.clientHeight=l.clientHeight??0,i.scrollLeft=l.scrollLeft??0,i.clientWidth=l.clientWidth??0,i.scrollWidth=l.scrollWidth??0}return{type:e.type,target:n?i:null}}executeDebouncedAction(e,n,r,i,l){const u={...r,$event:n,$args:l},d={type:n.type,target:n.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(e,u,i)(d)}handleDebouncedAction(e,n,r,i,l,u){const d=this.normalizeDebounceConfig(e.debounce),f=l||`default-${e.handler}-${Date.now()}`,g=this.extractEventData(n),m=g._changedKeys;if(m&&Array.isArray(m)&&g?.target?.value&&typeof g.target.value=="object"&&!Array.isArray(g.target.value)){const _=this.debounceAccumulatedValues.get(f);if(_){const x={..._};for(const k of m)x[k]=g.target.value[k];g.target.value=x}this.debounceAccumulatedValues.set(f,g.target.value)}const b=this.debounceTimers.get(f);b&&(clearTimeout(b),this.debounceTimers.delete(f));const v=qn();v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"pending",params:e.params,debounce:{delay:d.delay,status:"pending",scheduledAt:Date.now()}});const S=!this.debounceTimers.has(f+"_leading");if(d.leading&&S&&(this.debounceTimers.set(f+"_leading",setTimeout(()=>{},0)),this.executeDebouncedAction(e,g,r,i,u),v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"leading"}})),d.trailing){const _=()=>{this.executeDebouncedAction(e,g,r,i,u),v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"trailing"}})},x=setTimeout(()=>{this.debounceTimers.delete(f),this.debounceTimers.delete(f+"_leading"),this.pendingDebounceFlushers.delete(f),this.debounceAccumulatedValues.delete(f),_()},d.delay);this.debounceTimers.set(f,x),this.pendingDebounceFlushers.set(f,_)}}clearDebounceTimers(e){if(e)for(const[n,r]of this.debounceTimers)n.startsWith(e+"-")&&(clearTimeout(r),this.debounceTimers.delete(n),this.pendingDebounceFlushers.delete(n),this.debounceAccumulatedValues.delete(n));else{for(const n of this.debounceTimers.values())clearTimeout(n);this.debounceTimers.clear(),this.pendingDebounceFlushers.clear(),this.debounceAccumulatedValues.clear()}}debouncedCall(e,n,r){const i=this.debounceTimers.get(e);i&&(clearTimeout(i),this.debounceTimers.delete(e));const l=setTimeout(()=>{this.debounceTimers.delete(e),this.pendingDebounceFlushers.delete(e),r()},n);this.debounceTimers.set(e,l),this.pendingDebounceFlushers.set(e,r)}flushPendingDebounceTimers(){if(this.pendingDebounceFlushers.size===0)return;const e=new Map(this.pendingDebounceFlushers);for(const[n,r]of e){const i=this.debounceTimers.get(n);i&&(clearTimeout(i),this.debounceTimers.delete(n)),this.debounceTimers.delete(n+"_leading"),this.pendingDebounceFlushers.delete(n),r()}this.globalStateUpdater&&this.globalStateUpdater({})}bindActionsToProps(e,n,r){const i={...e};if(e.actions&&Array.isArray(e.actions)){const l=new Map;for(const m of e.actions){const b=this.resolveActionRef(m),v=b.event||this.getEventHandlerName(b.type);l.has(v)||l.set(v,[]),l.get(v).push(b)}const u=e.name||e.id||"unknown",d=l.has("onDrop"),f=l.has("onDragOver"),g=l.has("onDragEnter");d&&!f&&(i.onDragOver=m=>{m.preventDefault()}),d&&!g&&(i.onDragEnter=m=>{m.preventDefault()});for(const[m,b]of l)i[m]=(...v)=>{for(const S of b){const _=v[0],x=_&&typeof _=="object"&&"preventDefault"in _;if(x&&(m==="onDragOver"||m==="onDrop")&&_.preventDefault(),x&&S.key&&"key"in _){if(this.isImeComposing(_)){z.log("Key filter skipped (IME composing):",S.key);continue}if(_.key!==S.key){z.log("Key filter not matched:",S.key,"actual:",_.key);continue}z.log("Key filter matched:",S.key)}if(S.debounce){const k=`${u}-${S.handler}-${m}`,O=_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null;let M;if(x)M=_;else if(O){const D={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};_._changedKeys&&(D._changedKeys=_._changedKeys),M=D}else M=new Event("custom");const T=S.event?{...n,$args:v}:n;this.handleDebouncedAction(S,M,T,r,k,v)}else if(this.flushPendingDebounceTimers(),S.event){const k={...n,$args:v},O=x?_:new Event("custom");this.createHandler(S,k,r)(O)}else if(x)this.createHandler(S,n,r)(_);else if(_&&typeof _=="object"&&!("preventDefault"in _)&&"target"in _&&_.target!==null){const O={type:"custom",target:_.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(S,n,r)(O)}}}}return i}getEventHandlerName(e){return{click:"onClick",change:"onChange",input:"onInput",submit:"onSubmit",focus:"onFocus",blur:"onBlur",keydown:"onKeyDown",keyup:"onKeyUp",keypress:"onKeyPress",mousedown:"onMouseDown",mouseup:"onMouseUp",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",scroll:"onScroll",dragstart:"onDragStart",drag:"onDrag",dragend:"onDragEnd",dragenter:"onDragEnter",dragover:"onDragOver",dragleave:"onDragLeave",drop:"onDrop"}[e]||`on${e.charAt(0).toUpperCase()}${e.slice(1)}`}sanitizeForDevTools(e,n=5){const r=new WeakSet,i=(l,u)=>{if(l==null||typeof l=="string"||typeof l=="number"||typeof l=="boolean")return l;if(u>n)return"[Max Depth Exceeded]";if(typeof l=="function")return`[Function: ${l.name||"anonymous"}]`;if(l instanceof Element)return`[Element: ${l.tagName}${l.className?"."+l.className.split(" ").join("."):""}]`;if(l instanceof Event)return`[Event: ${l.type}]`;if(Array.isArray(l)){if(r.has(l))return"[Circular Array]";r.add(l);const d=l.slice(0,10).map(f=>i(f,u+1));return l.length>10&&d.push(`... ${l.length-10} more items`),d}if(typeof l=="object"){if(r.has(l))return"[Circular Reference]";r.add(l);const d={},f=Object.keys(l),g=f.slice(0,20);for(const m of g)try{d[m]=i(l[m],u+1)}catch{d[m]="[Error accessing property]"}return f.length>20&&(d["..."]=`${f.length-20} more properties`),d}return String(l)};return i(e,0)}};$(gs,"loadedScripts",new Set);let fo=gs,Jl=null;function lm(o){return Jl||(Jl=new fo(o)),Jl}function mC(o){Jl=o}const cm=dt("TransitionManager");class yC{constructor(){$(this,"isPending",!1);$(this,"subscribers",new Set)}setPending(e){this.isPending!==e&&(cm.log("setPending:",{from:this.isPending,to:e,stack:new Error().stack}),this.isPending=e,this.notifySubscribers())}getIsPending(){return this.isPending}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){cm.log("notifySubscribers:",{isPending:this.isPending,subscriberCount:this.subscribers.size}),this.subscribers.forEach(e=>{e(this.isPending)})}clearSubscribers(){this.subscribers.clear()}}const Ha=new yC,Ql=dt("TransitionContext"),um=I.createContext({isTransitioning:!1}),Nd=({children:o})=>{const[e,n]=I.useState(()=>{const i=Ha.getIsPending();return Ql.log("Initial state:",i),i});I.useEffect(()=>{Ql.log("Subscribing to TransitionManager");const i=Ha.subscribe(l=>{Ql.log("Received isPending update:",l),n(l)});return()=>{Ql.log("Unsubscribing from TransitionManager"),i()}},[]);const r=I.useMemo(()=>({isTransitioning:e}),[e]);return ft.jsx(um.Provider,{value:r,children:o})},dm=()=>I.useContext(um),Zl="__g7LocalInitTracking";function Id(){const o=globalThis;return o[Zl]||(o[Zl]={hash:"",timestamp:void 0,consumed:void 0}),o[Zl]}function bC(){const o=globalThis;o[Zl]={hash:"",timestamp:void 0,consumed:void 0}}function vC(o){Id().consumed=o}function SC(o){return Id().consumed===o}function wC(o,e){return typeof o=="number"&&typeof e=="number"?Math.max(o,e):e??o}function CC(o,e){if(e===void 0)return o;if(!(o!==null&&typeof o=="object"&&e!==null&&typeof e=="object"&&!SC(o)))return e;const{_forceLocalInit:r,...i}=o,{_forceLocalInit:l,...u}=e,d={...i,...u},f=wC(r,l);return f!==void 0&&(d._forceLocalInit=f),d}function EC(o){return o<768?"mobile":o<1024?"tablet":"desktop"}function fm(o){const e=EC(o);return{width:o,isMobile:e==="mobile",isTablet:e==="tablet",isDesktop:e==="desktop",matchedPreset:e}}const Hd=I.createContext(fm(1024)),ec=({children:o,overrideWidth:e})=>{const[n,r]=I.useState(()=>ui.getWidth());I.useEffect(()=>{const u=ui.subscribe(d=>{r(d)});return()=>{u()}},[]);const i=e??n,l=I.useMemo(()=>fm(i),[i]);return ft.jsx(Hd.Provider,{value:l,children:o})},zd=()=>I.useContext(Hd);window.G7Core?.createLogger?.("Core:State");function _C(o,e,n,r){const{syncOnControlledChange:i=!0,isEqual:l}=r??{},u=o!==void 0;I.useRef(u);const[d,f]=I.useState(o!==void 0?o:e),g=d,m=I.useRef(o),b=I.useRef(!1);I.useEffect(()=>{if(b.current){m.current=o;return}i&&u&&o!==m.current&&(l?!l(d,o):d!==o)&&f(o),m.current=o},[o,u,i,l,d]);const v=I.useCallback(S=>{const _=typeof S=="function"?S(g):S;(l?l(g,_):g===_)||(b.current=!0,f(_),n?.(_),requestAnimationFrame(()=>{b.current=!1}))},[g,u,n,l]);return[g,v]}function AC(o,e){return o===e?!0:o.length!==e.length?!1:o.every((n,r)=>n===e[r])}function hm(o,e){if(o===e)return!0;const n=Object.keys(o),r=Object.keys(e);return n.length!==r.length?!1:n.every(i=>o[i]===e[i])}const gm=I.createContext({}),xC=({children:o,value:e})=>ft.jsx(gm.Provider,{value:e,children:o}),TC=()=>I.useContext(gm),RC=(o,e)=>{if(!o)return;const n=e.split(".");let r=o;for(const i of n){if(r==null)return;r=r[i]}return r},kC=(o,e,n)=>{const r=e.split(".");if(r.length===1)return{...o,[r[0]]:n};const i={...o};let l=i;for(let u=0;u{ss.warn("IsolatedStateContext not available: setState called outside IsolatedStateProvider")},getState:()=>{},mergeState:()=>{ss.warn("IsolatedStateContext not available: mergeState called outside IsolatedStateProvider")},stateRef:{current:{}},scopeId:""},pm=I.createContext(DC);let OC=0;function LC(){return`isolated-${++OC}-${Date.now().toString(36)}`}function mm(o,e){if(e==null)return o;if(typeof e!="object"||Array.isArray(e))return e;const n={...o};for(const r of Object.keys(e)){const i=e[r],l=o?.[r];i!==null&&typeof i=="object"&&!Array.isArray(i)&&l!==null&&typeof l=="object"&&!Array.isArray(l)?n[r]=mm(l,i):n[r]=i}return n}function MC(o,e){return e?e.split(".").reduce((n,r)=>n?.[r],o):o}function $C(o,e,n){const r=e.split("."),i={...o};let l=i;for(let u=0;u{const i=I.useRef(e||LC()).current,[l,u]=I.useState(o),d=I.useRef(l);d.current=l;const f=I.useCallback(S=>MC(d.current,S),[]),g=I.useCallback((S,_)=>{u(x=>{const k=$C(x,S,_);return ss.log(`[${i}] setState: ${S} =`,_),k})},[i]),m=I.useCallback((S,_="deep")=>{u(x=>{let k;return _==="replace"?k=S:_==="shallow"?k={...x,...S}:k=mm(x,S),ss.log(`[${i}] mergeState (${_}):`,S),k})},[i]),b=I.useMemo(()=>({state:l,setState:g,getState:f,mergeState:m,stateRef:d,scopeId:i}),[l,g,f,m,i]),v=I.useMemo(()=>({state:d.current,setState:g,getState:f,mergeState:m}),[g,f,m]);return I.useEffect(()=>{const S=window.G7Core,_=window.__g7IsolatedStates||{};return window.__g7IsolatedStates||(window.__g7IsolatedStates=_),_[i]=v,S&&(S._isolatedStates||(S._isolatedStates={}),S._isolatedStates[i]=l),S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:created",{id:i,state:l}),ss.log(`[${i}] IsolatedStateProvider mounted with initial state:`,l),()=>{delete _[i],S?._isolatedStates&&delete S._isolatedStates[i],S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:destroyed",{id:i}),ss.log(`[${i}] IsolatedStateProvider unmounted`)}},[i,v]),I.useEffect(()=>{const S=window.G7Core,_=window.__g7IsolatedStates;_?.[i]&&(_[i].state=l),S?._isolatedStates&&(S._isolatedStates[i]=l),S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:updated",{id:i,state:l})},[l,i]),ft.jsx(pm.Provider,{value:b,children:n})},IC=()=>{const o=I.useContext(pm);return o.scopeId?o:null};var Ar=Pg();function HC(){for(var o=arguments.length,e=new Array(o),n=0;nr=>{e.forEach(i=>i(r))},e)}const tc=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function os(o){const e=Object.prototype.toString.call(o);return e==="[object Window]"||e==="[object global]"}function jd(o){return"nodeType"in o}function Mn(o){var e,n;return o?os(o)?o:jd(o)&&(e=(n=o.ownerDocument)==null?void 0:n.defaultView)!=null?e:window:window}function Ud(o){const{Document:e}=Mn(o);return o instanceof e}function ho(o){return os(o)?!1:o instanceof Mn(o).HTMLElement}function ym(o){return o instanceof Mn(o).SVGElement}function ls(o){return o?os(o)?o.document:jd(o)?Ud(o)?o:ho(o)||ym(o)?o.ownerDocument:document:document:document}const za=tc?I.useLayoutEffect:I.useEffect;function Bd(o){const e=I.useRef(o);return za(()=>{e.current=o}),I.useCallback(function(){for(var n=arguments.length,r=new Array(n),i=0;i{o.current=setInterval(r,i)},[]),n=I.useCallback(()=>{o.current!==null&&(clearInterval(o.current),o.current=null)},[]);return[e,n]}function go(o,e){e===void 0&&(e=[o]);const n=I.useRef(o);return za(()=>{n.current!==o&&(n.current=o)},e),n}function po(o,e){const n=I.useRef();return I.useMemo(()=>{const r=o(n.current);return n.current=r,r},[...e])}function nc(o){const e=Bd(o),n=I.useRef(null),r=I.useCallback(i=>{i!==n.current&&e?.(i,n.current),n.current=i},[]);return[n,r]}function Pd(o){const e=I.useRef();return I.useEffect(()=>{e.current=o},[o]),e.current}let qd={};function mo(o,e){return I.useMemo(()=>{if(e)return e;const n=qd[o]==null?0:qd[o]+1;return qd[o]=n,o+"-"+n},[o,e])}function bm(o){return function(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),i=1;i{const d=Object.entries(u);for(const[f,g]of d){const m=l[f];m!=null&&(l[f]=m+o*g)}return l},{...e})}}const cs=bm(1),yo=bm(-1);function jC(o){return"clientX"in o&&"clientY"in o}function Gd(o){if(!o)return!1;const{KeyboardEvent:e}=Mn(o.target);return e&&o instanceof e}function UC(o){if(!o)return!1;const{TouchEvent:e}=Mn(o.target);return e&&o instanceof e}function Vd(o){if(UC(o)){if(o.touches&&o.touches.length){const{clientX:e,clientY:n}=o.touches[0];return{x:e,y:n}}else if(o.changedTouches&&o.changedTouches.length){const{clientX:e,clientY:n}=o.changedTouches[0];return{x:e,y:n}}}return jC(o)?{x:o.clientX,y:o.clientY}:null}const bo=Object.freeze({Translate:{toString(o){if(!o)return;const{x:e,y:n}=o;return"translate3d("+(e?Math.round(e):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(o){if(!o)return;const{scaleX:e,scaleY:n}=o;return"scaleX("+e+") scaleY("+n+")"}},Transform:{toString(o){if(o)return[bo.Translate.toString(o),bo.Scale.toString(o)].join(" ")}},Transition:{toString(o){let{property:e,duration:n,easing:r}=o;return e+" "+n+"ms "+r}}}),vm="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function BC(o){return o.matches(vm)?o:o.querySelector(vm)}const PC={display:"none"};function qC(o){let{id:e,value:n}=o;return Xe.createElement("div",{id:e,style:PC},n)}function GC(o){let{id:e,announcement:n,ariaLiveType:r="assertive"}=o;const i={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return Xe.createElement("div",{id:e,style:i,role:"status","aria-live":r,"aria-atomic":!0},n)}function VC(){const[o,e]=I.useState("");return{announce:I.useCallback(r=>{r!=null&&e(r)},[]),announcement:o}}const Sm=I.createContext(null);function FC(o){const e=I.useContext(Sm);I.useEffect(()=>{if(!e)throw new Error("useDndMonitor must be used within a children of ");return e(o)},[o,e])}function KC(){const[o]=I.useState(()=>new Set),e=I.useCallback(r=>(o.add(r),()=>o.delete(r)),[o]);return[I.useCallback(r=>{let{type:i,event:l}=r;o.forEach(u=>{var d;return(d=u[i])==null?void 0:d.call(u,l)})},[o]),e]}const WC={draggable:` + `,i.onmouseenter=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.2)"},i.onmouseleave=()=>{i.style.backgroundColor="rgba(0, 0, 0, 0.1)"};const l=()=>{a.parentNode&&a.parentNode.removeChild(a)};i.onclick=l;const u=d=>{this.isImeComposing(d)||d.key==="Escape"&&(l(),document.removeEventListener("keydown",u))};return document.addEventListener("keydown",u),a.onclick=d=>{d.target===a&&l()},r.appendChild(i),a.appendChild(r),document.body.appendChild(a),{overlay:a,layer:r,closeLayer:l}}getNestedProperty(e,a){return a.split(".").reduce((r,i)=>r&&r[i]!==void 0?r[i]:void 0,e)}async handleSaveToLocalStorage(e,a){const{key:r,value:i}=e;if(!r)return H.error("saveToLocalStorage: key parameter is required"),!1;try{const l=typeof i=="string"?i:JSON.stringify(i);return localStorage.setItem(r,l),H.log(`saveToLocalStorage: saved ${r}`,i),!0}catch(l){return H.error("saveToLocalStorage: failed to save",{key:r,error:l}),!1}}async handleLoadFromLocalStorage(e,a){const{key:r,target:i,stateKey:l,defaultValue:u}=e;if(!r)return H.error("loadFromLocalStorage: key parameter is required"),u;try{const d=localStorage.getItem(r);if(d===null)return H.log(`loadFromLocalStorage: no value found for ${r}, using default`),l&&a.setState&&a.setState({[l]:u}),u;let f;try{f=JSON.parse(d)}catch{f=d}return H.log(`loadFromLocalStorage: loaded ${r}`,f),l&&a.setState&&a.setState({[l]:f}),f}catch(d){return H.error("loadFromLocalStorage: failed to load",{key:r,error:d}),u}}async handleCustomAction(e,a){const r=this.customHandlers.get(e.handler);if(!r){if(this.previewMode){H.warn(`[Preview] Unknown action handler "${e.handler}" — skipped (preview mode)`);return}const i=new It(`Unknown action handler: ${e.handler}`,e);throw i.unknownHandler=!0,i}return await r(e,a)}resolveParams(e,a){if(!e)return{};const r={};for(const[i,l]of Object.entries(e))i.includes("{{")&&H.warn(`[resolveParams] setState params의 키에 표현식이 포함되어 있습니다: "${i}". 키는 해석되지 않습니다. 배열 항목 수정은 .map()/.filter() 패턴을 사용하세요.`),typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,a):typeof l=="string"?r[i]=l:Array.isArray(l)?r[i]=l.map(u=>typeof u=="string"&&u.includes("{{")?this.evaluateExpression(u,a):u):typeof l=="object"&&l!==null&&!Array.isArray(l)&&Object.getPrototypeOf(l)!==Object.prototype&&Object.getPrototypeOf(l)!==null?r[i]=l:typeof l=="object"&&l!==null?r[i]=this.resolveParams(l,a):r[i]=l;return r}resolveValue(e,a){return a?this.bindingEngine.resolveBindings(e,a,{skipCache:!0}):e}evaluateExpressions(e,a){let r={};for(const[i,l]of Object.entries(e))if(i==="..."){let u=l;typeof l=="string"&&l.includes("{{")&&(u=this.evaluateExpression(l,a)),u&&typeof u=="object"&&!Array.isArray(u)&&(r={...r,...u})}else typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,a):typeof l=="object"&&l!==null&&!Array.isArray(l)?r[i]=this.evaluateExpressions(l,a):r[i]=l;return r}evaluateExpressionsIfNeeded(e,a){const r={};for(const[i,l]of Object.entries(e))typeof l=="string"&&l.includes("{{")?r[i]=this.evaluateExpression(l,a):r[i]=l;return r}evaluateExpression(e,a){if(!a)return e;const r=e.match(/^\{\{(.+)\}\}$/);if(!(r&&!r[1].includes("}}")&&!r[1].includes("{{"))){let d=a;if(e.includes("_global")){const g=window.G7Core?.state?.get()?._global;g&&(d={...a,_global:g})}if(e.includes("_computed")||e.includes("$computed")){const g=window.__g7ActionContext?.computedRef?.current;g&&Object.keys(g).length>0&&(d={...d,_computed:g,$computed:g})}return this.bindingEngine.resolveBindings(e,d,{skipCache:!0})}let l=r[1].trim();l=l.replace(/\$args\.(\d+)/g,"$args[$1]");let u=a;if(l.includes("_global")){const f=window.G7Core?.state?.get()?._global;f&&(u={...a,_global:f})}if(l.includes("_computed")||l.includes("$computed")){const f=window.__g7ActionContext?.computedRef?.current;f&&Object.keys(f).length>0&&(u={...u,_computed:f,$computed:f})}l.includes("_global.modules")&&(H.log("[evaluateExpression] expression:",l),H.log("[evaluateExpression] dataContext._global:",u._global),H.log("[evaluateExpression] dataContext._global?.modules:",u._global?.modules));try{const d=this.bindingEngine.evaluateExpression(l,u);return l.includes("_global.modules")&&H.log("[evaluateExpression] result:",d),typeof d=="string"&&d.startsWith("$t:")&&this.translationEngine&&this.translationContext?this.translationEngine.resolveTranslations(d,this.translationContext,a):d}catch(d){return H.error("Expression evaluation failed:",e,d),e}}setDefaultContext(e){this.defaultContext={...this.defaultContext,...e}}setGlobalStateUpdater(e){this.globalStateUpdater=e}getGlobalStateUpdater(){return this.globalStateUpdater}async dispatchAction(e,a){const r={...this.defaultContext,...a,data:{...this.defaultContext.data,...a?.data}};if(e.confirm){let i=this.resolveValue(e.confirm,r.data);if(this.translationEngine&&this.translationContext&&i.startsWith("$t:")&&(i=this.translationEngine.resolveTranslations(i,this.translationContext,r.data)),!confirm(i))return{success:!1}}try{return await this.executeAction(e,r)}catch(i){if(H.error("dispatchAction failed:",i),e.onError){const l=i instanceof It&&i.originalError?{message:i.originalError.message,response:i.originalError.response}:{message:i instanceof Error?i.message:String(i)},u={...r,data:{...r.data,error:l}},d=Array.isArray(e.onError)?e.onError:[e.onError];for(const f of d)await this.executeAction(f,u)}return{success:!1,error:i instanceof Error?i:new Error(String(i))}}}registerHandler(e,a,r){this.customHandlers.set(e,a);const i=Un();i?.isEnabled()&&i.trackHandlerRegistration(e,r?.category??"custom",r?.description,r?.source)}unregisterHandler(e){this.customHandlers.delete(e);const a=Un();a?.isEnabled()&&a.trackHandlerUnregistration(e)}getRegisteredHandlers(){return Array.from(this.customHandlers.keys())}getHandler(e){return this.customHandlers.get(e)}normalizeDebounceConfig(e){return typeof e=="number"?{delay:e,leading:!1,trailing:!0}:{delay:e.delay,leading:e.leading??!1,trailing:e.trailing??!0}}extractEventData(e){const a=e.target;if(a&&!("tagName"in a)){const l={type:e.type??"custom",target:{value:a.value,name:a.name??""}};return e._changedKeys&&(l._changedKeys=e._changedKeys),l}const i=a?{value:a.value,name:a.name??"",checked:a.checked??!1,type:a.type??"",tagName:a.tagName}:{};if(e.type==="scroll"&&a){const l=a;i.scrollHeight=l.scrollHeight??0,i.scrollTop=l.scrollTop??0,i.clientHeight=l.clientHeight??0,i.scrollLeft=l.scrollLeft??0,i.clientWidth=l.clientWidth??0,i.scrollWidth=l.scrollWidth??0}return{type:e.type,target:a?i:null}}executeDebouncedAction(e,a,r,i,l){const u={...r,$event:a,$args:l},d={type:a.type,target:a.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(e,u,i)(d)}handleDebouncedAction(e,a,r,i,l,u){const d=this.normalizeDebounceConfig(e.debounce),f=l||`default-${e.handler}-${Date.now()}`,g=this.extractEventData(a),m=g._changedKeys;if(m&&Array.isArray(m)&&g?.target?.value&&typeof g.target.value=="object"&&!Array.isArray(g.target.value)){const E=this.debounceAccumulatedValues.get(f);if(E){const A={...E};for(const T of m)A[T]=g.target.value[T];g.target.value=A}this.debounceAccumulatedValues.set(f,g.target.value)}const b=this.debounceTimers.get(f);b&&(clearTimeout(b),this.debounceTimers.delete(f));const v=Un();v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"pending",params:e.params,debounce:{delay:d.delay,status:"pending",scheduledAt:Date.now()}});const S=!this.debounceTimers.has(f+"_leading");if(d.leading&&S&&(this.debounceTimers.set(f+"_leading",setTimeout(()=>{},0)),this.executeDebouncedAction(e,g,r,i,u),v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"leading"}})),d.trailing){const E=()=>{this.executeDebouncedAction(e,g,r,i,u),v?.isEnabled()&&v.trackAction({handler:typeof e.handler=="string"?e.handler:String(e.handler),type:e.type,status:"success",params:e.params,debounce:{delay:d.delay,status:"executed",executedAt:Date.now(),mode:"trailing"}})},A=setTimeout(()=>{this.debounceTimers.delete(f),this.debounceTimers.delete(f+"_leading"),this.pendingDebounceFlushers.delete(f),this.debounceAccumulatedValues.delete(f),E()},d.delay);this.debounceTimers.set(f,A),this.pendingDebounceFlushers.set(f,E)}}clearDebounceTimers(e){if(e)for(const[a,r]of this.debounceTimers)a.startsWith(e+"-")&&(clearTimeout(r),this.debounceTimers.delete(a),this.pendingDebounceFlushers.delete(a),this.debounceAccumulatedValues.delete(a));else{for(const a of this.debounceTimers.values())clearTimeout(a);this.debounceTimers.clear(),this.pendingDebounceFlushers.clear(),this.debounceAccumulatedValues.clear()}}debouncedCall(e,a,r){const i=this.debounceTimers.get(e);i&&(clearTimeout(i),this.debounceTimers.delete(e));const l=setTimeout(()=>{this.debounceTimers.delete(e),this.pendingDebounceFlushers.delete(e),r()},a);this.debounceTimers.set(e,l),this.pendingDebounceFlushers.set(e,r)}flushPendingDebounceTimers(){if(this.pendingDebounceFlushers.size===0)return;const e=new Map(this.pendingDebounceFlushers);for(const[a,r]of e){const i=this.debounceTimers.get(a);i&&(clearTimeout(i),this.debounceTimers.delete(a)),this.debounceTimers.delete(a+"_leading"),this.pendingDebounceFlushers.delete(a),r()}this.globalStateUpdater&&this.globalStateUpdater({})}bindActionsToProps(e,a,r){const i={...e};if(e.actions&&Array.isArray(e.actions)){const l=new Map;for(const m of e.actions){const b=this.resolveActionRef(m),v=b.event||this.getEventHandlerName(b.type);l.has(v)||l.set(v,[]),l.get(v).push(b)}const u=e.name||e.id||"unknown",d=l.has("onDrop"),f=l.has("onDragOver"),g=l.has("onDragEnter");d&&!f&&(i.onDragOver=m=>{m.preventDefault()}),d&&!g&&(i.onDragEnter=m=>{m.preventDefault()});for(const[m,b]of l)i[m]=(...v)=>{for(const S of b){const E=v[0],A=E&&typeof E=="object"&&"preventDefault"in E;if(A&&(m==="onDragOver"||m==="onDrop")&&E.preventDefault(),A&&S.key&&"key"in E){if(this.isImeComposing(E)){H.log("Key filter skipped (IME composing):",S.key);continue}if(E.key!==S.key){H.log("Key filter not matched:",S.key,"actual:",E.key);continue}H.log("Key filter matched:",S.key)}if(S.debounce){const T=`${u}-${S.handler}-${m}`,L=E&&typeof E=="object"&&!("preventDefault"in E)&&"target"in E&&E.target!==null;let M;if(A)M=E;else if(L){const D={type:"custom",target:E.target,preventDefault:()=>{},stopPropagation:()=>{}};E._changedKeys&&(D._changedKeys=E._changedKeys),M=D}else M=new Event("custom");const R=S.event?{...a,$args:v}:a;this.handleDebouncedAction(S,M,R,r,T,v)}else if(this.flushPendingDebounceTimers(),S.event){const T={...a,$args:v},L=A?E:new Event("custom");this.createHandler(S,T,r)(L)}else if(A)this.createHandler(S,a,r)(E);else if(E&&typeof E=="object"&&!("preventDefault"in E)&&"target"in E&&E.target!==null){const L={type:"custom",target:E.target,preventDefault:()=>{},stopPropagation:()=>{}};this.createHandler(S,a,r)(L)}}}}return i}getEventHandlerName(e){return{click:"onClick",change:"onChange",input:"onInput",submit:"onSubmit",focus:"onFocus",blur:"onBlur",keydown:"onKeyDown",keyup:"onKeyUp",keypress:"onKeyPress",mousedown:"onMouseDown",mouseup:"onMouseUp",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",scroll:"onScroll",dragstart:"onDragStart",drag:"onDrag",dragend:"onDragEnd",dragenter:"onDragEnter",dragover:"onDragOver",dragleave:"onDragLeave",drop:"onDrop"}[e]||`on${e.charAt(0).toUpperCase()}${e.slice(1)}`}sanitizeForDevTools(e,a=5){const r=new WeakSet,i=(l,u)=>{if(l==null||typeof l=="string"||typeof l=="number"||typeof l=="boolean")return l;if(u>a)return"[Max Depth Exceeded]";if(typeof l=="function")return`[Function: ${l.name||"anonymous"}]`;if(l instanceof Element)return`[Element: ${l.tagName}${l.className?"."+l.className.split(" ").join("."):""}]`;if(l instanceof Event)return`[Event: ${l.type}]`;if(Array.isArray(l)){if(r.has(l))return"[Circular Array]";r.add(l);const d=l.slice(0,10).map(f=>i(f,u+1));return l.length>10&&d.push(`... ${l.length-10} more items`),d}if(typeof l=="object"){if(r.has(l))return"[Circular Reference]";r.add(l);const d={},f=Object.keys(l),g=f.slice(0,20);for(const m of g)try{d[m]=i(l[m],u+1)}catch{d[m]="[Error accessing property]"}return f.length>20&&(d["..."]=`${f.length-20} more properties`),d}return String(l)};return i(e,0)}};$(ds,"loadedScripts",new Set);let lo=ds,Wl=null;function em(o){return Wl||(Wl=new lo(o)),Wl}function tC(o){Wl=o}const tm=ct("TransitionManager");class nC{constructor(){$(this,"isPending",!1);$(this,"subscribers",new Set)}setPending(e){this.isPending!==e&&(tm.log("setPending:",{from:this.isPending,to:e,stack:new Error().stack}),this.isPending=e,this.notifySubscribers())}getIsPending(){return this.isPending}subscribe(e){return this.subscribers.add(e),()=>{this.subscribers.delete(e)}}notifySubscribers(){tm.log("notifySubscribers:",{isPending:this.isPending,subscriberCount:this.subscribers.size}),this.subscribers.forEach(e=>{e(this.isPending)})}clearSubscribers(){this.subscribers.clear()}}const Ia=new nC,Yl=ct("TransitionContext"),nm=N.createContext({isTransitioning:!1}),Ld=({children:o})=>{const[e,a]=N.useState(()=>{const i=Ia.getIsPending();return Yl.log("Initial state:",i),i});N.useEffect(()=>{Yl.log("Subscribing to TransitionManager");const i=Ia.subscribe(l=>{Yl.log("Received isPending update:",l),a(l)});return()=>{Yl.log("Unsubscribing from TransitionManager"),i()}},[]);const r=N.useMemo(()=>({isTransitioning:e}),[e]);return ut.jsx(nm.Provider,{value:r,children:o})},am=()=>N.useContext(nm),Xl="__g7LocalInitTracking";function Md(){const o=globalThis;return o[Xl]||(o[Xl]={hash:"",timestamp:void 0,consumed:void 0}),o[Xl]}function aC(){const o=globalThis;o[Xl]={hash:"",timestamp:void 0,consumed:void 0}}function rC(o){Md().consumed=o}function iC(o){return Md().consumed===o}function sC(o,e){return typeof o=="number"&&typeof e=="number"?Math.max(o,e):e??o}function oC(o,e){if(e===void 0)return o;if(!(o!==null&&typeof o=="object"&&e!==null&&typeof e=="object"&&!iC(o)))return e;const{_forceLocalInit:r,...i}=o,{_forceLocalInit:l,...u}=e,d={...i,...u},f=sC(r,l);return f!==void 0&&(d._forceLocalInit=f),d}function lC(o){return o<768?"mobile":o<1024?"tablet":"desktop"}function rm(o){const e=lC(o);return{width:o,isMobile:e==="mobile",isTablet:e==="tablet",isDesktop:e==="desktop",matchedPreset:e}}const $d=N.createContext(rm(1024)),Jl=({children:o,overrideWidth:e})=>{const[a,r]=N.useState(()=>ui.getWidth());N.useEffect(()=>{const u=ui.subscribe(d=>{r(d)});return()=>{u()}},[]);const i=e??a,l=N.useMemo(()=>rm(i),[i]);return ut.jsx($d.Provider,{value:l,children:o})},Nd=()=>N.useContext($d);window.G7Core?.createLogger?.("Core:State");function cC(o,e,a,r){const{syncOnControlledChange:i=!0,isEqual:l}=r??{},u=o!==void 0;N.useRef(u);const[d,f]=N.useState(o!==void 0?o:e),g=d,m=N.useRef(o),b=N.useRef(!1);N.useEffect(()=>{if(b.current){m.current=o;return}i&&u&&o!==m.current&&(l?!l(d,o):d!==o)&&f(o),m.current=o},[o,u,i,l,d]);const v=N.useCallback(S=>{const E=typeof S=="function"?S(g):S;(l?l(g,E):g===E)||(b.current=!0,f(E),a?.(E),requestAnimationFrame(()=>{b.current=!1}))},[g,u,a,l]);return[g,v]}function uC(o,e){return o===e?!0:o.length!==e.length?!1:o.every((a,r)=>a===e[r])}function im(o,e){if(o===e)return!0;const a=Object.keys(o),r=Object.keys(e);return a.length!==r.length?!1:a.every(i=>o[i]===e[i])}const sm=N.createContext({}),dC=({children:o,value:e})=>ut.jsx(sm.Provider,{value:e,children:o}),fC=()=>N.useContext(sm),hC=(o,e)=>{if(!o)return;const a=e.split(".");let r=o;for(const i of a){if(r==null)return;r=r[i]}return r},gC=(o,e,a)=>{const r=e.split(".");if(r.length===1)return{...o,[r[0]]:a};const i={...o};let l=i;for(let u=0;u{as.warn("IsolatedStateContext not available: setState called outside IsolatedStateProvider")},getState:()=>{},mergeState:()=>{as.warn("IsolatedStateContext not available: mergeState called outside IsolatedStateProvider")},stateRef:{current:{}},scopeId:""},om=N.createContext(pC);let mC=0;function yC(){return`isolated-${++mC}-${Date.now().toString(36)}`}function lm(o,e){if(e==null)return o;if(typeof e!="object"||Array.isArray(e))return e;const a={...o};for(const r of Object.keys(e)){const i=e[r],l=o?.[r];i!==null&&typeof i=="object"&&!Array.isArray(i)&&l!==null&&typeof l=="object"&&!Array.isArray(l)?a[r]=lm(l,i):a[r]=i}return a}function bC(o,e){return e?e.split(".").reduce((a,r)=>a?.[r],o):o}function vC(o,e,a){const r=e.split("."),i={...o};let l=i;for(let u=0;u{const i=N.useRef(e||yC()).current,[l,u]=N.useState(o),d=N.useRef(l);d.current=l;const f=N.useCallback(S=>bC(d.current,S),[]),g=N.useCallback((S,E)=>{u(A=>{const T=vC(A,S,E);return as.log(`[${i}] setState: ${S} =`,E),T})},[i]),m=N.useCallback((S,E="deep")=>{u(A=>{let T;return E==="replace"?T=S:E==="shallow"?T={...A,...S}:T=lm(A,S),as.log(`[${i}] mergeState (${E}):`,S),T})},[i]),b=N.useMemo(()=>({state:l,setState:g,getState:f,mergeState:m,stateRef:d,scopeId:i}),[l,g,f,m,i]),v=N.useMemo(()=>({state:d.current,setState:g,getState:f,mergeState:m}),[g,f,m]);return N.useEffect(()=>{const S=window.G7Core,E=window.__g7IsolatedStates||{};return window.__g7IsolatedStates||(window.__g7IsolatedStates=E),E[i]=v,S&&(S._isolatedStates||(S._isolatedStates={}),S._isolatedStates[i]=l),S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:created",{id:i,state:l}),as.log(`[${i}] IsolatedStateProvider mounted with initial state:`,l),()=>{delete E[i],S?._isolatedStates&&delete S._isolatedStates[i],S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:destroyed",{id:i}),as.log(`[${i}] IsolatedStateProvider unmounted`)}},[i,v]),N.useEffect(()=>{const S=window.G7Core,E=window.__g7IsolatedStates;E?.[i]&&(E[i].state=l),S?._isolatedStates&&(S._isolatedStates[i]=l),S?.devTools?.isEnabled?.()&&S.devTools.emit?.("isolated:updated",{id:i,state:l})},[l,i]),ut.jsx(om.Provider,{value:b,children:a})},wC=()=>{const o=N.useContext(om);return o.scopeId?o:null};var Ar=Ug();function CC(){for(var o=arguments.length,e=new Array(o),a=0;ar=>{e.forEach(i=>i(r))},e)}const Ql=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function rs(o){const e=Object.prototype.toString.call(o);return e==="[object Window]"||e==="[object global]"}function Id(o){return"nodeType"in o}function Tn(o){var e,a;return o?rs(o)?o:Id(o)&&(e=(a=o.ownerDocument)==null?void 0:a.defaultView)!=null?e:window:window}function Hd(o){const{Document:e}=Tn(o);return o instanceof e}function co(o){return rs(o)?!1:o instanceof Tn(o).HTMLElement}function cm(o){return o instanceof Tn(o).SVGElement}function is(o){return o?rs(o)?o.document:Id(o)?Hd(o)?o:co(o)||cm(o)?o.ownerDocument:document:document:document}const Ha=Ql?N.useLayoutEffect:N.useEffect;function zd(o){const e=N.useRef(o);return Ha(()=>{e.current=o}),N.useCallback(function(){for(var a=arguments.length,r=new Array(a),i=0;i{o.current=setInterval(r,i)},[]),a=N.useCallback(()=>{o.current!==null&&(clearInterval(o.current),o.current=null)},[]);return[e,a]}function uo(o,e){e===void 0&&(e=[o]);const a=N.useRef(o);return Ha(()=>{a.current!==o&&(a.current=o)},e),a}function fo(o,e){const a=N.useRef();return N.useMemo(()=>{const r=o(a.current);return a.current=r,r},[...e])}function Zl(o){const e=zd(o),a=N.useRef(null),r=N.useCallback(i=>{i!==a.current&&e?.(i,a.current),a.current=i},[]);return[a,r]}function jd(o){const e=N.useRef();return N.useEffect(()=>{e.current=o},[o]),e.current}let Ud={};function ho(o,e){return N.useMemo(()=>{if(e)return e;const a=Ud[o]==null?0:Ud[o]+1;return Ud[o]=a,o+"-"+a},[o,e])}function um(o){return function(e){for(var a=arguments.length,r=new Array(a>1?a-1:0),i=1;i{const d=Object.entries(u);for(const[f,g]of d){const m=l[f];m!=null&&(l[f]=m+o*g)}return l},{...e})}}const ss=um(1),go=um(-1);function _C(o){return"clientX"in o&&"clientY"in o}function Bd(o){if(!o)return!1;const{KeyboardEvent:e}=Tn(o.target);return e&&o instanceof e}function AC(o){if(!o)return!1;const{TouchEvent:e}=Tn(o.target);return e&&o instanceof e}function Pd(o){if(AC(o)){if(o.touches&&o.touches.length){const{clientX:e,clientY:a}=o.touches[0];return{x:e,y:a}}else if(o.changedTouches&&o.changedTouches.length){const{clientX:e,clientY:a}=o.changedTouches[0];return{x:e,y:a}}}return _C(o)?{x:o.clientX,y:o.clientY}:null}const po=Object.freeze({Translate:{toString(o){if(!o)return;const{x:e,y:a}=o;return"translate3d("+(e?Math.round(e):0)+"px, "+(a?Math.round(a):0)+"px, 0)"}},Scale:{toString(o){if(!o)return;const{scaleX:e,scaleY:a}=o;return"scaleX("+e+") scaleY("+a+")"}},Transform:{toString(o){if(o)return[po.Translate.toString(o),po.Scale.toString(o)].join(" ")}},Transition:{toString(o){let{property:e,duration:a,easing:r}=o;return e+" "+a+"ms "+r}}}),dm="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function xC(o){return o.matches(dm)?o:o.querySelector(dm)}const TC={display:"none"};function RC(o){let{id:e,value:a}=o;return We.createElement("div",{id:e,style:TC},a)}function kC(o){let{id:e,announcement:a,ariaLiveType:r="assertive"}=o;const i={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return We.createElement("div",{id:e,style:i,role:"status","aria-live":r,"aria-atomic":!0},a)}function DC(){const[o,e]=N.useState("");return{announce:N.useCallback(r=>{r!=null&&e(r)},[]),announcement:o}}const fm=N.createContext(null);function OC(o){const e=N.useContext(fm);N.useEffect(()=>{if(!e)throw new Error("useDndMonitor must be used within a children of ");return e(o)},[o,e])}function LC(){const[o]=N.useState(()=>new Set),e=N.useCallback(r=>(o.add(r),()=>o.delete(r)),[o]);return[N.useCallback(r=>{let{type:i,event:l}=r;o.forEach(u=>{var d;return(d=u[i])==null?void 0:d.call(u,l)})},[o]),e]}const MC={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},YC={onDragStart(o){let{active:e}=o;return"Picked up draggable item "+e.id+"."},onDragOver(o){let{active:e,over:n}=o;return n?"Draggable item "+e.id+" was moved over droppable area "+n.id+".":"Draggable item "+e.id+" is no longer over a droppable area."},onDragEnd(o){let{active:e,over:n}=o;return n?"Draggable item "+e.id+" was dropped over droppable area "+n.id:"Draggable item "+e.id+" was dropped."},onDragCancel(o){let{active:e}=o;return"Dragging was cancelled. Draggable item "+e.id+" was dropped."}};function XC(o){let{announcements:e=YC,container:n,hiddenTextDescribedById:r,screenReaderInstructions:i=WC}=o;const{announce:l,announcement:u}=VC(),d=mo("DndLiveRegion"),[f,g]=I.useState(!1);if(I.useEffect(()=>{g(!0)},[]),FC(I.useMemo(()=>({onDragStart(b){let{active:v}=b;l(e.onDragStart({active:v}))},onDragMove(b){let{active:v,over:S}=b;e.onDragMove&&l(e.onDragMove({active:v,over:S}))},onDragOver(b){let{active:v,over:S}=b;l(e.onDragOver({active:v,over:S}))},onDragEnd(b){let{active:v,over:S}=b;l(e.onDragEnd({active:v,over:S}))},onDragCancel(b){let{active:v,over:S}=b;l(e.onDragCancel({active:v,over:S}))}}),[l,e])),!f)return null;const m=Xe.createElement(Xe.Fragment,null,Xe.createElement(qC,{id:r,value:i.draggable}),Xe.createElement(GC,{id:d,announcement:u}));return n?Ar.createPortal(m,n):m}var rn;(function(o){o.DragStart="dragStart",o.DragMove="dragMove",o.DragEnd="dragEnd",o.DragCancel="dragCancel",o.DragOver="dragOver",o.RegisterDroppable="registerDroppable",o.SetDroppableDisabled="setDroppableDisabled",o.UnregisterDroppable="unregisterDroppable"})(rn||(rn={}));function ac(){}function wm(o,e){return I.useMemo(()=>({sensor:o,options:e??{}}),[o,e])}function JC(){for(var o=arguments.length,e=new Array(o),n=0;n[...e].filter(r=>r!=null),[...e])}const ya=Object.freeze({x:0,y:0});function Cm(o,e){return Math.sqrt(Math.pow(o.x-e.x,2)+Math.pow(o.y-e.y,2))}function Em(o,e){let{data:{value:n}}=o,{data:{value:r}}=e;return n-r}function QC(o,e){let{data:{value:n}}=o,{data:{value:r}}=e;return r-n}function _m(o){let{left:e,top:n,height:r,width:i}=o;return[{x:e,y:n},{x:e+i,y:n},{x:e,y:n+r},{x:e+i,y:n+r}]}function Am(o,e){if(!o||o.length===0)return null;const[n]=o;return n[e]}function xm(o,e,n){return e===void 0&&(e=o.left),n===void 0&&(n=o.top),{x:e+o.width*.5,y:n+o.height*.5}}const ZC=o=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=o;const i=xm(e,e.left,e.top),l=[];for(const u of r){const{id:d}=u,f=n.get(d);if(f){const g=Cm(xm(f),i);l.push({id:d,data:{droppableContainer:u,value:g}})}}return l.sort(Em)},eE=o=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=o;const i=_m(e),l=[];for(const u of r){const{id:d}=u,f=n.get(d);if(f){const g=_m(f),m=i.reduce((v,S,_)=>v+Cm(g[_],S),0),b=Number((m/4).toFixed(4));l.push({id:d,data:{droppableContainer:u,value:b}})}}return l.sort(Em)};function tE(o,e){const n=Math.max(e.top,o.top),r=Math.max(e.left,o.left),i=Math.min(e.left+e.width,o.left+o.width),l=Math.min(e.top+e.height,o.top+o.height),u=i-r,d=l-n;if(r{let{collisionRect:e,droppableRects:n,droppableContainers:r}=o;const i=[];for(const l of r){const{id:u}=l,d=n.get(u);if(d){const f=tE(d,e);f>0&&i.push({id:u,data:{droppableContainer:l,value:f}})}}return i.sort(QC)};function aE(o,e,n){return{...o,scaleX:e&&n?e.width/n.width:1,scaleY:e&&n?e.height/n.height:1}}function Tm(o,e){return o&&e?{x:o.left-e.left,y:o.top-e.top}:ya}function rE(o){return function(n){for(var r=arguments.length,i=new Array(r>1?r-1:0),l=1;l({...u,top:u.top+o*d.y,bottom:u.bottom+o*d.y,left:u.left+o*d.x,right:u.right+o*d.x}),{...n})}}const iE=rE(1);function sE(o){if(o.startsWith("matrix3d(")){const e=o.slice(9,-1).split(/, /);return{x:+e[12],y:+e[13],scaleX:+e[0],scaleY:+e[5]}}else if(o.startsWith("matrix(")){const e=o.slice(7,-1).split(/, /);return{x:+e[4],y:+e[5],scaleX:+e[0],scaleY:+e[3]}}return null}function oE(o,e,n){const r=sE(e);if(!r)return o;const{scaleX:i,scaleY:l,x:u,y:d}=r,f=o.left-u-(1-i)*parseFloat(n),g=o.top-d-(1-l)*parseFloat(n.slice(n.indexOf(" ")+1)),m=i?o.width/i:o.width,b=l?o.height/l:o.height;return{width:m,height:b,top:g,right:f+m,bottom:g+b,left:f}}const lE={ignoreTransform:!1};function us(o,e){e===void 0&&(e=lE);let n=o.getBoundingClientRect();if(e.ignoreTransform){const{transform:g,transformOrigin:m}=Mn(o).getComputedStyle(o);g&&(n=oE(n,g,m))}const{top:r,left:i,width:l,height:u,bottom:d,right:f}=n;return{top:r,left:i,width:l,height:u,bottom:d,right:f}}function Rm(o){return us(o,{ignoreTransform:!0})}function cE(o){const e=o.innerWidth,n=o.innerHeight;return{top:0,left:0,right:e,bottom:n,width:e,height:n}}function uE(o,e){return e===void 0&&(e=Mn(o).getComputedStyle(o)),e.position==="fixed"}function dE(o,e){e===void 0&&(e=Mn(o).getComputedStyle(o));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(i=>{const l=e[i];return typeof l=="string"?n.test(l):!1})}function rc(o,e){const n=[];function r(i){if(e!=null&&n.length>=e||!i)return n;if(Ud(i)&&i.scrollingElement!=null&&!n.includes(i.scrollingElement))return n.push(i.scrollingElement),n;if(!ho(i)||ym(i)||n.includes(i))return n;const l=Mn(o).getComputedStyle(i);return i!==o&&dE(i,l)&&n.push(i),uE(i,l)?n:r(i.parentNode)}return o?r(o):n}function km(o){const[e]=rc(o,1);return e??null}function Fd(o){return!tc||!o?null:os(o)?o:jd(o)?Ud(o)||o===ls(o).scrollingElement?window:ho(o)?o:null:null}function Dm(o){return os(o)?o.scrollX:o.scrollLeft}function Om(o){return os(o)?o.scrollY:o.scrollTop}function Kd(o){return{x:Dm(o),y:Om(o)}}var fn;(function(o){o[o.Forward=1]="Forward",o[o.Backward=-1]="Backward"})(fn||(fn={}));function Lm(o){return!tc||!o?!1:o===document.scrollingElement}function Mm(o){const e={x:0,y:0},n=Lm(o)?{height:window.innerHeight,width:window.innerWidth}:{height:o.clientHeight,width:o.clientWidth},r={x:o.scrollWidth-n.width,y:o.scrollHeight-n.height},i=o.scrollTop<=e.y,l=o.scrollLeft<=e.x,u=o.scrollTop>=r.y,d=o.scrollLeft>=r.x;return{isTop:i,isLeft:l,isBottom:u,isRight:d,maxScroll:r,minScroll:e}}const fE={x:.2,y:.2};function hE(o,e,n,r,i){let{top:l,left:u,right:d,bottom:f}=n;r===void 0&&(r=10),i===void 0&&(i=fE);const{isTop:g,isBottom:m,isLeft:b,isRight:v}=Mm(o),S={x:0,y:0},_={x:0,y:0},x={height:e.height*i.y,width:e.width*i.x};return!g&&l<=e.top+x.height?(S.y=fn.Backward,_.y=r*Math.abs((e.top+x.height-l)/x.height)):!m&&f>=e.bottom-x.height&&(S.y=fn.Forward,_.y=r*Math.abs((e.bottom-x.height-f)/x.height)),!v&&d>=e.right-x.width?(S.x=fn.Forward,_.x=r*Math.abs((e.right-x.width-d)/x.width)):!b&&u<=e.left+x.width&&(S.x=fn.Backward,_.x=r*Math.abs((e.left+x.width-u)/x.width)),{direction:S,speed:_}}function gE(o){if(o===document.scrollingElement){const{innerWidth:l,innerHeight:u}=window;return{top:0,left:0,right:l,bottom:u,width:l,height:u}}const{top:e,left:n,right:r,bottom:i}=o.getBoundingClientRect();return{top:e,left:n,right:r,bottom:i,width:o.clientWidth,height:o.clientHeight}}function $m(o){return o.reduce((e,n)=>cs(e,Kd(n)),ya)}function pE(o){return o.reduce((e,n)=>e+Dm(n),0)}function mE(o){return o.reduce((e,n)=>e+Om(n),0)}function yE(o,e){if(e===void 0&&(e=us),!o)return;const{top:n,left:r,bottom:i,right:l}=e(o);km(o)&&(i<=0||l<=0||n>=window.innerHeight||r>=window.innerWidth)&&o.scrollIntoView({block:"center",inline:"center"})}const bE=[["x",["left","right"],pE],["y",["top","bottom"],mE]];class Wd{constructor(e,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const r=rc(n),i=$m(r);this.rect={...e},this.width=e.width,this.height=e.height;for(const[l,u,d]of bE)for(const f of u)Object.defineProperty(this,f,{get:()=>{const g=d(r),m=i[l]-g;return this.rect[f]+m},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class vo{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var r;return(r=this.target)==null?void 0:r.removeEventListener(...n)})},this.target=e}add(e,n,r){var i;(i=this.target)==null||i.addEventListener(e,n,r),this.listeners.push([e,n,r])}}function vE(o){const{EventTarget:e}=Mn(o);return o instanceof e?o:ls(o)}function Yd(o,e){const n=Math.abs(o.x),r=Math.abs(o.y);return typeof e=="number"?Math.sqrt(n**2+r**2)>e:"x"in e&&"y"in e?n>e.x&&r>e.y:"x"in e?n>e.x:"y"in e?r>e.y:!1}var ea;(function(o){o.Click="click",o.DragStart="dragstart",o.Keydown="keydown",o.ContextMenu="contextmenu",o.Resize="resize",o.SelectionChange="selectionchange",o.VisibilityChange="visibilitychange"})(ea||(ea={}));function Nm(o){o.preventDefault()}function SE(o){o.stopPropagation()}var st;(function(o){o.Space="Space",o.Down="ArrowDown",o.Right="ArrowRight",o.Left="ArrowLeft",o.Up="ArrowUp",o.Esc="Escape",o.Enter="Enter",o.Tab="Tab"})(st||(st={}));const Im={start:[st.Space,st.Enter],cancel:[st.Esc],end:[st.Space,st.Enter,st.Tab]},wE=(o,e)=>{let{currentCoordinates:n}=e;switch(o.code){case st.Right:return{...n,x:n.x+25};case st.Left:return{...n,x:n.x-25};case st.Down:return{...n,y:n.y+25};case st.Up:return{...n,y:n.y-25}}};class Xd{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:n}}=e;this.props=e,this.listeners=new vo(ls(n)),this.windowListeners=new vo(Mn(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(ea.Resize,this.handleCancel),this.windowListeners.add(ea.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(ea.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:n}=this.props,r=e.node.current;r&&yE(r),n(ya)}handleKeyDown(e){if(Gd(e)){const{active:n,context:r,options:i}=this.props,{keyboardCodes:l=Im,coordinateGetter:u=wE,scrollBehavior:d="smooth"}=i,{code:f}=e;if(l.end.includes(f)){this.handleEnd(e);return}if(l.cancel.includes(f)){this.handleCancel(e);return}const{collisionRect:g}=r.current,m=g?{x:g.left,y:g.top}:ya;this.referenceCoordinates||(this.referenceCoordinates=m);const b=u(e,{active:n,context:r.current,currentCoordinates:m});if(b){const v=yo(b,m),S={x:0,y:0},{scrollableAncestors:_}=r.current;for(const x of _){const k=e.code,{isTop:O,isRight:M,isLeft:T,isBottom:D,maxScroll:j,minScroll:H}=Mm(x),P=gE(x),X={x:Math.min(k===st.Right?P.right-P.width/2:P.right,Math.max(k===st.Right?P.left:P.left+P.width/2,b.x)),y:Math.min(k===st.Down?P.bottom-P.height/2:P.bottom,Math.max(k===st.Down?P.top:P.top+P.height/2,b.y))},pe=k===st.Right&&!M||k===st.Left&&!T,_e=k===st.Down&&!D||k===st.Up&&!O;if(pe&&X.x!==b.x){const we=x.scrollLeft+v.x,Me=k===st.Right&&we<=j.x||k===st.Left&&we>=H.x;if(Me&&!v.y){x.scrollTo({left:we,behavior:d});return}Me?S.x=x.scrollLeft-we:S.x=k===st.Right?x.scrollLeft-j.x:x.scrollLeft-H.x,S.x&&x.scrollBy({left:-S.x,behavior:d});break}else if(_e&&X.y!==b.y){const we=x.scrollTop+v.y,Me=k===st.Down&&we<=j.y||k===st.Up&&we>=H.y;if(Me&&!v.x){x.scrollTo({top:we,behavior:d});return}Me?S.y=x.scrollTop-we:S.y=k===st.Down?x.scrollTop-j.y:x.scrollTop-H.y,S.y&&x.scrollBy({top:-S.y,behavior:d});break}}this.handleMove(e,cs(yo(b,this.referenceCoordinates),S))}}}handleMove(e,n){const{onMove:r}=this.props;e.preventDefault(),r(n)}handleEnd(e){const{onEnd:n}=this.props;e.preventDefault(),this.detach(),n()}handleCancel(e){const{onCancel:n}=this.props;e.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}Xd.activators=[{eventName:"onKeyDown",handler:(o,e,n)=>{let{keyboardCodes:r=Im,onActivation:i}=e,{active:l}=n;const{code:u}=o.nativeEvent;if(r.start.includes(u)){const d=l.activatorNode.current;return d&&o.target!==d?!1:(o.preventDefault(),i?.({event:o.nativeEvent}),!0)}return!1}}];function Hm(o){return!!(o&&"distance"in o)}function zm(o){return!!(o&&"delay"in o)}class Jd{constructor(e,n,r){var i;r===void 0&&(r=vE(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=n;const{event:l}=e,{target:u}=l;this.props=e,this.events=n,this.document=ls(u),this.documentListeners=new vo(this.document),this.listeners=new vo(r),this.windowListeners=new vo(Mn(u)),this.initialCoordinates=(i=Vd(l))!=null?i:ya,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:n,bypassActivationConstraint:r}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(ea.Resize,this.handleCancel),this.windowListeners.add(ea.DragStart,Nm),this.windowListeners.add(ea.VisibilityChange,this.handleCancel),this.windowListeners.add(ea.ContextMenu,Nm),this.documentListeners.add(ea.Keydown,this.handleKeydown),n){if(r!=null&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(zm(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(Hm(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,n){const{active:r,onPending:i}=this.props;i(r,e,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:e}=this,{onStart:n}=this.props;e&&(this.activated=!0,this.documentListeners.add(ea.Click,SE,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(ea.SelectionChange,this.removeTextSelection),n(e))}handleMove(e){var n;const{activated:r,initialCoordinates:i,props:l}=this,{onMove:u,options:{activationConstraint:d}}=l;if(!i)return;const f=(n=Vd(e))!=null?n:ya,g=yo(i,f);if(!r&&d){if(Hm(d)){if(d.tolerance!=null&&Yd(g,d.tolerance))return this.handleCancel();if(Yd(g,d.distance))return this.handleStart()}if(zm(d)&&Yd(g,d.tolerance))return this.handleCancel();this.handlePending(d,g);return}e.cancelable&&e.preventDefault(),u(f)}handleEnd(){const{onAbort:e,onEnd:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleCancel(){const{onAbort:e,onCancel:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleKeydown(e){e.code===st.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}}const CE={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Qd extends Jd{constructor(e){const{event:n}=e,r=ls(n.target);super(e,CE,r)}}Qd.activators=[{eventName:"onPointerDown",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:r}=e;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];const EE={move:{name:"mousemove"},end:{name:"mouseup"}};var Zd;(function(o){o[o.RightClick=2]="RightClick"})(Zd||(Zd={}));class _E extends Jd{constructor(e){super(e,EE,ls(e.event.target))}}_E.activators=[{eventName:"onMouseDown",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:r}=e;return n.button===Zd.RightClick?!1:(r?.({event:n}),!0)}}];const ef={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class AE extends Jd{constructor(e){super(e,ef)}static setup(){return window.addEventListener(ef.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(ef.move.name,e)};function e(){}}}AE.activators=[{eventName:"onTouchStart",handler:(o,e)=>{let{nativeEvent:n}=o,{onActivation:r}=e;const{touches:i}=n;return i.length>1?!1:(r?.({event:n}),!0)}}];var So;(function(o){o[o.Pointer=0]="Pointer",o[o.DraggableRect=1]="DraggableRect"})(So||(So={}));var ic;(function(o){o[o.TreeOrder=0]="TreeOrder",o[o.ReversedTreeOrder=1]="ReversedTreeOrder"})(ic||(ic={}));function xE(o){let{acceleration:e,activator:n=So.Pointer,canScroll:r,draggingRect:i,enabled:l,interval:u=5,order:d=ic.TreeOrder,pointerCoordinates:f,scrollableAncestors:g,scrollableAncestorRects:m,delta:b,threshold:v}=o;const S=RE({delta:b,disabled:!l}),[_,x]=zC(),k=I.useRef({x:0,y:0}),O=I.useRef({x:0,y:0}),M=I.useMemo(()=>{switch(n){case So.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case So.DraggableRect:return i}},[n,i,f]),T=I.useRef(null),D=I.useCallback(()=>{const H=T.current;if(!H)return;const P=k.current.x*O.current.x,X=k.current.y*O.current.y;H.scrollBy(P,X)},[]),j=I.useMemo(()=>d===ic.TreeOrder?[...g].reverse():g,[d,g]);I.useEffect(()=>{if(!l||!g.length||!M){x();return}for(const H of j){if(r?.(H)===!1)continue;const P=g.indexOf(H),X=m[P];if(!X)continue;const{direction:pe,speed:_e}=hE(H,X,M,e,v);for(const we of["x","y"])S[we][pe[we]]||(_e[we]=0,pe[we]=0);if(_e.x>0||_e.y>0){x(),T.current=H,_(D,u),k.current=_e,O.current=pe;return}}k.current={x:0,y:0},O.current={x:0,y:0},x()},[e,D,r,x,l,u,JSON.stringify(M),JSON.stringify(S),_,g,j,m,JSON.stringify(v)])}const TE={x:{[fn.Backward]:!1,[fn.Forward]:!1},y:{[fn.Backward]:!1,[fn.Forward]:!1}};function RE(o){let{delta:e,disabled:n}=o;const r=Pd(e);return po(i=>{if(n||!r||!i)return TE;const l={x:Math.sign(e.x-r.x),y:Math.sign(e.y-r.y)};return{x:{[fn.Backward]:i.x[fn.Backward]||l.x===-1,[fn.Forward]:i.x[fn.Forward]||l.x===1},y:{[fn.Backward]:i.y[fn.Backward]||l.y===-1,[fn.Forward]:i.y[fn.Forward]||l.y===1}}},[n,e,r])}function kE(o,e){const n=e!=null?o.get(e):void 0,r=n?n.node.current:null;return po(i=>{var l;return e==null?null:(l=r??i)!=null?l:null},[r,e])}function DE(o,e){return I.useMemo(()=>o.reduce((n,r)=>{const{sensor:i}=r,l=i.activators.map(u=>({eventName:u.eventName,handler:e(u.handler,r)}));return[...n,...l]},[]),[o,e])}var wo;(function(o){o[o.Always=0]="Always",o[o.BeforeDragging=1]="BeforeDragging",o[o.WhileDragging=2]="WhileDragging"})(wo||(wo={}));var tf;(function(o){o.Optimized="optimized"})(tf||(tf={}));const jm=new Map;function OE(o,e){let{dragging:n,dependencies:r,config:i}=e;const[l,u]=I.useState(null),{frequency:d,measure:f,strategy:g}=i,m=I.useRef(o),b=k(),v=go(b),S=I.useCallback(function(O){O===void 0&&(O=[]),!v.current&&u(M=>M===null?O:M.concat(O.filter(T=>!M.includes(T))))},[v]),_=I.useRef(null),x=po(O=>{if(b&&!n)return jm;if(!O||O===jm||m.current!==o||l!=null){const M=new Map;for(let T of o){if(!T)continue;if(l&&l.length>0&&!l.includes(T.id)&&T.rect.current){M.set(T.id,T.rect.current);continue}const D=T.node.current,j=D?new Wd(f(D),D):null;T.rect.current=j,j&&M.set(T.id,j)}return M}return O},[o,l,n,b,f]);return I.useEffect(()=>{m.current=o},[o]),I.useEffect(()=>{b||S()},[n,b]),I.useEffect(()=>{l&&l.length>0&&u(null)},[JSON.stringify(l)]),I.useEffect(()=>{b||typeof d!="number"||_.current!==null||(_.current=setTimeout(()=>{S(),_.current=null},d))},[d,b,S,...r]),{droppableRects:x,measureDroppableContainers:S,measuringScheduled:l!=null};function k(){switch(g){case wo.Always:return!1;case wo.BeforeDragging:return n;default:return!n}}}function Um(o,e){return po(n=>o?n||(typeof e=="function"?e(o):o):null,[e,o])}function LE(o,e){return Um(o,e)}function ME(o){let{callback:e,disabled:n}=o;const r=Bd(e),i=I.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:l}=window;return new l(r)},[r,n]);return I.useEffect(()=>()=>i?.disconnect(),[i]),i}function sc(o){let{callback:e,disabled:n}=o;const r=Bd(e),i=I.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:l}=window;return new l(r)},[n]);return I.useEffect(()=>()=>i?.disconnect(),[i]),i}function $E(o){return new Wd(us(o),o)}function Bm(o,e,n){e===void 0&&(e=$E);const[r,i]=I.useState(null);function l(){i(f=>{if(!o)return null;if(o.isConnected===!1){var g;return(g=f??n)!=null?g:null}const m=e(o);return JSON.stringify(f)===JSON.stringify(m)?f:m})}const u=ME({callback(f){if(o)for(const g of f){const{type:m,target:b}=g;if(m==="childList"&&b instanceof HTMLElement&&b.contains(o)){l();break}}}}),d=sc({callback:l});return za(()=>{l(),o?(d?.observe(o),u?.observe(document.body,{childList:!0,subtree:!0})):(d?.disconnect(),u?.disconnect())},[o]),r}function NE(o){const e=Um(o);return Tm(o,e)}const Pm=[];function IE(o){const e=I.useRef(o),n=po(r=>o?r&&r!==Pm&&o&&e.current&&o.parentNode===e.current.parentNode?r:rc(o):Pm,[o]);return I.useEffect(()=>{e.current=o},[o]),n}function HE(o){const[e,n]=I.useState(null),r=I.useRef(o),i=I.useCallback(l=>{const u=Fd(l.target);u&&n(d=>d?(d.set(u,Kd(u)),new Map(d)):null)},[]);return I.useEffect(()=>{const l=r.current;if(o!==l){u(l);const d=o.map(f=>{const g=Fd(f);return g?(g.addEventListener("scroll",i,{passive:!0}),[g,Kd(g)]):null}).filter(f=>f!=null);n(d.length?new Map(d):null),r.current=o}return()=>{u(o),u(l)};function u(d){d.forEach(f=>{const g=Fd(f);g?.removeEventListener("scroll",i)})}},[i,o]),I.useMemo(()=>o.length?e?Array.from(e.values()).reduce((l,u)=>cs(l,u),ya):$m(o):ya,[o,e])}function qm(o,e){e===void 0&&(e=[]);const n=I.useRef(null);return I.useEffect(()=>{n.current=null},e),I.useEffect(()=>{const r=o!==ya;r&&!n.current&&(n.current=o),!r&&n.current&&(n.current=null)},[o]),n.current?yo(o,n.current):ya}function zE(o){I.useEffect(()=>{if(!tc)return;const e=o.map(n=>{let{sensor:r}=n;return r.setup==null?void 0:r.setup()});return()=>{for(const n of e)n?.()}},o.map(e=>{let{sensor:n}=e;return n}))}function jE(o,e){return I.useMemo(()=>o.reduce((n,r)=>{let{eventName:i,handler:l}=r;return n[i]=u=>{l(u,e)},n},{}),[o,e])}function Gm(o){return I.useMemo(()=>o?cE(o):null,[o])}const Vm=[];function UE(o,e){e===void 0&&(e=us);const[n]=o,r=Gm(n?Mn(n):null),[i,l]=I.useState(Vm);function u(){l(()=>o.length?o.map(f=>Lm(f)?r:new Wd(e(f),f)):Vm)}const d=sc({callback:u});return za(()=>{d?.disconnect(),u(),o.forEach(f=>d?.observe(f))},[o]),i}function BE(o){if(!o)return null;if(o.children.length>1)return o;const e=o.children[0];return ho(e)?e:o}function PE(o){let{measure:e}=o;const[n,r]=I.useState(null),i=I.useCallback(g=>{for(const{target:m}of g)if(ho(m)){r(b=>{const v=e(m);return b?{...b,width:v.width,height:v.height}:v});break}},[e]),l=sc({callback:i}),u=I.useCallback(g=>{const m=BE(g);l?.disconnect(),m&&l?.observe(m),r(m?e(m):null)},[e,l]),[d,f]=nc(u);return I.useMemo(()=>({nodeRef:d,rect:n,setRef:f}),[n,d,f])}const qE=[{sensor:Qd,options:{}},{sensor:Xd,options:{}}],GE={current:{}},oc={draggable:{measure:Rm},droppable:{measure:Rm,strategy:wo.WhileDragging,frequency:tf.Optimized},dragOverlay:{measure:us}};class Co extends Map{get(e){var n;return e!=null&&(n=super.get(e))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:n}=e;return!n})}getNodeFor(e){var n,r;return(n=(r=this.get(e))==null?void 0:r.node.current)!=null?n:void 0}}const VE={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Co,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:ac},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:oc,measureDroppableContainers:ac,windowRect:null,measuringScheduled:!1},FE={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:ac,draggableNodes:new Map,over:null,measureDroppableContainers:ac},lc=I.createContext(FE),Fm=I.createContext(VE);function KE(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Co}}}function WE(o,e){switch(e.type){case rn.DragStart:return{...o,draggable:{...o.draggable,initialCoordinates:e.initialCoordinates,active:e.active}};case rn.DragMove:return o.draggable.active==null?o:{...o,draggable:{...o.draggable,translate:{x:e.coordinates.x-o.draggable.initialCoordinates.x,y:e.coordinates.y-o.draggable.initialCoordinates.y}}};case rn.DragEnd:case rn.DragCancel:return{...o,draggable:{...o.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case rn.RegisterDroppable:{const{element:n}=e,{id:r}=n,i=new Co(o.droppable.containers);return i.set(r,n),{...o,droppable:{...o.droppable,containers:i}}}case rn.SetDroppableDisabled:{const{id:n,key:r,disabled:i}=e,l=o.droppable.containers.get(n);if(!l||r!==l.key)return o;const u=new Co(o.droppable.containers);return u.set(n,{...l,disabled:i}),{...o,droppable:{...o.droppable,containers:u}}}case rn.UnregisterDroppable:{const{id:n,key:r}=e,i=o.droppable.containers.get(n);if(!i||r!==i.key)return o;const l=new Co(o.droppable.containers);return l.delete(n),{...o,droppable:{...o.droppable,containers:l}}}default:return o}}function YE(o){let{disabled:e}=o;const{active:n,activatorEvent:r,draggableNodes:i}=I.useContext(lc),l=Pd(r),u=Pd(n?.id);return I.useEffect(()=>{if(!e&&!r&&l&&u!=null){if(!Gd(l)||document.activeElement===l.target)return;const d=i.get(u);if(!d)return;const{activatorNode:f,node:g}=d;if(!f.current&&!g.current)return;requestAnimationFrame(()=>{for(const m of[f.current,g.current]){if(!m)continue;const b=BC(m);if(b){b.focus();break}}})}},[r,e,i,u,l]),null}function XE(o,e){let{transform:n,...r}=e;return o!=null&&o.length?o.reduce((i,l)=>l({transform:i,...r}),n):n}function JE(o){return I.useMemo(()=>({draggable:{...oc.draggable,...o?.draggable},droppable:{...oc.droppable,...o?.droppable},dragOverlay:{...oc.dragOverlay,...o?.dragOverlay}}),[o?.draggable,o?.droppable,o?.dragOverlay])}function QE(o){let{activeNode:e,measure:n,initialRect:r,config:i=!0}=o;const l=I.useRef(!1),{x:u,y:d}=typeof i=="boolean"?{x:i,y:i}:i;za(()=>{if(!u&&!d||!e){l.current=!1;return}if(l.current||!r)return;const g=e?.node.current;if(!g||g.isConnected===!1)return;const m=n(g),b=Tm(m,r);if(u||(b.x=0),d||(b.y=0),l.current=!0,Math.abs(b.x)>0||Math.abs(b.y)>0){const v=km(g);v&&v.scrollBy({top:b.y,left:b.x})}},[e,u,d,r,n])}const Km=I.createContext({...ya,scaleX:1,scaleY:1});var xr;(function(o){o[o.Uninitialized=0]="Uninitialized",o[o.Initializing=1]="Initializing",o[o.Initialized=2]="Initialized"})(xr||(xr={}));const ZE=I.memo(function(e){var n,r,i,l;let{id:u,accessibility:d,autoScroll:f=!0,children:g,sensors:m=qE,collisionDetection:b=nE,measuring:v,modifiers:S,..._}=e;const x=I.useReducer(WE,void 0,KE),[k,O]=x,[M,T]=KC(),[D,j]=I.useState(xr.Uninitialized),H=D===xr.Initialized,{draggable:{active:P,nodes:X,translate:pe},droppable:{containers:_e}}=k,we=P!=null?X.get(P):null,Me=I.useRef({initial:null,translated:null}),qe=I.useMemo(()=>{var Te;return P!=null?{id:P,data:(Te=we?.data)!=null?Te:GE,rect:Me}:null},[P,we]),Je=I.useRef(null),[gt,Q]=I.useState(null),[fe,ve]=I.useState(null),ae=go(_,Object.values(_)),ge=mo("DndDescribedBy",u),A=I.useMemo(()=>_e.getEnabled(),[_e]),K=JE(v),{droppableRects:ue,measureDroppableContainers:he,measuringScheduled:le}=OE(A,{dragging:H,dependencies:[pe.x,pe.y],config:K.droppable}),de=kE(X,P),Oe=I.useMemo(()=>fe?Vd(fe):null,[fe]),He=me(),Pe=LE(de,K.draggable.measure);QE({activeNode:P!=null?X.get(P):null,config:He.layoutShiftCompensation,initialRect:Pe,measure:K.draggable.measure});const ke=Bm(de,K.draggable.measure,Pe),Ot=Bm(de?de.parentElement:null),ut=I.useRef({activatorEvent:null,active:null,activeNode:de,collisionRect:null,collisions:null,droppableRects:ue,draggableNodes:X,draggingNode:null,draggingNodeRect:null,droppableContainers:_e,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),Ct=_e.getNodeFor((n=ut.current.over)==null?void 0:n.id),vt=PE({measure:K.dragOverlay.measure}),kt=(r=vt.nodeRef.current)!=null?r:de,Ee=H?(i=vt.rect)!=null?i:ke:null,Nt=!!(vt.nodeRef.current&&vt.rect),zt=NE(Nt?null:ke),qt=Gm(kt?Mn(kt):null),Lt=IE(H?Ct??de:null),_n=UE(Lt),hn=XE(S,{transform:{x:pe.x-zt.x,y:pe.y-zt.y,scaleX:1,scaleY:1},activatorEvent:fe,active:qe,activeNodeRect:ke,containerNodeRect:Ot,draggingNodeRect:Ee,over:ut.current.over,overlayNodeRect:vt.rect,scrollableAncestors:Lt,scrollableAncestorRects:_n,windowRect:qt}),Ba=Oe?cs(Oe,pe):null,Jt=HE(Lt),Dr=qm(Jt),tr=qm(Jt,[ke]),An=cs(hn,Dr),sn=Ee?iE(Ee,hn):null,tt=qe&&sn?b({active:qe,collisionRect:sn,droppableRects:ue,droppableContainers:A,pointerCoordinates:Ba}):null,na=Am(tt,"id"),[kn,nr]=I.useState(null),_a=Nt?hn:cs(hn,tr),on=aE(_a,(l=kn?.rect)!=null?l:null,ke),Dn=I.useRef(null),q=I.useCallback((Te,xe)=>{let{sensor:je,options:ze}=xe;if(Je.current==null)return;const $e=X.get(Je.current);if(!$e)return;const De=Te.nativeEvent,Ye=new je({active:Je.current,activeNode:$e,event:De,options:ze,context:ut,onAbort(Ae){if(!X.get(Ae))return;const{onDragAbort:Ue}=ae.current,ot={id:Ae};Ue?.(ot),M({type:"onDragAbort",event:ot})},onPending(Ae,Re,Ue,ot){if(!X.get(Ae))return;const{onDragPending:Qt}=ae.current,Kt={id:Ae,constraint:Re,initialCoordinates:Ue,offset:ot};Qt?.(Kt),M({type:"onDragPending",event:Kt})},onStart(Ae){const Re=Je.current;if(Re==null)return;const Ue=X.get(Re);if(!Ue)return;const{onDragStart:ot}=ae.current,At={activatorEvent:De,active:{id:Re,data:Ue.data,rect:Me}};Ar.unstable_batchedUpdates(()=>{ot?.(At),j(xr.Initializing),O({type:rn.DragStart,initialCoordinates:Ae,active:Re}),M({type:"onDragStart",event:At}),Q(Dn.current),ve(De)})},onMove(Ae){O({type:rn.DragMove,coordinates:Ae})},onEnd:Ke(rn.DragEnd),onCancel:Ke(rn.DragCancel)});Dn.current=Ye;function Ke(Ae){return async function(){const{active:Ue,collisions:ot,over:At,scrollAdjustedTranslate:Qt}=ut.current;let Kt=null;if(Ue&&Qt){const{cancelDrop:Fn}=ae.current;Kt={activatorEvent:De,active:Ue,collisions:ot,delta:Qt,over:At},Ae===rn.DragEnd&&typeof Fn=="function"&&await Promise.resolve(Fn(Kt))&&(Ae=rn.DragCancel)}Je.current=null,Ar.unstable_batchedUpdates(()=>{O({type:Ae}),j(xr.Uninitialized),nr(null),Q(null),ve(null),Dn.current=null;const Fn=Ae===rn.DragEnd?"onDragEnd":"onDragCancel";if(Kt){const Nn=ae.current[Fn];Nn?.(Kt),M({type:Fn,event:Kt})}})}}},[X]),ie=I.useCallback((Te,xe)=>(je,ze)=>{const $e=je.nativeEvent,De=X.get(ze);if(Je.current!==null||!De||$e.dndKit||$e.defaultPrevented)return;const Ye={active:De};Te(je,xe.options,Ye)===!0&&($e.dndKit={capturedBy:xe.sensor},Je.current=ze,q(je,xe))},[X,q]),oe=DE(m,ie);zE(m),za(()=>{ke&&D===xr.Initializing&&j(xr.Initialized)},[ke,D]),I.useEffect(()=>{const{onDragMove:Te}=ae.current,{active:xe,activatorEvent:je,collisions:ze,over:$e}=ut.current;if(!xe||!je)return;const De={active:xe,activatorEvent:je,collisions:ze,delta:{x:An.x,y:An.y},over:$e};Ar.unstable_batchedUpdates(()=>{Te?.(De),M({type:"onDragMove",event:De})})},[An.x,An.y]),I.useEffect(()=>{const{active:Te,activatorEvent:xe,collisions:je,droppableContainers:ze,scrollAdjustedTranslate:$e}=ut.current;if(!Te||Je.current==null||!xe||!$e)return;const{onDragOver:De}=ae.current,Ye=ze.get(na),Ke=Ye&&Ye.rect.current?{id:Ye.id,rect:Ye.rect.current,data:Ye.data,disabled:Ye.disabled}:null,Ae={active:Te,activatorEvent:xe,collisions:je,delta:{x:$e.x,y:$e.y},over:Ke};Ar.unstable_batchedUpdates(()=>{nr(Ke),De?.(Ae),M({type:"onDragOver",event:Ae})})},[na]),za(()=>{ut.current={activatorEvent:fe,active:qe,activeNode:de,collisionRect:sn,collisions:tt,droppableRects:ue,draggableNodes:X,draggingNode:kt,draggingNodeRect:Ee,droppableContainers:_e,over:kn,scrollableAncestors:Lt,scrollAdjustedTranslate:An},Me.current={initial:Ee,translated:sn}},[qe,de,tt,sn,X,kt,Ee,ue,_e,kn,Lt,An]),xE({...He,delta:pe,draggingRect:sn,pointerCoordinates:Ba,scrollableAncestors:Lt,scrollableAncestorRects:_n});const Z=I.useMemo(()=>({active:qe,activeNode:de,activeNodeRect:ke,activatorEvent:fe,collisions:tt,containerNodeRect:Ot,dragOverlay:vt,draggableNodes:X,droppableContainers:_e,droppableRects:ue,over:kn,measureDroppableContainers:he,scrollableAncestors:Lt,scrollableAncestorRects:_n,measuringConfiguration:K,measuringScheduled:le,windowRect:qt}),[qe,de,ke,fe,tt,Ot,vt,X,_e,ue,kn,he,Lt,_n,K,le,qt]),be=I.useMemo(()=>({activatorEvent:fe,activators:oe,active:qe,activeNodeRect:ke,ariaDescribedById:{draggable:ge},dispatch:O,draggableNodes:X,over:kn,measureDroppableContainers:he}),[fe,oe,qe,ke,O,ge,X,kn,he]);return Xe.createElement(Sm.Provider,{value:T},Xe.createElement(lc.Provider,{value:be},Xe.createElement(Fm.Provider,{value:Z},Xe.createElement(Km.Provider,{value:on},g)),Xe.createElement(YE,{disabled:d?.restoreFocus===!1})),Xe.createElement(XC,{...d,hiddenTextDescribedById:ge}));function me(){const Te=gt?.autoScrollEnabled===!1,xe=typeof f=="object"?f.enabled===!1:f===!1,je=H&&!Te&&!xe;return typeof f=="object"?{...f,enabled:je}:{enabled:je}}}),e_=I.createContext(null),Wm="button",t_="Draggable";function n_(o){let{id:e,data:n,disabled:r=!1,attributes:i}=o;const l=mo(t_),{activators:u,activatorEvent:d,active:f,activeNodeRect:g,ariaDescribedById:m,draggableNodes:b,over:v}=I.useContext(lc),{role:S=Wm,roleDescription:_="draggable",tabIndex:x=0}=i??{},k=f?.id===e,O=I.useContext(k?Km:e_),[M,T]=nc(),[D,j]=nc(),H=jE(u,e),P=go(n);za(()=>(b.set(e,{id:e,key:l,node:M,activatorNode:D,data:P}),()=>{const pe=b.get(e);pe&&pe.key===l&&b.delete(e)}),[b,e]);const X=I.useMemo(()=>({role:S,tabIndex:x,"aria-disabled":r,"aria-pressed":k&&S===Wm?!0:void 0,"aria-roledescription":_,"aria-describedby":m.draggable}),[r,S,x,k,_,m.draggable]);return{active:f,activatorEvent:d,activeNodeRect:g,attributes:X,isDragging:k,listeners:r?void 0:H,node:M,over:v,setNodeRef:T,setActivatorNodeRef:j,transform:O}}function a_(){return I.useContext(Fm)}const r_="Droppable",i_={timeout:25};function s_(o){let{data:e,disabled:n=!1,id:r,resizeObserverConfig:i}=o;const l=mo(r_),{active:u,dispatch:d,over:f,measureDroppableContainers:g}=I.useContext(lc),m=I.useRef({disabled:n}),b=I.useRef(!1),v=I.useRef(null),S=I.useRef(null),{disabled:_,updateMeasurementsFor:x,timeout:k}={...i_,...i},O=go(x??r),M=I.useCallback(()=>{if(!b.current){b.current=!0;return}S.current!=null&&clearTimeout(S.current),S.current=setTimeout(()=>{g(Array.isArray(O.current)?O.current:[O.current]),S.current=null},k)},[k]),T=sc({callback:M,disabled:_||!u}),D=I.useCallback((X,pe)=>{T&&(pe&&(T.unobserve(pe),b.current=!1),X&&T.observe(X))},[T]),[j,H]=nc(D),P=go(e);return I.useEffect(()=>{!T||!j.current||(T.disconnect(),b.current=!1,T.observe(j.current))},[j,T]),I.useEffect(()=>(d({type:rn.RegisterDroppable,element:{id:r,key:l,disabled:n,node:j,rect:v,data:P}}),()=>d({type:rn.UnregisterDroppable,key:l,id:r})),[r]),I.useEffect(()=>{n!==m.current.disabled&&(d({type:rn.SetDroppableDisabled,id:r,key:l,disabled:n}),m.current.disabled=n)},[r,l,n,d]),{active:u,rect:v,isOver:f?.id===r,node:j,over:f,setNodeRef:H}}function nf(o,e,n){const r=o.slice();return r.splice(n<0?r.length+n:n,0,r.splice(e,1)[0]),r}function o_(o,e){return o.reduce((n,r,i)=>{const l=e.get(r);return l&&(n[i]=l),n},Array(o.length))}function cc(o){return o!==null&&o>=0}function l_(o,e){if(o===e)return!0;if(o.length!==e.length)return!1;for(let n=0;n{var e;let{rects:n,activeNodeRect:r,activeIndex:i,overIndex:l,index:u}=o;const d=(e=n[i])!=null?e:r;if(!d)return null;const f=d_(n,u,i);if(u===i){const g=n[l];return g?{x:ii&&u<=l?{x:-d.width-f,y:0,...uc}:u=l?{x:d.width+f,y:0,...uc}:{x:0,y:0,...uc}};function d_(o,e,n){const r=o[e],i=o[e-1],l=o[e+1];return!r||!i&&!l?0:n{let{rects:e,activeIndex:n,overIndex:r,index:i}=o;const l=nf(e,r,n),u=e[i],d=l[i];return!d||!u?null:{x:d.left-u.left,y:d.top-u.top,scaleX:d.width/u.width,scaleY:d.height/u.height}},dc={scaleX:1,scaleY:1},f_=o=>{var e;let{activeIndex:n,activeNodeRect:r,index:i,rects:l,overIndex:u}=o;const d=(e=l[n])!=null?e:r;if(!d)return null;if(i===n){const g=l[u];return g?{x:0,y:nn&&i<=u?{x:0,y:-d.height-f,...dc}:i=u?{x:0,y:d.height+f,...dc}:{x:0,y:0,...dc}};function h_(o,e,n){const r=o[e],i=o[e-1],l=o[e+1];return r?nr.map(H=>typeof H=="object"&&"id"in H?H.id:H),[r]),_=u!=null,x=u?S.indexOf(u.id):-1,k=g?S.indexOf(g.id):-1,O=I.useRef(S),M=!l_(S,O.current),T=k!==-1&&x===-1||M,D=c_(l);za(()=>{M&&_&&m(S)},[M,S,_,m]),I.useEffect(()=>{O.current=S},[S]);const j=I.useMemo(()=>({activeIndex:x,containerId:b,disabled:D,disableTransforms:T,items:S,overIndex:k,useDragOverlay:v,sortedRects:o_(S,f),strategy:i}),[x,b,D.draggable,D.droppable,T,S,k,f,v,i]);return Xe.createElement(Xm.Provider,{value:j},e)}const p_=o=>{let{id:e,items:n,activeIndex:r,overIndex:i}=o;return nf(n,r,i).indexOf(e)},m_=o=>{let{containerId:e,isSorting:n,wasDragging:r,index:i,items:l,newIndex:u,previousItems:d,previousContainerId:f,transition:g}=o;return!g||!r||d!==l&&i===u?!1:n?!0:u!==i&&e===f},y_={duration:200,easing:"ease"},Jm="transform",b_=bo.Transition.toString({property:Jm,duration:0,easing:"linear"}),v_={roleDescription:"sortable"};function S_(o){let{disabled:e,index:n,node:r,rect:i}=o;const[l,u]=I.useState(null),d=I.useRef(n);return za(()=>{if(!e&&n!==d.current&&r.current){const f=i.current;if(f){const g=us(r.current,{ignoreTransform:!0}),m={x:f.left-g.left,y:f.top-g.top,scaleX:f.width/g.width,scaleY:f.height/g.height};(m.x||m.y)&&u(m)}}n!==d.current&&(d.current=n)},[e,n,r,i]),I.useEffect(()=>{l&&u(null)},[l]),l}function w_(o){let{animateLayoutChanges:e=m_,attributes:n,disabled:r,data:i,getNewIndex:l=p_,id:u,strategy:d,resizeObserverConfig:f,transition:g=y_}=o;const{items:m,containerId:b,activeIndex:v,disabled:S,disableTransforms:_,sortedRects:x,overIndex:k,useDragOverlay:O,strategy:M}=I.useContext(Xm),T=C_(r,S),D=m.indexOf(u),j=I.useMemo(()=>({sortable:{containerId:b,index:D,items:m},...i}),[b,i,D,m]),H=I.useMemo(()=>m.slice(m.indexOf(u)),[m,u]),{rect:P,node:X,isOver:pe,setNodeRef:_e}=s_({id:u,data:j,disabled:T.droppable,resizeObserverConfig:{updateMeasurementsFor:H,...f}}),{active:we,activatorEvent:Me,activeNodeRect:qe,attributes:Je,setNodeRef:gt,listeners:Q,isDragging:fe,over:ve,setActivatorNodeRef:ae,transform:ge}=n_({id:u,data:j,attributes:{...v_,...n},disabled:T.draggable}),A=HC(_e,gt),K=!!we,ue=K&&!_&&cc(v)&&cc(k),he=!O&&fe,le=he&&ue?ge:null,Oe=ue?le??(d??M)({rects:x,activeNodeRect:qe,activeIndex:v,overIndex:k,index:D}):null,He=cc(v)&&cc(k)?l({id:u,items:m,activeIndex:v,overIndex:k}):D,Pe=we?.id,ke=I.useRef({activeId:Pe,items:m,newIndex:He,containerId:b}),Ot=m!==ke.current.items,ut=e({active:we,containerId:b,isDragging:fe,isSorting:K,id:u,index:D,items:m,newIndex:ke.current.newIndex,previousItems:ke.current.items,previousContainerId:ke.current.containerId,transition:g,wasDragging:ke.current.activeId!=null}),Ct=S_({disabled:!ut,index:D,node:X,rect:P});return I.useEffect(()=>{K&&ke.current.newIndex!==He&&(ke.current.newIndex=He),b!==ke.current.containerId&&(ke.current.containerId=b),m!==ke.current.items&&(ke.current.items=m)},[K,He,b,m]),I.useEffect(()=>{if(Pe===ke.current.activeId)return;if(Pe!=null&&ke.current.activeId==null){ke.current.activeId=Pe;return}const kt=setTimeout(()=>{ke.current.activeId=Pe},50);return()=>clearTimeout(kt)},[Pe]),{active:we,activeIndex:v,attributes:Je,data:j,rect:P,index:D,newIndex:He,items:m,isOver:pe,isSorting:K,isDragging:fe,listeners:Q,node:X,overIndex:k,over:ve,setNodeRef:A,setActivatorNodeRef:ae,setDroppableNodeRef:_e,setDraggableNodeRef:gt,transform:Ct??Oe,transition:vt()};function vt(){if(Ct||Ot&&ke.current.newIndex===D)return b_;if(!(he&&!Gd(Me)||!g)&&(K||ut))return bo.Transition.toString({...g,property:Jm})}}function C_(o,e){var n,r;return typeof o=="boolean"?{draggable:o,droppable:!1}:{draggable:(n=o?.draggable)!=null?n:e.draggable,droppable:(r=o?.droppable)!=null?r:e.droppable}}function fc(o){if(!o)return!1;const e=o.data.current;return!!(e&&"sortable"in e&&typeof e.sortable=="object"&&"containerId"in e.sortable&&"items"in e.sortable&&"index"in e.sortable)}const E_=[st.Down,st.Right,st.Up,st.Left],__=(o,e)=>{let{context:{active:n,collisionRect:r,droppableRects:i,droppableContainers:l,over:u,scrollableAncestors:d}}=e;if(E_.includes(o.code)){if(o.preventDefault(),!n||!r)return;const f=[];l.getEnabled().forEach(b=>{if(!b||b!=null&&b.disabled)return;const v=i.get(b.id);if(v)switch(o.code){case st.Down:r.topv.top&&f.push(b);break;case st.Left:r.left>v.left&&f.push(b);break;case st.Right:r.left1&&(m=g[1].id),m!=null){const b=l.get(n.id),v=l.get(m),S=v?i.get(v.id):null,_=v?.node.current;if(_&&S&&b&&v){const k=rc(_).some((H,P)=>d[P]!==H),O=Qm(b,v),M=A_(b,v),T=k||!O?{x:0,y:0}:{x:M?r.width-S.width:0,y:M?r.height-S.height:0},D={x:S.left,y:S.top};return T.x&&T.y?D:yo(D,T)}}}};function Qm(o,e){return!fc(o)||!fc(e)?!1:o.data.current.sortable.containerId===e.data.current.sortable.containerId}function A_(o,e){return!fc(o)||!fc(e)||!Qm(o,e)?!1:o.data.current.sortable.index{const d=I.useId(),f=I.useRef({inProgress:!1,lastSortTime:0,lastActiveId:null}),g=JC(wm(Qd,{activationConstraint:{distance:5}}),wm(Xd,{coordinateGetter:__})),m=I.useCallback(S=>{f.current={inProgress:!0,lastSortTime:Date.now(),lastActiveId:S.active.id},l?.({activeId:S.active.id})},[l]),b=I.useCallback(S=>{const{active:_,over:x}=S,k=Date.now();if(!x||_.id===x.id){f.current.inProgress=!1;return}const{lastSortTime:O,lastActiveId:M,inProgress:T}=f.current;if(M===_.id&&k-O<500&&!T)return;f.current={inProgress:!1,lastSortTime:k,lastActiveId:_.id};const D=o.findIndex(H=>String(H[e])===String(_.id)),j=o.findIndex(H=>String(H[e])===String(x.id));if(D!==-1&&j!==-1&&D!==j){const H=nf(o,D,j);i?.(H,{oldIndex:D,newIndex:j})}},[o,e,i]),v=o.map(S=>String(S[e]));return ft.jsx(ZE,{id:d,sensors:g,collisionDetection:ZC,onDragStart:m,onDragEnd:b,children:ft.jsx(g_,{items:v,strategy:x_[n],children:r})},u!==void 0?`${d}-v${u}`:d)},Zm=I.createContext(null),R_=({value:o,children:e})=>ft.jsx(Zm.Provider,{value:o,children:e}),k_=()=>I.useContext(Zm),D_=({id:o,handle:e,as:n="div",children:r})=>{const{attributes:i,listeners:l,setNodeRef:u,transform:d,transition:f,isDragging:g}=w_({id:o}),m={transform:bo.Transform.toString(d),transition:f,opacity:g?.5:1,position:"relative"};return ft.jsx(n,{ref:u,style:m,...i,...e?{}:l,"data-sortable-item":!0,"data-sortable-id":String(o),"data-dragging":g,children:ft.jsx(R_,{value:{listeners:l,handle:e,isDragging:g},children:r})})},ht=dt("DynamicRenderer");function Za(){try{return window.G7Core?.devTools}catch{return}}function rf(o,e,n,r){if(typeof o=="string"){if(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*/.test(o))try{return e.resolveTranslations(o,n,r)}catch{return o}return o}if(Array.isArray(o))return o.map(i=>rf(i,e,n,r));if(o&&typeof o=="object"){const i={};for(const[l,u]of Object.entries(o))i[l]=rf(u,e,n,r);return i}return o}function O_(o){const e=Object.keys(o);return e.length>0&&e.every(n=>/^\d+$/.test(n))}const ba=(o,e)=>{const n=["errors"],r={...o};for(const i of Object.keys(e)){const l=e[i],u=o[i];if(l===null){r[i]=null;continue}if(n.includes(i)){r[i]=l;continue}if(Array.isArray(l)){r[i]=l;continue}if(Array.isArray(u)&&l!==null&&typeof l=="object"&&!Array.isArray(l)&&O_(l)){const d=Object.keys(l).map(g=>parseInt(g,10));if((d.length>0?Math.max(...d):0)>=u.length+d.length+10)r[i]={...l};else{const g=[...u];for(const[m,b]of Object.entries(l)){const v=parseInt(m,10);v>=0&&v=g.length&&(g[v]=b)}r[i]=g}}else l!==null&&typeof l=="object"&&u!==null&&typeof u=="object"&&!Array.isArray(u)?r[i]=ba(u,l):r[i]=l}return r},ey=(o,e)=>{const n={...o};for(const r of Object.keys(e)){if(!(r in n))continue;const i=e[r],l=n[r];if(i!==null&&typeof i=="object"&&!Array.isArray(i)&&l!==null&&typeof l=="object"&&!Array.isArray(l)){const u=ey(l,i);Object.keys(u).length===0?delete n[r]:n[r]=u}else delete n[r]}return n},L_=o=>{const e={},n=o.split(";").filter(r=>r.trim());for(const r of n){const i=r.indexOf(":");if(i===-1)continue;const l=r.substring(0,i).trim(),u=r.substring(i+1).trim();if(l&&u){const d=l.replace(/-([a-z])/g,(f,g)=>g.toUpperCase());e[d]=u}}return e};class M_ extends I.Component{constructor(e){super(e),this.state={hasError:!1,error:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,n){ht.error(`컴포넌트 렌더링 에러 (ID: ${this.props.componentId}, Name: ${this.props.componentName}):`,e,n)}render(){return this.state.hasError?this.props.fallback?this.props.fallback:ft.jsxs("div",{className:"p-4 my-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-400",children:[ft.jsx("div",{className:"font-semibold mb-1",children:"컴포넌트 로드 실패"}),ft.jsxs("div",{className:"text-xs opacity-80",children:[this.props.componentName&&`[${this.props.componentName}] `,"데이터를 표시할 수 없습니다."]}),this.state.error&&ft.jsxs("details",{className:"mt-2",children:[ft.jsx("summary",{className:"cursor-pointer text-xs opacity-70 hover:opacity-100",children:"상세 정보"}),ft.jsx("div",{className:"mt-1 p-2 bg-red-100 dark:bg-red-900/30 rounded text-xs font-mono whitespace-pre-wrap break-all",children:this.state.error.message})]})]}):this.props.children}}const ja=I.memo(({componentDef:o,dataContext:e,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:u,parentComponentContext:d,parentFormContextProp:f,parentDataContext:g,isRootRenderer:m=!1,isInsideIteration:b=!1,isEditMode:v=!1,onComponentSelect:S,onComponentHover:_,onDragStart:x,onDragEnd:k,componentPath:O,layoutKey:M})=>{const[T,D]=I.useState({loadingActions:{}});I.useLayoutEffect(()=>{d||(i.startRenderCycle(),window.__g7SetLocalOverrideKeys||(window.__g7ForcedLocalFields=void 0),window.__g7PendingLocalState=null)});const j=I.useRef(null);I.useLayoutEffect(()=>{if(!d&&e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:q,_merge:ie,...oe}=e._localInit,Z=`${JSON.stringify(oe)}:${q||"no-force"}`;if(j.current!==Z){j.current=Z;const be=ie||"shallow",Te=window.G7Core?.state?.get?.()?._local||{},xe=window.__g7PendingLocalState||{},je={...Te,...xe};be==="replace"?window.__g7PendingLocalState={loadingActions:je.loadingActions||{},...oe,hasChanges:!1}:be==="deep"?window.__g7PendingLocalState=ba(je,{...oe,hasChanges:!1}):window.__g7PendingLocalState={...je,...oe,hasChanges:!1};const ze=window.__g7SetLocalOverrideKeys;ze&&typeof ze=="object"&&(be==="replace"?window.__g7SetLocalOverrideKeys={...oe,hasChanges:!1}:window.__g7SetLocalOverrideKeys=ba(ze,{...oe,hasChanges:!1}))}}}),I.useLayoutEffect(()=>{if(!d){const q=window.__g7SetLocalOverrideKeys;if(q){D(be=>ey(be,q));const ie=q,oe=window.__g7ForcedLocalFields,Z=window.__g7LastSetLocalSnapshot;queueMicrotask(()=>{window.__g7SetLocalOverrideKeys===ie&&(window.__g7SetLocalOverrideKeys=void 0),window.__g7ForcedLocalFields===oe&&(window.__g7ForcedLocalFields=void 0),window.__g7LastSetLocalSnapshot===Z&&(window.__g7LastSetLocalSnapshot=void 0)})}}},[e._local]);const H=I.useRef(M);I.useEffect(()=>{o._fromBase&&M&&H.current!==M&&(H.current=M,D({loadingActions:{}}))},[M,o._fromBase]);const P=I.useRef(""),X=I.useRef(!1),pe=I.useRef(null),_e=I.useRef({}),we=I.useRef({}),Me=I.useRef(g),qe=IC(),Je=gC(),gt=Je?.getParentDataContext()??g;I.useEffect(()=>{if(e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:q,...ie}=e._localInit,oe=JSON.stringify(ie),Z=q!==void 0,be=Id(),me=`${oe}:${q||"no-force"}`;if(be.hash!==me){be.hash=me,be.timestamp=q,P.current=oe,Z&&(X.current=!0);const{_merge:je,...ze}=ie,$e=je||"shallow";if(D(De=>$e==="replace"?{loadingActions:De.loadingActions||{},...ze,hasChanges:!1}:$e==="deep"?ba(De,{...ze,hasChanges:!1}):{...De,...ze,hasChanges:!1}),e._globalSetState){const De=e._global?._local||{};let Ye;$e==="replace"?Ye={...ze,hasChanges:!1}:$e==="deep"?Ye=ba(De,{...ze,hasChanges:!1}):Ye={...De,...ze,hasChanges:!1},e._globalSetState({_local:Ye})}i.invalidateCacheByKeys(["_local"]),ht.log("_localInit applied (data changed):",Object.keys(ie))}pe.current=e._localInit,vC(e._localInit)}},[e._localInit,g,i]);const{isTransitioning:Q}=dm(),fe=TC(),ve=f??fe,{width:ae}=zd(),ge=k_(),A=I.useMemo(()=>{let q=o;if(o.type==="iterator"&&o.data){const{data:Z,itemName:be,indexName:me,...Te}=o;q={...Te,iteration:{source:Z,item_var:be||"item",index_var:me}}}if(!q.responsive)return q;const ie=ui.getMatchingKey(q.responsive,ae);if(!ie)return q;const oe=q.responsive[ie];return{...q,props:{...q.props,...oe.props},children:oe.children??q.children,text:oe.text??q.text,if:oe.if??q.if,iteration:oe.iteration??q.iteration,__responsiveChildrenKey:oe.children!==void 0?ie:void 0}},[o,ae]),K=I.useCallback(q=>{const ie=q?.__setStateId;if(D(oe=>{const Z=window.__g7PendingLocalState,me=window.__g7SetLocalOverrideKeys||Z,Te=me?ba(oe,me):oe;if(typeof q=="function"){const De=q(Te);return ba(Te,De)}const{__mergeMode:xe,__setStateId:je,...ze}=q;let $e;if(xe==="replace"?$e=ze:xe==="shallow"?$e={...Te,...ze}:$e=ba(Te,ze),xe!==void 0){const De=window.__g7ForcedLocalFields;De&&(window.__g7ForcedLocalFields={...De,...ze})}return $e}),ie){const oe=Za();if(oe?.isEnabled()){const Z=A.id||"unknown",be=A.name||"Unknown";setTimeout(()=>{oe.trackComponentRender?.(Z,be,0,["_local"],[]),oe.completeStateChange?.(ie)},0)}}},[A.id,A.name]),ue=d?.state??T,he=d?.setState??K,le=I.useMemo(()=>{const q=e._localInit&&e._localInit!==pe.current;let ie;q&&e._local?ie=e._local:ie=e._local?ba(e._local,ue):ue;const oe=window.__g7ForcedLocalFields;oe&&(ie=ba(ie,oe));const Z={...e,_local:ie},be=e._computedDefinitions;if(be&&Object.keys(be).length>0){const me=new Rn,Te={...Z,...qe?{_isolated:qe.state}:{}},xe={},je=Za(),ze=je?.isEnabled()??!1;for(const[$e,De]of Object.entries(be)){const Ye=ze?Date.now():0,Ke=ze?e._computed?.[$e]:void 0;let Ae;try{if(typeof De=="string")if(De.startsWith("{{")&&De.endsWith("}}")){const Re=De.slice(2,-2).trim(),Ue=me.evaluateExpression(Re,Te,{skipCache:!0});xe[$e]=Ue}else xe[$e]=De;else if(De&&typeof De=="object"&&"$switch"in De){const Re=me.resolveSwitch(De,Te,{skipCache:!0});xe[$e]=Re}}catch(Re){ht.warn(`Failed to recalculate computed value: ${$e}`,Re),xe[$e]=e._computed?.[$e],Ae=Re instanceof Error?Re.message:String(Re)}if(ze&&(Ae||Ke!==xe[$e])){const Ue=Date.now()-Ye;je.trackComputedProperty?.($e,typeof De=="string"?De:JSON.stringify(De),[{type:"_local",path:"_local",value:Z._local}],xe[$e],Ue,A.id,Ae),Ke!==xe[$e]&&je.trackComputedRecalc?.($e,"dependency-change",Ke,xe[$e],Ue,{type:"_local",path:"_local"},A.id)}}Z._computed=xe}if(qe&&(Z._isolated=qe.state),gt&&(Z.$parent=gt),o.extensionPointProps){const me=i.resolveObject(o.extensionPointProps,Z,b?{skipCache:!0}:void 0);Z.extensionPointProps=v&&l?rf(me,l,n,Z):me}return o.extensionPointCallbacks&&(Z.extensionPointCallbacks=o.extensionPointCallbacks),v&&(Z.$templateId===void 0&&n?.templateId&&(Z.$templateId=n.templateId),Z.$locale===void 0&&n?.locale&&(Z.$locale=n.locale)),Z},[e,ue,qe?.state,gt,Je?.version,o.extensionPointProps,o.extensionPointCallbacks]);_e.current=le._local,we.current=le._computed||{},Me.current=gt,I.useEffect(()=>{const q=Za();if(!q?.isEnabled())return;const ie=le._local;ie&&q.updateLocalState(ie);const oe=le._computed;oe&&q.updateComputedState(oe),gt&&q.updateParentContext?.(gt);const Z=A.id||`comp-${Date.now()}`,be=A.name||"Unknown",me={global:Object.keys(e._global||{}),local:Object.keys(ie||{}),context:d?Object.keys(d.state||{}):[]},Te={type:d?"componentContext":"dynamicState",hasComponentContext:!!d,parentId:d?"parent":void 0};q.trackComponentStateSource?.(Z,be,me,Te),ue&&Object.keys(ue).length>0&&q.trackDynamicState?.(Z,ue);const xe=!!(A.expandChildren||A.props?.expandChildren);q.trackContextFlow?.(Z,be,!!d,xe,!0)},[le._local,e,A.id,A.name,d,ue,A.expandChildren,A.props?.expandChildren,gt]);const de=I.useMemo(()=>({state:{...le._local},setState:he,stateRef:_e,computedRef:we,isolatedContext:qe,parentDataContext:le}),[le._local,he,T,qe,le]),Oe=I.useMemo(()=>{const q=Qi({if:A.if,condition:A.condition,conditions:A.conditions},le,i,A.id);if(A.if||A.condition!==void 0||A.conditions){const oe=Za();oe?.isEnabled()&&A.if&&oe.trackIfCondition(A.id||`if-${Date.now()}`,A.if,q)}return q},[A.if,A.conditions,le,i,A.id]),He=I.useMemo(()=>{if(!A.slot)return null;const q=A.slot;if(q.startsWith("{{")&&q.endsWith("}}")){const ie=q.slice(2,-2).trim();try{const oe={componentId:A.id,componentName:A.name,propName:"slot"},Z=i.evaluateExpression(ie,le,oe);return ht.log(`[Slot] 표현식 평가: "${ie}" => "${Z}" (컴포넌트: ${A.id})`),typeof Z=="string"?Z:null}catch(oe){return ht.warn(`slot 표현식 평가 실패 (컴포넌트: ${A.id}):`,oe),null}}return ht.log(`[Slot] 정적 슬롯 ID: "${q}" (컴포넌트: ${A.id})`),q},[A.slot,A.id,le,i]),Pe=I.useMemo(()=>!He||!A.id?"":`${A.id}-${He}-${JSON.stringify(A.slotOrder??0)}`,[He,A.id,A.slotOrder]),ke=I.useRef(null);I.useEffect(()=>{const q=window.__slotContextValue,ie=q?.isEnabled??!1;if(A.slot&&ht.log(`[Slot Registration] 컴포넌트: ${A.id}, isSlotEnabled: ${ie}, shouldRender: ${Oe}, resolvedSlotId: ${He}`),!ie||!Oe){A.slot&&ht.log(`[Slot Registration] 등록 스킵 - isSlotEnabled: ${ie}, shouldRender: ${Oe}`),ke.current&&A.id&&q&&(q.unregisterFromSlot(ke.current,A.id),ke.current=null);return}if(!He||!A.id){A.slot&&ht.log(`[Slot Registration] 등록 스킵 - resolvedSlotId: ${He}, id: ${A.id}`),ke.current&&A.id&&q&&(q.unregisterFromSlot(ke.current,A.id),ke.current=null);return}ke.current&&ke.current!==He&&q.unregisterFromSlot(ke.current,A.id);const oe={...A,slot:void 0,slotOrder:void 0};return q.registerToSlot(He,A.id,{componentDef:oe,dataContext:e,order:A.slotOrder??0,parentFormContext:f??null,getParentComponentContext:()=>de,translationContext:n,registrationKey:Pe}),ke.current=He,()=>{const Z=window.__slotContextValue;He&&A.id&&Z&&Z.unregisterFromSlot(He,A.id)}},[Oe,He,A,e,f,de,n,Pe]);const Ot=I.useCallback(q=>{if(typeof q=="string"){if(op(q))return cp(q);if(lp(q)){const ie=[],oe=q.replace(new RegExp(`${Ll}([^${Zs}]*)${Zs}`,"g"),(be,me)=>(ie.push(me),`${_r}${ie.length-1}${_r}`));let Z=oe;return/\$t:[a-zA-Z0-9._-]+/.test(oe)&&(Z=l.resolveTranslations(oe,n,le)),Z.replace(new RegExp(`${_r}(\\d+)${_r}`,"g"),(be,me)=>ie[parseInt(me)])}if(q.startsWith("$t:defer:"))return q;if(/\$t:[a-zA-Z0-9._-]+/.test(q)){const ie=q.trim();return ie.startsWith("{")&&ie.endsWith("}")||ie.startsWith("[")&&ie.endsWith("]")?q:l.resolveTranslations(q,n,le)}return q}if(Array.isArray(q))return q.map(ie=>Ot(ie));if(q&&typeof q=="object"){const ie={};for(const[oe,Z]of Object.entries(q))ie[oe]=Ot(Z);return ie}return q},[l,n,le]),ut=I.useCallback(q=>/[?:|&!+\-*/<>=()]/.test(q),[]),Ct=I.useCallback(q=>{if(!q.startsWith("{{")||!q.endsWith("}}"))return null;const ie=q.slice(2,-2);let oe=null,Z=0;for(let be=0;be0?ie[be-1]:"")!=="\\"&&((me==='"'||me==="'")&&!oe?oe=me:me===oe&&(oe=null),!oe&&(me==="{"&&Z++,me==="}"&&Z--,Z<0)))return null}return Z===0?ie.trim():null},[]),vt=I.useMemo(()=>{let q={...A.props||{}};const ie=["cellChildren","expandChildren","expandContext","render"],Z=r.getMetadata(A.name)?.skipBindingKeys||[],be=[...new Set([...ie,...Z])],me=b?{skipCache:!0}:void 0,Te=Ae=>{if(me)return me;if(Ae.startsWith("_computed")||Ae.startsWith("$computed"))return{skipCache:!0}};for(const[Ae,Re]of Object.entries(q))if(!be.includes(Ae))if(typeof Re=="string"){const Ue=Ct(Re);if(Ue!==null){let ot=!1,At=Ue;Ue.startsWith(Na)&&(ot=!0,At=Ue.slice(Na.length));const Qt={componentId:A.id,componentName:A.name,propName:Ae};let Kt;if(ci(At))Kt=i.resolveBindings(`{{${At}}}`,le,me,Qt);else if(ut(At))try{Kt=i.evaluateExpression(At,le,Qt)}catch(Fn){ht.warn(`표현식 평가 실패 (컴포넌트: ${A.id}, prop: ${Ae}):`,Fn),Kt=void 0}else Kt=i.resolve(At,le,Te(At),Qt);q[Ae]=ot&&Kt!=null?Ji(Kt):Kt}else{const ot={componentId:A.id,componentName:A.name,propName:Ae};q[Ae]=i.resolveBindings(Re,le,me,ot)}}else Array.isArray(Re)?q[Ae]=Re.map(Ue=>typeof Ue=="string"?i.resolveBindings(Ue,le,me):typeof Ue=="object"&&Ue!==null?i.resolveObject(Ue,le,me):Ue):Re&&typeof Re=="object"&&(q[Ae]=i.resolveObject(Re,le,me));for(const[Ae,Re]of Object.entries(q))q[Ae]=Ot(Re);if(typeof q.style=="string"&&(q.style=L_(q.style)),q=fd(q,v?void 0:A.actions,le,{componentContext:de,actionDispatcher:u}),A.classMap){const Ae=gw(A.classMap,le,i,{skipCache:!0});if(Ae){const Re=q.className||"";q.className=Re?`${Re} ${Ae}`:Ae}}A.style&&(q.style=A.style),A.actions&&A.actions.length>0&&A.type==="composite"&&Object.assign(q,ue),A.component_layout&&(q.__componentLayoutDefs=A.component_layout);const xe=A.expandChildren||A.props?.expandChildren,je=A.props?.cellChildren,ze=A.children&&A.children.length>0,$e=A.component_layout,De=A.slot,Ye=A.props?.modalId,Ke=A.type==="composite";return(xe||je||ze||$e||De||Ye||Ke)&&(q.__componentContext=de),q},[A.props,A.actions,A.style,A.classMap,A.id,le,n,i,l,de,ue,Ot,ut,Ct,b]),kt=I.useRef(null),Ee=I.useMemo(()=>kt.current&&hm(kt.current,vt)?kt.current:(kt.current=vt,vt),[vt]),Nt=I.useMemo(()=>{const q=A.id;if(typeof q!="string"||!q.includes("{{"))return q;const ie=i.resolveBindings(q,le,{skipCache:!0});return typeof ie=="string"?ie:q},[A.id,le,i]),zt=I.useMemo(()=>{const q=A.dataKey,ie=A.trackChanges,oe=A.debounce;if(q){const Z=q.startsWith("_global."),be=Z?q.slice(8):q,me=Z?e._global:le._local;return{dataKey:be,trackChanges:ie??!1,debounce:oe,setState:Z?xe=>{e._globalSetState?e._globalSetState(xe):ht.warn("_global.formData 사용 시 _globalSetState가 필요합니다.")}:he,state:me??{},_isGlobal:Z}}if(ve.dataKey)return ve},[A.dataKey,A.trackChanges,A.debounce,A.id,he,le._local,e._global,e._globalSetState,ve]),qt=I.useRef(new Set);I.useEffect(()=>{qt.current.clear()},[e._global?._remountKeys]);const Lt=I.useCallback((q,ie)=>{const oe=e._global?._remountKeys;return q&&oe?.[q]&&!qt.current.has(q)?(qt.current.add(q),`${q}-remount-${oe[q]}`):q||ie},[e._global?._remountKeys]),_n=I.useMemo(()=>{if(A.text!==void 0){if(typeof A.text=="string"){const q=A.text;let ie=q;const oe={componentId:A.id,componentName:A.name,propName:"text",skipCache:!0},Z=Ct(q);if(Z!==null){let be=!1,me=Z;if(Z.startsWith(Na)&&(be=!0,me=Z.slice(Na.length)),ci(me))ie=i.resolveBindings(`{{${me}}}`,le,{skipCache:!0},oe);else if(ut(me))try{ie=i.evaluateExpression(me,le,oe)}catch(Te){ht.warn(`text 표현식 평가 실패 (컴포넌트: ${A.id}):`,Te),ie=""}else ie=i.resolve(me,le,{skipCache:!0},oe);be&&ie!=null&&(ie=Ji(ie))}else q.includes("{{")&&(ie=i.resolveBindings(q,le,{skipCache:!0},oe));return ie=Ot(ie),ie??""}return A.text}return!A.children||A.children.length===0?null:A.children.map((q,ie)=>{if(typeof q=="string")return q;const oe=A.__responsiveChildrenKey,Z=oe?`responsive.${oe}.children`:"children",be=O?`${O}.${Z}.${ie}`:`${ie}`,me=q._fromBase?q.id||`child-${ie}`:Lt(q.id,`child-${ie}`),Te=!q._fromBase&&M?`${me}_${M}`:me;return ft.jsx(ja,{componentDef:q,dataContext:le,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:u,parentComponentContext:de,parentFormContextProp:zt,isInsideIteration:b,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:be,onDragStart:x,onDragEnd:k,layoutKey:M},Te)})},[A.text,A.children,A.id,e,le,n,r,i,l,u,de,zt,Ct,ut,Ot,Lt,b,v,S,_,O,x,k,M]),hn=I.useMemo(()=>{if(!A.iteration)return null;try{const q=dd(A.iteration.source,le,i);if(!Array.isArray(q))return ht.warn(`iteration source가 배열이 아닙니다 (컴포넌트: ${A.id}):`,q),null;const ie=Za();return ie?.isEnabled()&&ie.trackIteration(A.id||`iteration-${Date.now()}`,A.iteration.source,A.iteration.item_var,A.iteration.index_var,q.length),q.map((oe,Z)=>{const be={...le,[A.iteration.item_var]:oe};A.iteration.index_var&&(be[A.iteration.index_var]=Z);const me=Z===0||Z===q.length-1;ie?.isEnabled()&&me&&ie.trackNestedContext?.({componentId:`${A.id||"unknown"}-iter-${Z}`,componentType:"iteration",parentContext:{available:Object.keys(le),values:{}},ownContext:{added:[A.iteration.item_var,...A.iteration.index_var?[A.iteration.index_var]:[]],values:{[A.iteration.item_var]:oe,...A.iteration.index_var?{[A.iteration.index_var]:Z}:{}}},depth:1});const Te={...A,iteration:void 0,responsive:void 0},xe=O?`${O}.iteration.${Z}`:`iteration.${Z}`,je=`${Lt(A.id,"item")}-${Z}`;return ft.jsx(ja,{componentDef:Te,dataContext:be,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:u,parentComponentContext:de,parentFormContextProp:zt,isInsideIteration:!0,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:xe,onDragStart:x,onDragEnd:k,layoutKey:M},je)})}catch(q){return ht.error(`iteration 렌더링 실패 (컴포넌트: ${A.id}):`,q),null}},[A.iteration,A.children,A.id,e,le,n,r,i,l,u,de,zt,Lt,v,S,_,O,x,k,M]),[Ba,Jt]=I.useState(0),Dr=I.useMemo(()=>{if(!A.sortable||!A.itemTemplate)return null;const{sortable:q,itemTemplate:ie}=A;try{const oe=dd(q.source,le,i);if(!Array.isArray(oe))return ht.warn(`sortable source가 배열이 아닙니다 (컴포넌트: ${A.id}):`,oe),null;if(oe.length===0)return null;const Z=q.itemKey||"id",be=q.strategy||"verticalList",me=q.itemVar||"$item",Te=q.indexVar||"$index",xe=q.handle,je=q.wrapperElement,ze=(Ye,Ke)=>{const Ae=A.actions?.filter(Re=>Re.event==="onSortEnd"||Re.type==="onSortEnd");if(Jt(Re=>Re+1),Ae&&Ae.length>0&&u){const Re={...le,$sortedItems:Ye,$sortEvent:Ke};Ae.forEach(Ue=>{try{const ot=u.createHandler(Ue,Re,de),At=new Event("sortend");ot(At)}catch(ot){ht.error(`onSortEnd action failed: ${Ue.handler}`,ot)}})}},$e=Ye=>{const Ke=A.actions?.filter(Ae=>Ae.event==="onSortStart"||Ae.type==="onSortStart");if(Ke&&Ke.length>0&&u){const Ae={...le,$activeId:Ye.activeId};Ke.forEach(Re=>{try{const Ue=u.createHandler(Re,Ae,de),ot=new Event("sortstart");Ue(ot)}catch(Ue){ht.error(`onSortStart action failed: ${Re.handler}`,Ue)}})}},De=oe.map((Ye,Ke)=>{const Ae=String(Ye[Z]),Re={...le,[me]:Ye,[Te]:Ke},Ue=O?`${O}.sortable.${Ke}`:`sortable.${Ke}`,ot=`${Lt(A.id,"sortable-item")}-v${Ba}-${Ae}`;return ft.jsx(D_,{id:Ae,handle:xe,...je?{as:je}:{},children:(je&&ie.children?ie.children:[ie]).map((At,Qt)=>ft.jsx(ja,{componentDef:At,dataContext:Re,translationContext:n,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:u,parentComponentContext:de,parentFormContextProp:void 0,isInsideIteration:!0,isEditMode:v,onComponentSelect:S,onComponentHover:_,componentPath:Ue,onDragStart:x,onDragEnd:k,layoutKey:M},`${ot}-child-${Qt}`))},ot)});return ft.jsx(T_,{items:oe,itemKey:Z,strategy:be,onSortEnd:ze,onSortStart:$e,sortVersion:Ba,children:De})}catch(oe){return ht.error(`sortable 렌더링 실패 (컴포넌트: ${A.id}):`,oe),null}},[A.sortable,A.itemTemplate,A.actions,A.id,e,le,n,r,i,l,u,de,Lt,v,S,_,O,x,k,Ba,M]),tr=I.useRef(!1);I.useEffect(()=>{if(tr.current)return;tr.current=!0;const q=Za();if(q?.isEnabled()&&q.trackMount(o.id,{name:o.name,type:o.type||"component",props:Ee}),o.lifecycle?.onMount&&o.lifecycle.onMount.length>0)for(const oe of o.lifecycle.onMount)try{const Z=u.createHandler(oe,le,de),be=new Event("mount");Z(be),ht.log(`Lifecycle onMount executed: ${oe.handler} (Component: ${o.id})`)}catch(Z){ht.error(`Lifecycle onMount failed: ${oe.handler} (Component: ${o.id})`,Z)}const ie=[];if(o.onComponentEvent&&o.onComponentEvent.length>0){const oe=window.G7Core;if(oe?.componentEvent?.on)for(const Z of o.onComponentEvent){if(!Z.event){ht.warn(`onComponentEvent: event name is required (Component: ${o.id})`);continue}const be=async Te=>{try{const xe={...le,_eventData:Te},ze=await u.createHandler({handler:Z.handler,params:Z.params,onSuccess:Z.onSuccess,onError:Z.onError},xe,de)(new CustomEvent(Z.event,{detail:Te}));return ht.log(`onComponentEvent "${Z.event}" handled: ${Z.handler} (Component: ${o.id})`,{eventData:Te,result:ze}),ze}catch(xe){throw ht.error(`onComponentEvent "${Z.event}" failed: ${Z.handler} (Component: ${o.id})`,xe),xe}},me=oe.componentEvent.on(Z.event,be);ie.push(me),ht.log(`onComponentEvent: Subscribed to "${Z.event}" (Component: ${o.id})`)}else ht.warn(`onComponentEvent: G7Core.componentEvent is not available (Component: ${o.id})`)}return()=>{const oe=Za();oe?.isEnabled()&&oe.trackUnmount(o.id);for(const Z of ie)try{Z()}catch(be){ht.error(`onComponentEvent: Failed to unsubscribe (Component: ${o.id})`,be)}if(ie.length>0&&ht.log(`onComponentEvent: Unsubscribed ${ie.length} event(s) (Component: ${o.id})`),o.lifecycle?.onUnmount&&o.lifecycle.onUnmount.length>0)for(const Z of o.lifecycle.onUnmount)try{const be=u.createHandler(Z,le,de),me=new Event("unmount");be(me),ht.log(`Lifecycle onUnmount executed: ${Z.handler} (Component: ${o.id})`)}catch(be){ht.error(`Lifecycle onUnmount failed: ${Z.handler} (Component: ${o.id})`,be)}}},[]);const An=I.useMemo(()=>{const q=Ee.dataKey,ie=Ee.trackChanges,oe=Ee.debounce;return q?{dataKey:q,trackChanges:ie??!1,debounce:oe,setState:he,state:ue}:null},[Ee.dataKey,Ee.trackChanges,Ee.debounce,he,ue]);I.useEffect(()=>{const q=A.dataKey||Ee.dataKey;if(!q)return;const ie=Za();if(!ie?.isEnabled())return;const oe=[],Z=me=>{if(me)for(const Te of me){const xe=Te?.props?.name;if(xe&&oe.push({name:xe,type:Te.name||Te.type||"unknown"}),Te?.children&&Z(Te.children),Te?.slots)for(const je of Object.values(Te.slots))Array.isArray(je)&&Z(je)}};if(o.children&&Z(o.children),o.slots)for(const me of Object.values(o.slots))Array.isArray(me)&&Z(me);const be=o.id||`form-${q}`;return ie.trackForm(be,q,oe),()=>{ie.untrackForm(be)}},[A,Ee.dataKey,o.id,o.children]);const sn=I.useRef(null),tt=I.useRef(void 0);I.useEffect(()=>{const q=Ee?.name;if(!q||!ve.dataKey||!ve.setState||Ee?.autoBinding===!1)return;const ie=`${ve.dataKey}.${q}`,oe=window.__g7AutoBindingPaths??new Map;return window.__g7AutoBindingPaths=oe,oe.set(ie,(oe.get(ie)??0)+1),()=>{const Z=oe.get(ie)??0;Z<=1?oe.delete(ie):oe.set(ie,Z-1)}},[Ee?.name,Ee?.autoBinding,ve.dataKey,ve.setState]);const na=I.useMemo(()=>{const{dataKey:q,trackChanges:ie,debounce:oe,...Z}=Ee,be=Z.name;if(!be||!ve.dataKey||!ve.setState)return Z;if(Z.autoBinding===!1){const{autoBinding:Ae,...Re}=Z;return Re}const me=`${ve.dataKey}.${be}`,Te=RC(ve.state,me),xe=Te!==void 0?Te:Z.value,ze=r.getMetadata(o.name)?.bindingType,$e=typeof xe=="boolean"&&(ze==="checked"||ze==="checkable"&&["checkbox","radio"].includes(Z?.type)),De=ve.debounce,Ye=Ae=>{const Re=ve.state||{},Ue=kC(Re,me,Ae);if(ve.trackChanges&&(Ue.hasChanges=!0),window.__g7PendingLocalState=Ue,ve.setState(Ue),ve._isGlobal)return;const ot=window.G7Core,At={[me]:Ae};ve.trackChanges&&(At.hasChanges=!0),ot?.state?.setLocal?.(At,{render:!1})},Ke=(Ae,Re)=>{De&&De>0?(sn.current&&clearTimeout(sn.current),sn.current=setTimeout(()=>{Ye(Ae),sn.current=null,Re&&(Re(),window.__g7PendingLocalState=null)},De)):(Ye(Ae),Re&&(Re(),window.__g7PendingLocalState=null))};if($e)return{...Z,checked:xe,onChange:Re=>{const Ue=Re.target.checked;Ye(Ue),Z.onChange&&Z.onChange(Re)}};{const Ae=Ue=>{const ot=Ue?.target?.value!==void 0?Ue.target.value:Ue;De&&De>0&&(tt.current=ot);const At=Ue?.target?{target:{value:ot,name:Ue.target.name,type:Ue.target.type,checked:Ue.target.checked},currentTarget:Ue.currentTarget?{value:ot,name:Ue.currentTarget.name}:void 0}:Ue;Ke(ot,()=>{Z.onChange&&Z.onChange(At)})};let Re;return tt.current!==void 0?sn.current!==null?Re=tt.current:String(xe??"")===String(tt.current)?(tt.current=void 0,Re=xe??""):Re=tt.current:Re=xe??"",{...Z,value:Re,onChange:Ae}}},[Ee,ve,ve.state,ve.debounce]),kn=I.useMemo(()=>{const q=Za();if(q?.isEnabled()&&q.trackRender(o.name),!Oe)return null;if(A.sortable&&A.itemTemplate)return Dr;if(A.iteration)return hn;if(A.type==="extension_point"){const{layout:ze,columns:$e,gap:De,className:Ye,...Ke}=A.props||{};let Ae=Ye||"";return ze==="grid"&&$e?Ae=`${Ae} grid grid-cols-${$e} gap-${De||4}`.trim():ze==="flex"&&(Ae=`${Ae} flex flex-col gap-${De||4}`.trim()),ft.jsx("div",{id:Nt,className:Ae||void 0,...Ke,children:_n})}const ie=r.getComponent(A.name);if(!ie)return ht.error(`컴포넌트를 찾을 수 없습니다: ${A.name} (ID: ${A.id})`,"componentDef:",JSON.stringify(A,null,2)),null;const oe=e._global?._remountKeys,Z={...na,id:na.id??Nt,...oe&&Object.keys(oe).length>0&&{__remountTrigger:JSON.stringify(oe)}};if(ge?.listeners&&ge.handle&&na["data-drag-handle"]!==void 0&&(Object.assign(Z,ge.listeners),Z.style={...Z.style,cursor:ge.isDragging?"grabbing":"grab"}),v){const ze=A.id||`auto_${A.name||A.type||"unknown"}_${Math.random().toString(36).substring(2,8)}`,$e=Ke=>{typeof Ke.preventDefault=="function"&&Ke.preventDefault(),S&&(Ke.stopPropagation(),S(ze,Ke))},De={"data-editor-id":ze,"data-editor-name":A.name,"data-editor-type":A.type,onClick:$e};O&&(De["data-editor-path"]=O);const Ye=A.__editorSlotName??A.slot;typeof Ye=="string"&&(De["data-editor-slot"]=Ye),_&&(De.onMouseMove=Ke=>{Ke.stopPropagation(),_(ze,Ke)},De.onMouseLeave=Ke=>{_(null,Ke)}),Object.assign(Z,De),Z.editorAttrs=De}const be=A.type==="basic",me=A.type==="layout",xe=be||me?(()=>{const ze={};for(const[$e,De]of Object.entries(Z))$e.startsWith("__")||$e==="editorAttrs"&&be||(ze[$e]=De);return ze})():Z;let je=ft.jsx(ie,{...xe,children:_n});if(zt&&(je=ft.jsx(xC,{value:zt,children:je})),A.isolatedState){const ze=typeof A.isolatedState=="object"?A.isolatedState:{},$e=e?._isolatedInit,De=$e?{...ze,...$e}:ze;je=ft.jsx(NC,{initialState:De,scopeId:A.isolatedScopeId,children:je})}return je},[Oe,A.iteration,A.sortable,A.itemTemplate,A.type,A.name,A.id,A.props,A.isolatedState,A.isolatedScopeId,r,na,_n,hn,Dr,ge,An,v,S,_,x,k,O,e._global?._remountKeys]),nr=I.useMemo(()=>{const q=A.blur_until_loaded;if(!q||e?._global?.__isPreview)return!1;if(typeof q=="string"){const Z=Ct(q);if(Z!==null)try{const be={componentId:A.id,componentName:A.name,propName:"blur_until_loaded"};return!!i.evaluateExpression(Z,le,be)}catch(be){return ht.warn(`blur_until_loaded 표현식 평가 실패 (컴포넌트: ${A.id}):`,be),!1}return!1}if(typeof q=="object"&&q!==null){if(!q.enabled)return!1;if(q.data_sources)return(Array.isArray(q.data_sources)?q.data_sources:[q.data_sources]).some(Te=>e[Te]===void 0);if(Q)return!0;const Z=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(me=>!Z.includes(me)&&!me.startsWith("_")).some(me=>e[me]===void 0)}if(Q)return!0;const ie=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(Z=>!ie.includes(Z)&&!Z.startsWith("_")).some(Z=>e[Z]===void 0)},[A.blur_until_loaded,A.id,Q,e,le,i,Ct]);if(nr){const q=A.blur_until_loaded;if(typeof q=="object"&&q?.data_sources){const oe=(Array.isArray(q.data_sources)?q.data_sources:[q.data_sources]).reduce((be,me)=>(be[me]=e[me]===void 0?"UNDEFINED":"LOADED",be),{}),Z=Object.keys(e).filter(be=>!be.startsWith("_"));ht.log(`[BLUR APPLIED] Component: ${A.id}, data_sources status:`,oe,"Available keys:",Z)}else ht.log(`[BLUR APPLIED] Component: ${A.id}, blur_until_loaded:`,q)}const _a=I.useMemo(()=>{if(!A.blur_until_loaded)return{wrapperClasses:"",innerClasses:""};const q=vt?.className||"";if(!q)return{wrapperClasses:"",innerClasses:""};const ie=[/^(sm:|md:|lg:|xl:|2xl:)?(col-span-|row-span-|col-start-|col-end-|row-start-|row-end-)/,/^(sm:|md:|lg:|xl:|2xl:)?(self-|place-self-|order-)/,/^(sm:|md:|lg:|xl:|2xl:)?(flex-shrink|flex-grow|flex-\d|basis-)/],oe=q.split(/\s+/).filter(Boolean),Z=[],be=[];for(const me of oe)ie.some(xe=>xe.test(me))?Z.push(me):be.push(me);return{wrapperClasses:Z.join(" "),innerClasses:be.join(" ")}},[A.blur_until_loaded,vt?.className]),on=window.__slotContextValue;return He&&on?.isEnabled?null:ft.jsx(M_,{componentId:o.id,componentName:o.name,children:A.blur_until_loaded?ft.jsx("div",{className:[_a.wrapperClasses,nr?"opacity-50 blur-sm transition-all duration-300 pointer-events-none":void 0].filter(Boolean).join(" ")||void 0,children:kn}):kn})});ja.displayName="DynamicRenderer",typeof window<"u"&&(window.__DynamicRenderer=ja);function $_(o){const e={};try{const r=new URL(o,typeof window<"u"?window.location.origin:"http://localhost").searchParams,i=new Set;for(const l of r.keys()){if(i.has(l))continue;i.add(l);const u=r.getAll(l);u.length>1||l.endsWith("[]")?e[l]=u:e[l]=u[0]}}catch{}return e}function N_(o){try{return new URL(o,typeof window<"u"?window.location.origin:"http://localhost").pathname}catch{return o.split("?")[0]}}const bi=class bi{constructor(){$(this,"config",{enabled:!1,maxHistorySize:100,logLevel:"info",serverEndpoint:"/_boost/g7-debug/dump-state",autoCapture:!0});$(this,"stateHistory",[]);$(this,"snapshotIdCounter",0);$(this,"stateWatchers",new Map);$(this,"currentLocalState",{});$(this,"currentComputedState",{});$(this,"currentParentContext");$(this,"actionHistory",[]);$(this,"actionWatchers",new Set);$(this,"cacheStats",{hits:0,misses:0,entries:0});$(this,"cacheDecisions",[]);$(this,"cacheDecisionIdCounter",0);$(this,"maxCacheDecisions",200);$(this,"currentRenderCycleId",null);$(this,"isInActionExecution",!1);$(this,"isInIteration",!1);$(this,"mountedComponents",new Map);$(this,"eventListeners",new Map);$(this,"mountWatchers",new Set);$(this,"unmountWatchers",new Set);$(this,"renderCounts",new Map);$(this,"bindingEvalCount",0);$(this,"profilingData",[]);$(this,"isProfiling",!1);$(this,"profilingStartTime",0);$(this,"activeRequests",new Map);$(this,"requestHistory",[]);$(this,"pendingDataSources",new Set);$(this,"requestWatchers",new Set);$(this,"wsConnections",new Map);$(this,"wsMessageHistory",[]);$(this,"wsMessageWatchers",new Set);$(this,"ifConditions",new Map);$(this,"iterations",new Map);$(this,"conditionWatchers",new Set);$(this,"forms",new Map);$(this,"formWatchers",new Set);$(this,"expressionHistory",[]);$(this,"expressionWatchers",new Set);$(this,"expressionIdCounter",0);$(this,"dataSources",new Map);$(this,"dataPathTransforms",[]);$(this,"maxDataPathTransforms",100);$(this,"nestedContexts",[]);$(this,"nestedContextIdCounter",0);$(this,"maxNestedContexts",100);$(this,"formBindingIssues",[]);$(this,"formBindingValidations",[]);$(this,"formBindingIssueIdCounter",0);$(this,"maxFormBindingIssues",100);$(this,"computedProperties",new Map);$(this,"computedRecalcLogs",[]);$(this,"computedRecalcIdCounter",0);$(this,"maxComputedRecalcLogs",100);$(this,"handlers",new Map);$(this,"componentEventSubscriptions",new Map);$(this,"componentEventEmitHistory",[]);$(this,"componentEventIdCounter",0);$(this,"maxComponentEventHistory",100);$(this,"stateRenderingLogs",[]);$(this,"currentStateChangeContext",null);$(this,"stateRenderingIdCounter",0);$(this,"componentRenderCounts",new Map);$(this,"stateToComponentMap",new Map);$(this,"maxStateRenderingLogs",100);$(this,"componentStateSources",new Map);$(this,"contextFlowNodes",new Map);$(this,"dynamicStates",new Map);$(this,"styleIssues",[]);$(this,"componentStyles",new Map);$(this,"authEvents",[]);$(this,"authHeaderHistory",[]);$(this,"authEventIdCounter",0);$(this,"maxAuthEventHistory",50);$(this,"logHistory",[]);$(this,"logIdCounter",0);$(this,"maxLogHistory",500);$(this,"currentLayout",null);$(this,"layoutHistory",[]);$(this,"layoutIdCounter",0);$(this,"layoutStats",{totalLoads:0,cacheHits:0,apiLoads:0});$(this,"maxLayoutHistory",50);$(this,"executionDetails",new Map);$(this,"stateChangeHistory",[]);$(this,"dataSourceChangeHistory",[]);$(this,"changeAlerts",[]);$(this,"changeExpectations",new Map);$(this,"executionIdCounter",0);$(this,"stateChangeIdCounter",0);$(this,"alertIdCounter",0);$(this,"maxExecutionDetails",100);$(this,"maxStateChangeHistory",200);$(this,"maxChangeAlerts",100);$(this,"sequenceExecutions",[]);$(this,"sequenceExecutionStack",[]);$(this,"sequenceIdCounter",0);$(this,"maxSequenceExecutions",50);$(this,"staleClosureWarnings",[]);$(this,"staleClosureIdCounter",0);$(this,"maxStaleClosureWarnings",100);$(this,"stateCaptureRegistry",new Map);$(this,"modalStates",new Map);$(this,"modalStateIssues",[]);$(this,"modalStateRelations",[]);$(this,"modalStateChangeLogs",[]);$(this,"modalStateIssueIdCounter",0);$(this,"modalStateChangeLogIdCounter",0);$(this,"maxModalStateIssues",100);$(this,"maxModalStateChangeLogs",200);$(this,"namedActionDefinitions",{});$(this,"namedActionRefLogs",[]);$(this,"namedActionRefIdCounter",0);$(this,"maxNamedActionRefLogs",200);$(this,"debounceActionHistory",[]);$(this,"dataSourceUpdateHistory",[]);$(this,"debounceActionIdCounter",0);$(this,"dataSourceUpdateIdCounter",0)}static getInstance(){return bi.instance||(bi.instance=new bi),bi.instance}initialize(){if(this.config.enabled=this.checkDebugMode(),!this.config.enabled){console.log("[G7DevTools] 비활성화됨 (환경설정 > 고급 설정 > 디버그 모드를 켜세요)");return}console.log("[G7DevTools] 활성화됨"),this.setupGlobalErrorHandler()}checkDebugMode(){try{if(window.G7Config?.debug===!0)return!0;const r=window.G7Core?.state?.get?.();return r?.settings?.advanced?.debug_mode===!0||r?._global?.settings?.advanced?.debug_mode===!0}catch{return!1}}isEnabled(){return this.config.enabled}setupGlobalErrorHandler(){window.addEventListener("error",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"globalError",startTime:Date.now(),status:"error",error:{name:"Error",message:e.message,stack:e.error?.stack}})}),window.addEventListener("unhandledrejection",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"unhandledRejection",startTime:Date.now(),status:"error",error:{name:"UnhandledRejection",message:String(e.reason)}})})}captureStateSnapshot(e){if(!this.config.enabled)return;const n={id:++this.snapshotIdCounter,timestamp:Date.now(),source:e.source,prev:this.sanitizeObject(e.prev),next:this.sanitizeObject(e.next),diff:e.diff};this.stateHistory.push(n),this.stateHistory.length>this.config.maxHistorySize&&this.stateHistory.shift(),this.notifyStateWatchers(n)}getState(){try{const e=window.G7Core,n=e?.state?.get?.()||{},r={};if(e?._isolatedStates&&typeof e._isolatedStates=="object")for(const[i,l]of Object.entries(e._isolatedStates))r[i]=this.sanitizeObject(l);return{_global:n._global||n,_local:this.currentLocalState,_computed:this.currentComputedState,_isolated:Object.keys(r).length>0?r:void 0,$parent:this.currentParentContext}}catch{return{_global:{},_local:this.currentLocalState,_computed:this.currentComputedState}}}updateLocalState(e){this.config.enabled&&(this.currentLocalState=this.sanitizeObject(e))}updateComputedState(e){this.config.enabled&&(this.currentComputedState=this.sanitizeObject(e))}updateParentContext(e){this.config.enabled&&(this.currentParentContext=e?this.sanitizeObject(e):void 0)}getStateHistory(){return[...this.stateHistory]}getDualStorageMismatch(){const e=this.currentLocalState||{};let n={};try{n=(window.G7Core?.state?.get?.()||{})._local||{}}catch{n={}}const r=this.findMismatchedLeafPaths(e,n);return{hasMismatch:r.length>0,mismatchedPaths:r,storageA:e,storageB:n}}findMismatchedLeafPaths(e,n,r=""){const i=[],l=new Set([...Object.keys(e||{}),...Object.keys(n||{})]);for(const u of l){const d=r?`${r}.${u}`:u,f=e?.[u],g=n?.[u],m=f&&typeof f=="object"&&!Array.isArray(f),b=g&&typeof g=="object"&&!Array.isArray(g);if(m&&b)i.push(...this.findMismatchedLeafPaths(f,g,d));else{const v=f===void 0?"__undefined__":JSON.stringify(f),S=g===void 0?"__undefined__":JSON.stringify(g);v!==S&&i.push(d)}}return i}watchState(e,n){return this.stateWatchers.has(e)||this.stateWatchers.set(e,new Set),this.stateWatchers.get(e).add(n),()=>{this.stateWatchers.get(e)?.delete(n)}}notifyStateWatchers(e){if(this.stateWatchers.get("*")?.forEach(n=>{try{n(e.next,e.prev,"*")}catch(r){console.error("[G7DevTools] State watcher error:",r)}}),e.diff)for(const n of e.diff.changed)this.stateWatchers.get(n.path)?.forEach(r=>{try{r(n.newValue,n.oldValue,n.path)}catch(i){console.error("[G7DevTools] State watcher error:",i)}})}diffSnapshots(e,n){const r=this.stateHistory.find(l=>l.id===e),i=this.stateHistory.find(l=>l.id===n);return!r||!i?null:this.calculateDiff(r.next,i.next)}calculateDiff(e,n,r=""){const i={added:[],removed:[],changed:[]},l=new Set(Object.keys(e)),u=new Set(Object.keys(n));for(const d of u){const f=r?`${r}.${d}`:d;l.has(d)?JSON.stringify(e[d])!==JSON.stringify(n[d])&&i.changed.push({path:f,oldValue:e[d],newValue:n[d]}):i.added.push(f)}for(const d of l){const f=r?`${r}.${d}`:d;u.has(d)||i.removed.push(f)}return i}logAction(e){if(!this.config.enabled)return;const n=this.actionHistory.findIndex(r=>r.id===e.id);n>=0?this.actionHistory[n]={...this.actionHistory[n],...e}:(this.actionHistory.push(e),this.actionHistory.length>this.config.maxHistorySize&&this.actionHistory.shift()),this.isProfiling&&this.profilingData.push({type:"action",action:e.type,timestamp:performance.now(),duration:e.duration}),this.notifyActionWatchers(e)}getActionHistory(){return[...this.actionHistory]}watchActions(e){return this.actionWatchers.add(e),()=>{this.actionWatchers.delete(e)}}notifyActionWatchers(e){this.actionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Action watcher error:",r)}})}getActionMetrics(){const e=this.actionHistory.filter(l=>l.status==="success"),n=this.actionHistory.filter(l=>l.status==="error"),r={};for(const l of this.actionHistory)r[l.type]=(r[l.type]||0)+1;const i=e.reduce((l,u)=>l+(u.duration||0),0);return{totalActions:this.actionHistory.length,successCount:e.length,errorCount:n.length,averageDuration:e.length>0?i/e.length:0,actionsByType:r}}recordCacheHit(){this.config.enabled&&this.cacheStats.hits++}recordCacheMiss(){this.config.enabled&&this.cacheStats.misses++}updateCacheEntries(e){this.config.enabled&&(this.cacheStats.entries=e)}getCacheStats(){const e=this.cacheStats.hits+this.cacheStats.misses;return{...this.cacheStats,hitRate:e>0?this.cacheStats.hits/e:0}}resetCacheStats(){this.cacheStats={hits:0,misses:0,entries:0}}trackCacheDecision(e){if(!this.config.enabled)return;const n={id:`cache-${++this.cacheDecisionIdCounter}`,timestamp:Date.now(),expression:e.expression,decision:e.decision,reason:e.reason,context:{isInIteration:this.isInIteration,isInAction:this.isInActionExecution,renderCycleId:this.currentRenderCycleId||void 0,componentId:e.componentId,skipCacheOption:e.skipCacheOption},cachedValue:e.cachedValue!==void 0?this.safeClone(e.cachedValue):void 0,freshValue:e.freshValue!==void 0?this.safeClone(e.freshValue):void 0,valueMatch:e.cachedValue!==void 0&&e.freshValue!==void 0?JSON.stringify(e.cachedValue)===JSON.stringify(e.freshValue):void 0,duration:e.duration};this.cacheDecisions.push(n),this.cacheDecisions.length>this.maxCacheDecisions&&this.cacheDecisions.shift(),e.decision==="cache_hit"?this.cacheStats.hits++:(e.decision==="cache_miss"||e.decision==="skip_cache")&&this.cacheStats.misses++}startRenderCycle(e){return this.currentRenderCycleId=`render-${Date.now()}-${e||"root"}`,this.currentRenderCycleId}endRenderCycle(){this.currentRenderCycleId=null}startActionContext(){this.isInActionExecution=!0}endActionContext(){this.isInActionExecution=!1}startIterationContext(){this.isInIteration=!0}endIterationContext(){this.isInIteration=!1}getCacheDecisionTrackingInfo(){const e=this.getCacheDecisionStats();return{decisions:[...this.cacheDecisions],stats:e,timestamp:Date.now()}}getCacheDecisionStats(){const e=this.cacheDecisions;let n=0,r=0,i=0,l=0;const u={},d={};for(const g of e)g.decision==="cache_hit"?n++:g.decision==="cache_miss"?r++:g.decision==="skip_cache"?i++:g.decision==="invalidate"&&l++,u[g.reason]=(u[g.reason]||0)+1,g.context.componentId&&(d[g.context.componentId]=(d[g.context.componentId]||0)+1);const f=n+r+i;return{totalDecisions:e.length,cacheHits:n,cacheMisses:r,skipCacheCount:i,invalidateCount:l,byReason:u,byComponent:d,avgHitRate:f>0?n/f:0}}getRecentCacheDecisions(e=20,n){let r=[...this.cacheDecisions];return n&&(r=r.filter(i=>i.decision===n)),r.slice(-e)}clearCacheDecisionData(){this.cacheDecisions=[],this.cacheDecisionIdCounter=0}summarizePropsForLifecycle(e){if(!e||typeof e!="object")return e;const n={},r=["children","ref","key"];for(const[i,l]of Object.entries(e))if(!r.includes(i))if(l==null)n[i]=l;else if(Array.isArray(l))n[i]=`[Array(${l.length})]`;else if(typeof l=="object"){const u=Object.keys(l);u.length<=3?n[i]=`{${u.join(", ")}}`:n[i]=`{${u.slice(0,3).join(", ")}, ... +${u.length-3}}`}else typeof l=="string"&&l.length>50?n[i]=l.substring(0,50)+"...":typeof l=="function"?n[i]="[Function]":n[i]=l;return n}trackMount(e,n){if(!this.config.enabled)return;const r=this.summarizePropsForLifecycle(n.props),i={...n,props:r,id:e,mountTime:Date.now()};this.mountedComponents.set(e,i),this.mountWatchers.forEach(l=>{try{l({componentId:e,componentName:n.name,timestamp:i.mountTime})}catch(u){console.error("[G7DevTools] Mount watcher error:",u)}})}trackUnmount(e){if(!this.config.enabled)return;const n=this.mountedComponents.get(e),r=this.eventListeners.get(e)||[];r.length>0&&console.warn(`[G7DevTools] 컴포넌트 ${e} 언마운트 시 ${r.length}개 리스너 미정리`),n&&this.unmountWatchers.forEach(i=>{try{i({componentId:e,componentName:n.name,timestamp:Date.now(),orphanedListeners:r.length})}catch(l){console.error("[G7DevTools] Unmount watcher error:",l)}}),this.mountedComponents.delete(e)}trackListener(e,n,r){if(!this.config.enabled)return;const i=this.eventListeners.get(e)||[];i.push({type:n,target:r,addedAt:Date.now()}),this.eventListeners.set(e,i)}removeListener(e,n){if(!this.config.enabled)return;const r=this.eventListeners.get(e)||[],i=r.findIndex(l=>l.type===n);i>=0&&r.splice(i,1)}getMountedComponents(){return Array.from(this.mountedComponents.values())}getOrphanedListeners(){const e=[];for(const[n,r]of this.eventListeners)this.mountedComponents.has(n)||e.push(...r.map(i=>({...i,componentId:n})));return e}watchMount(e){return this.mountWatchers.add(e),()=>{this.mountWatchers.delete(e)}}watchUnmount(e){return this.unmountWatchers.add(e),()=>{this.unmountWatchers.delete(e)}}trackRender(e){if(!this.config.enabled)return;const n=this.renderCounts.get(e)||0;this.renderCounts.set(e,n+1),this.isProfiling&&this.profilingData.push({type:"render",component:e,timestamp:performance.now()})}trackBindingEval(e){this.config.enabled&&(this.bindingEvalCount++,this.isProfiling&&e&&this.profilingData.push({type:"binding",expression:e,timestamp:performance.now()}))}getRenderCount(){return new Map(this.renderCounts)}getBindingEvalCount(){return this.bindingEvalCount}getMemoryWarnings(){const e=[];try{const i=this.getState(),l=JSON.stringify(i).length;l>1e6&&e.push({type:"large-state",message:`상태 크기가 ${(l/1024/1024).toFixed(2)}MB입니다`,suggestion:"불필요한 데이터 정리 필요",severity:l>5e6?"error":"warning"})}catch{}this.stateHistory.length>this.config.maxHistorySize*.8&&e.push({type:"large-history",message:`상태 이력이 ${this.stateHistory.length}개입니다`,suggestion:"maxHistory 설정 조정 필요",severity:"warning"});const n=this.getOrphanedListeners().length;n>0&&e.push({type:"orphaned-listeners",message:`${n}개의 정리되지 않은 이벤트 리스너`,suggestion:"useEffect cleanup 또는 removeEventListener 확인",severity:n>10?"error":"warning"});const r=Array.from(this.renderCounts.entries()).filter(([,i])=>i>50);return r.length>0&&e.push({type:"excessive-renders",message:`${r.length}개 컴포넌트가 50회 이상 렌더링`,suggestion:"React.memo, useMemo, useCallback 사용 고려",severity:"warning"}),e}startProfiling(){this.config.enabled&&(this.isProfiling=!0,this.profilingData=[],this.profilingStartTime=performance.now(),console.log("[G7DevTools] 프로파일링 시작"))}stopProfiling(){this.isProfiling=!1;const e=performance.now()-this.profilingStartTime,n=this.analyzeProfile(e);return console.log("[G7DevTools] 프로파일링 완료",n),n}analyzeProfile(e){const n=this.profilingData.filter(g=>g.type==="render"),r=this.profilingData.filter(g=>g.type==="binding"),i=this.profilingData.filter(g=>g.type==="action"),l={};for(const g of n)g.component&&(l[g.component]||(l[g.component]={count:0,durations:[]}),l[g.component].count++,g.duration&&l[g.component].durations.push(g.duration));const u=Object.entries(l).map(([g,m])=>({name:g,renderCount:m.count,avgDuration:m.durations.length>0?m.durations.reduce((b,v)=>b+v,0)/m.durations.length:0})).sort((g,m)=>m.renderCount-g.renderCount).slice(0,10),d={};for(const g of r)g.expression&&(d[g.expression]=(d[g.expression]||0)+1);const f=Object.entries(d).sort(([,g],[,m])=>m-g).slice(0,10).map(([g])=>g);return{duration:e,entries:this.profilingData,summary:{totalRenders:n.length,totalBindings:r.length,totalActions:i.length,slowestComponents:u,hotPaths:f}}}resetPerformanceStats(){this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[]}trackRequest(e,n,r){if(!this.config.enabled)return"";const i=this.generateId(),l=N_(e),u=$_(e);return this.activeRequests.set(i,{id:i,url:l,fullUrl:e,queryParams:u,method:n,startTime:Date.now(),status:"pending",requestBody:r?.requestBody?this.sanitizeObject(r.requestBody):void 0,dataSourceId:r?.dataSourceId}),this.isProfiling&&this.profilingData.push({type:"network",timestamp:performance.now()}),i}completeRequest(e,n,r){if(!this.config.enabled)return;const i=this.activeRequests.get(e);if(!i)return;const l={...i,status:n>=200&&n<300?"success":"error",statusCode:n,duration:Date.now()-i.startTime,endTime:Date.now(),response:this.sanitizeObject(r)};this.requestHistory.push(l),this.activeRequests.delete(e),this.requestHistory.length>this.config.maxHistorySize&&this.requestHistory.shift(),this.notifyRequestWatchers(l)}failRequest(e,n){if(!this.config.enabled)return;const r=this.activeRequests.get(e);if(!r)return;const i={...r,status:"error",error:n,duration:Date.now()-r.startTime,endTime:Date.now()};this.requestHistory.push(i),this.activeRequests.delete(e),this.notifyRequestWatchers(i)}trackDataSource(e){this.config.enabled&&this.pendingDataSources.add(e)}completeDataSource(e){this.config.enabled&&this.pendingDataSources.delete(e)}getActiveRequests(){return Array.from(this.activeRequests.values())}getRequestHistory(){return[...this.requestHistory]}getPendingDataSources(){return Array.from(this.pendingDataSources)}watchRequest(e){return this.requestWatchers.add(e),()=>{this.requestWatchers.delete(e)}}notifyRequestWatchers(e){this.requestWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Request watcher error:",r)}})}trackWebSocketConnection(e,n,r){this.config.enabled&&this.wsConnections.set(e,{id:e,url:n,state:r,connectedAt:r==="open"?Date.now():void 0})}trackWebSocketMessage(e,n,r,i){if(!this.config.enabled)return;const l={id:this.generateId(),connectionId:e,direction:n,type:r,payload:this.sanitizeObject(i),timestamp:Date.now(),sequence:this.wsMessageHistory.length};this.wsMessageHistory.push(l),this.wsMessageHistory.length>this.config.maxHistorySize&&this.wsMessageHistory.shift(),this.notifyWebSocketWatchers(l)}getWebSocketConnections(){return Array.from(this.wsConnections.values())}getWebSocketMessageHistory(){return[...this.wsMessageHistory]}getWebSocketConnectionState(){const e=Array.from(this.wsConnections.values());return e.some(n=>n.state==="open")?"connected":e.some(n=>n.state==="connecting")?"reconnecting":"disconnected"}watchWebSocketMessage(e){return this.wsMessageWatchers.add(e),()=>{this.wsMessageWatchers.delete(e)}}notifyWebSocketWatchers(e){this.wsMessageWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] WebSocket watcher error:",r)}})}trackIfCondition(e,n,r){if(!this.config.enabled)return;const i=this.ifConditions.get(e),l=i?.evaluatedValue;this.ifConditions.set(e,{id:e,expression:n,evaluatedValue:r,evaluationCount:(i?.evaluationCount||0)+1,lastEvaluated:Date.now()}),i&&l!==r&&this.notifyConditionWatchers({id:e,expression:n,oldValue:l,newValue:r,timestamp:Date.now()})}trackIteration(e,n){this.config.enabled&&this.iterations.set(e,{id:e,...n,lastRendered:Date.now()})}getIfConditions(){return Array.from(this.ifConditions.values())}getIterations(){return Array.from(this.iterations.values())}getScopeChain(e){const n=[],r=this.getState();return n.push({name:"_global",value:r._global,source:"global"}),n.push({name:"_local",value:r._local,source:"local"}),n.push({name:"_computed",value:r._computed,source:"computed"}),n}watchConditionChange(e){return this.conditionWatchers.add(e),()=>{this.conditionWatchers.delete(e)}}notifyConditionWatchers(e){this.conditionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Condition watcher error:",r)}})}trackForm(e,n,r){this.config.enabled&&this.forms.set(e,{id:e,dataKey:n,inputs:r,trackedAt:Date.now()})}trackFormChange(e,n,r){if(!this.config.enabled)return;const i={formId:e,inputName:n,value:r,timestamp:Date.now()};this.notifyFormWatchers(i)}untrackForm(e){this.forms.delete(e)}getForms(){return Array.from(this.forms.values())}getFormState(e){const n=this.getState();return this.getNestedValue(n,e)||{}}getBindingPath(e,n){return`${e}.${n}`}watchFormChange(e){return this.formWatchers.add(e),()=>{this.formWatchers.delete(e)}}notifyFormWatchers(e){this.formWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Form watcher error:",r)}})}trackFormBindingIssue(e,n,r){if(!this.config.enabled)return;const i={id:`form-issue-${++this.formBindingIssueIdCounter}`,timestamp:Date.now(),type:e,severity:n,formId:r.formId,formDataKey:r.formDataKey,inputInfo:r.inputInfo,contextInfo:r.contextInfo,description:r.description,suggestion:r.suggestion,docLink:r.docLink};this.formBindingIssues.push(i),this.formBindingIssues.length>this.maxFormBindingIssues&&this.formBindingIssues.shift()}validateFormBinding(e,n,r,i){const l=r.map(f=>{const g=[];let m=!0;return f.name||(g.push("Input에 name 속성이 없습니다"),m=!1,this.trackFormBindingIssue("missing-input-name","error",{formId:e,formDataKey:n,inputInfo:{name:f.name,type:f.type},description:"Input에 name 속성이 없어 자동 바인딩이 동작하지 않습니다",suggestion:"Input에 name 속성을 추가하세요",docLink:"troubleshooting-components-form.md"})),n||(g.push("Form에 dataKey가 없습니다"),m=!1),{inputName:f.name||"(unnamed)",inputType:f.type,bindingPath:n&&f.name?`${n}.${f.name}`:"-",isValid:m,currentValue:f.value,issues:g}});if(n||this.trackFormBindingIssue("missing-datakey","error",{formId:e,description:"Form에 dataKey가 설정되지 않아 자동 바인딩이 동작하지 않습니다",suggestion:"Form 컴포넌트에 dataKey props를 설정하세요",docLink:"troubleshooting-components-form.md"}),i.isContextBroken){const f=i.breakReason?.includes("sortable")?"sortable-context-break":i.breakReason?.includes("modal")?"modal-context-isolation":"context-not-propagated";this.trackFormBindingIssue(f,"warning",{formId:e,formDataKey:n,contextInfo:{hasParentFormContext:i.hasParentContext,parentFormContextProp:i.parentContextProp,isInsideSortable:i.breakReason?.includes("sortable")||!1,isInsideModal:i.breakReason?.includes("modal")||!1,depth:0},description:i.breakReason||"Form 컨텍스트가 제대로 전파되지 않았습니다",suggestion:"parentFormContextProp={undefined}를 확인하거나 중첩 구조를 검토하세요",docLink:"troubleshooting-components-form.md"})}const u={formId:e,dataKey:n,timestamp:Date.now(),contextPropagation:i,inputBindings:l,isValid:l.every(f=>f.isValid)&&!!n&&!i.isContextBroken,issueCount:l.filter(f=>!f.isValid).length+(n?0:1)+(i.isContextBroken?1:0)},d=this.formBindingValidations.findIndex(f=>f.formId===e);return d>=0?this.formBindingValidations[d]=u:this.formBindingValidations.push(u),u}getFormBindingValidationTrackingInfo(){const e=this.getFormBindingValidationStats();return{issues:[...this.formBindingIssues],validations:[...this.formBindingValidations],stats:e,timestamp:Date.now()}}getFormBindingValidationStats(){const e={"missing-datakey":0,"missing-input-name":0,"context-not-propagated":0,"sortable-context-break":0,"modal-context-isolation":0,"duplicate-input-name":0,"value-type-mismatch":0,"binding-path-invalid":0},n={info:0,warning:0,error:0};for(const i of this.formBindingIssues)e[i.type]++,n[i.severity]++;const r=Object.entries(e).filter(([,i])=>i>0).sort((i,l)=>l[1]-i[1]).slice(0,5).map(([i,l])=>({type:i,count:l,description:this.getIssueTypeDescription(i)}));return{totalFormsValidated:this.formBindingValidations.length,validForms:this.formBindingValidations.filter(i=>i.isValid).length,formsWithIssues:this.formBindingValidations.filter(i=>!i.isValid).length,totalIssues:this.formBindingIssues.length,byIssueType:e,bySeverity:n,topIssues:r}}getIssueTypeDescription(e){return{"missing-datakey":"Form에 dataKey 누락","missing-input-name":"Input에 name 속성 누락","context-not-propagated":"Form 컨텍스트 전파 실패","sortable-context-break":"Sortable 컨테이너에서 컨텍스트 단절","modal-context-isolation":"모달에서 부모 Form 컨텍스트 격리","duplicate-input-name":"동일 Form 내 중복 Input name","value-type-mismatch":"값 타입과 Input 타입 불일치","binding-path-invalid":"바인딩 경로 유효하지 않음"}[e]||e}clearFormBindingIssues(){this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0}trackComputedProperty(e,n,r,i,l,u,d){if(!this.config.enabled)return;const f=`computed-${e}-${u||"global"}`,g={id:f,name:e,expression:n,componentId:u,dependencies:r,currentValue:this.safeClone(i),lastComputedAt:Date.now(),computationTime:l,error:d};this.computedProperties.set(f,g)}trackComputedRecalc(e,n,r,i,l,u,d){if(!this.config.enabled)return;const f=`computed-${e}-${d||"global"}`,g=JSON.stringify(r)!==JSON.stringify(i),m={id:`recalc-${++this.computedRecalcIdCounter}`,computedId:f,computedName:e,timestamp:Date.now(),trigger:n,triggeredBy:u,previousValue:this.safeClone(r),newValue:this.safeClone(i),valueChanged:g,computationTime:l,cascadeCount:0};this.computedRecalcLogs.push(m),this.computedRecalcLogs.length>this.maxComputedRecalcLogs&&this.computedRecalcLogs.shift();const b=this.computedProperties.get(f);b&&(b.currentValue=this.safeClone(i),b.lastComputedAt=Date.now(),b.computationTime=l)}analyzeComputedDependencyChain(e,n){const r=new Set,i=[];let l=!1,u,d=0;const f=(m,b)=>{if(d=Math.max(d,b),r.has(m))return l=!0,u=[...i,m],{name:m,type:"computed",children:[],depth:b};r.add(m),i.push(m);const v=this.computedProperties.get(`computed-${m}-${n||"global"}`),S=[];if(v)for(const _ of v.dependencies)_.type==="computed"?S.push(f(_.path,b+1)):S.push({name:_.path,type:_.type,children:[],depth:b+1});return i.pop(),r.delete(m),{name:m,type:"computed",children:S,depth:b}},g=f(e,0);return{root:e,tree:g,hasCycle:l,cyclePath:u,maxDepth:d}}getComputedDependencyTrackingInfo(){const e=Array.from(this.computedProperties.values()),n=this.getComputedDependencyStats(),r=[];for(const i of e){const l=this.analyzeComputedDependencyChain(i.name,i.componentId);r.push(l)}return{properties:e,recalcLogs:[...this.computedRecalcLogs],dependencyChains:r,stats:n,timestamp:Date.now()}}getComputedDependencyStats(){const e={"state-change":0,"datasource-update":0,"dependency-change":0,manual:0,initial:0},n={},r={};let i=0,l=0;for(const g of this.computedRecalcLogs)e[g.trigger]++,g.valueChanged||l++,n[g.computedName]=(n[g.computedName]||0)+1,r[g.computedName]||(r[g.computedName]=[]),r[g.computedName].push(g.computationTime),i+=g.computationTime;const u=Object.entries(n).sort((g,m)=>m[1]-g[1]).slice(0,5).map(([g,m])=>({name:g,count:m})),d=Object.entries(r).map(([g,m])=>({name:g,avgTime:m.reduce((b,v)=>b+v,0)/m.length})).sort((g,m)=>m.avgTime-g.avgTime).slice(0,5);let f=0;for(const g of this.computedProperties.values())this.analyzeComputedDependencyChain(g.name,g.componentId).hasCycle&&f++;return{totalComputed:this.computedProperties.size,totalRecalculations:this.computedRecalcLogs.length,unnecessaryRecalculations:l,avgComputationTime:this.computedRecalcLogs.length>0?i/this.computedRecalcLogs.length:0,topRecalculated:u,slowestComputed:d,cycleDetectionCount:f,byTrigger:e}}clearComputedTracking(){this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0}trackModalOpen(e){if(!this.config.enabled)return;const n={modalId:e.modalId,modalName:e.modalName,openedAt:Date.now(),closedAt:null,scopeType:e.scopeType||"isolated",parentModalId:e.parentModalId,componentId:e.componentId,initialState:e.initialState?{...e.initialState}:{},currentState:e.initialState?{...e.initialState}:{},stateChangeCount:0,isolatedStateKeys:e.isolatedStateKeys||[],sharedStateKeys:e.sharedStateKeys||[]};this.modalStates.set(e.modalId,n),this.validateModalDefinitionExists(e.modalId,e.modalName),e.parentModalId&&(this.modalStateRelations.find(i=>i.parentModalId===e.parentModalId&&i.childModalId===e.modalId)||this.modalStateRelations.push({parentModalId:e.parentModalId,childModalId:e.modalId,sharedKeys:e.sharedStateKeys||[],isolatedKeys:e.isolatedStateKeys||[],relationType:"parent-child"}))}trackModalClose(e,n){if(!this.config.enabled)return;const r=this.modalStates.get(e);r&&(r.closedAt=Date.now(),n&&(r.currentState={...n}),this.detectStateLeakage(r))}validateModalDefinitionExists(e,n){const r=this.currentLayout;if(!r?.layoutJson)return;const i=r.layoutJson.modals;if(!i){this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 현재 레이아웃에 modals 섹션이 없습니다. 레이아웃 경로: ${r.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 섹션에 id="${e}" 정의 존재`,actualValue:"modals 섹션 없음"});return}const l=Array.isArray(i)?i:Object.values(i);l.some(d=>d?.id===e||d?.props?.id===e)||this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:n,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 렌더링된 레이아웃의 modals 섹션에 해당 ID의 정의가 없습니다. partial 로딩 실패, 레이아웃 병합 문제, extends 상속 누락 등을 확인하세요. 레이아웃 경로: ${r.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 배열에 id="${e}" 항목 존재`,actualValue:`modals 배열에 ${l.length}개 모달 정의 (${l.map(d=>d?.id||"unknown").join(", ")})`})}trackModalStateChange(e){if(!this.config.enabled)return;const n=this.modalStates.get(e.modalId);if(!n)return;n.stateChangeCount++,n.currentState[e.stateKey]=e.newValue;const r=this.checkIsolationViolation(n,e.stateKey),i={id:`modal-change-${++this.modalStateChangeLogIdCounter}`,modalId:e.modalId,modalName:n.modalName,timestamp:Date.now(),stateKey:e.stateKey,previousValue:e.previousValue,newValue:e.newValue,changeSource:e.changeSource||"user-action",violatesIsolation:r};this.modalStateChangeLogs.push(i),this.modalStateChangeLogs.length>this.maxModalStateChangeLogs&&this.modalStateChangeLogs.shift(),r&&this.recordModalStateIssue({type:"isolation-violation",modalId:e.modalId,modalName:n.modalName,severity:"warning",description:`모달 '${n.modalName}'에서 격리된 상태 키 '${e.stateKey}'가 변경됨`,affectedStateKeys:[e.stateKey]})}detectStateLeakage(e){if(e.scopeType==="isolated")for(const n of e.isolatedStateKeys){const r=e.initialState[n],i=e.currentState[n];if(r!==i&&e.parentModalId){const l=this.modalStates.get(e.parentModalId);l&&l.currentState.hasOwnProperty(n)&&this.recordModalStateIssue({type:"state-leakage",modalId:e.modalId,modalName:e.modalName,severity:"error",description:`모달 닫힘 후 상태 '${n}'가 부모 모달로 유출될 수 있음`,affectedStateKeys:[n],leakedValue:i,expectedValue:r})}}}checkIsolationViolation(e,n){return e.scopeType==="isolated",!1}recordModalStateIssue(e){const n={id:`modal-issue-${++this.modalStateIssueIdCounter}`,type:e.type,modalId:e.modalId,modalName:e.modalName,timestamp:Date.now(),severity:e.severity,description:e.description,affectedStateKeys:e.affectedStateKeys,leakedValue:e.leakedValue,expectedValue:e.expectedValue,actualValue:e.actualValue,stackInfo:e.stackInfo};this.modalStateIssues.push(n),this.modalStateIssues.length>this.maxModalStateIssues&&this.modalStateIssues.shift()}getModalStateScopeTrackingInfo(){const e=Array.from(this.modalStates.values()),n=this.getModalStateScopeStats();return{modals:e,issues:[...this.modalStateIssues],relations:[...this.modalStateRelations],changeLogs:[...this.modalStateChangeLogs],stats:n,timestamp:Date.now()}}getModalStateScopeStats(){const e=Array.from(this.modalStates.values()),n={warning:0,error:0},r={"state-leakage":0,"isolation-violation":0,"parent-mutation":0,"orphaned-state":0,"scope-mismatch":0,"cleanup-failure":0,"missing-definition":0};for(const l of this.modalStateIssues)n[l.severity]++,r[l.type]++;const i={isolated:0,shared:0,inherited:0};for(const l of e)i[l.scopeType]++;return{totalModals:e.length,openModals:e.filter(l=>l.closedAt===null).length,nestedModals:e.filter(l=>l.parentModalId!==void 0).length,totalIssues:this.modalStateIssues.length,issuesBySeverity:n,issuesByType:r,byScope:i,leakageDetectionCount:r["state-leakage"],cleanupFailureCount:r["cleanup-failure"]}}clearModalStateScopeTracking(){this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0}setNamedActionDefinitions(e){this.config.enabled&&(this.namedActionDefinitions=e||{})}trackNamedActionRef(e){if(!this.config.enabled)return;const n={...e,id:`named_action_ref_${++this.namedActionRefIdCounter}`};this.namedActionRefLogs.push(n),this.namedActionRefLogs.length>this.maxNamedActionRefLogs&&(this.namedActionRefLogs=this.namedActionRefLogs.slice(-this.maxNamedActionRefLogs))}getNamedActionTrackingInfo(){const e={};for(const r of this.namedActionRefLogs)e[r.actionRefName]=(e[r.actionRefName]||0)+1;const n=Object.keys(this.namedActionDefinitions).filter(r=>!e[r]);return{definitions:this.namedActionDefinitions,refLogs:[...this.namedActionRefLogs],stats:{totalDefinitions:Object.keys(this.namedActionDefinitions).length,totalRefs:this.namedActionRefLogs.length,refCountByName:e,unusedDefinitions:n},timestamp:Date.now()}}clearNamedActionTracking(){this.namedActionDefinitions={},this.namedActionRefLogs=[],this.namedActionRefIdCounter=0}trackExpressionEval(e){if(!this.config.enabled)return;const n={...e,id:`expr-${++this.expressionIdCounter}`,timestamp:Date.now(),warning:this.detectExpressionWarning(e)};this.expressionHistory.push(n),this.expressionHistory.length>500&&this.expressionHistory.shift(),this.isProfiling&&this.profilingData.push({type:"binding",expression:e.expression,timestamp:performance.now(),duration:e.duration}),this.notifyExpressionWatchers(n)}detectExpressionWarning(e){const{expression:n,result:r,resultType:i,method:l,duration:u}=e;if(i==="undefined"&&!n.includes("??")&&!n.includes("||")&&/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(n.replace(/{{|}}/g,"").trim()))return{type:"undefined-result",message:"표현식 결과가 undefined입니다",suggestion:`데이터가 로드되었는지 확인하거나 fallback 값을 추가하세요: ${n} ?? ''`};if(i==="null")return{type:"null-result",message:"표현식 결과가 null입니다",suggestion:`nullish coalescing 연산자를 사용하세요: ${n} ?? ''`};if(l==="resolveBindings"&&i==="string"){const d=String(r);if(d.includes("[object Object]")||/^\[.*\]$/.test(d))return{type:"array-to-string",message:"배열/객체가 문자열로 변환되었습니다",suggestion:"evaluateExpression()을 사용하여 원본 타입을 유지하세요"}}if(l==="resolveBindings"&&i==="string"&&String(r).includes("[object Object]"))return{type:"object-to-string",message:"객체가 [object Object]로 변환되었습니다",suggestion:"evaluateExpression()을 사용하거나 특정 속성에 접근하세요"};if(i==="undefined"&&n.includes(".")&&!n.includes("?.")){const d=n.replace(/{{|}}/g,"").trim().split(".");if(d.length>=2)return{type:"missing-optional-chain",message:"Optional chaining(?.) 누락 가능성",suggestion:`안전한 접근을 위해 ?. 사용을 고려하세요: ${d.join("?.")}`}}if((n.includes("{{item.")||n.includes("{{index}}"))&&Array.from(this.iterations.values()).some(g=>g.itemVar!=="item"))return{type:"wrong-iteration-var",message:"'item'/'index' 대신 iteration에서 정의한 변수명을 사용해야 합니다",suggestion:"iteration의 item_var/index_var에 정의된 변수명을 확인하세요"};if(u&&u>10)return{type:"slow-evaluation",message:`표현식 평가에 ${u.toFixed(2)}ms 소요됨`,suggestion:"복잡한 표현식을 단순화하거나 computed 값으로 분리하세요"}}getExpressions(){return[...this.expressionHistory]}getExpressionWarnings(){return this.expressionHistory.filter(e=>e.warning!=null)}searchExpressions(e){const n=e.toLowerCase();return this.expressionHistory.filter(r=>r.expression.toLowerCase().includes(n)||r.componentName?.toLowerCase().includes(n)||r.propName?.toLowerCase().includes(n))}getExpressionStats(){const e=this.expressionHistory,n=new Set(e.map(f=>f.expression)),r=e.filter(f=>f.warning!=null),i=e.filter(f=>f.fromCache),l=e.filter(f=>f.duration!=null).map(f=>f.duration),u={};for(const f of e)u[f.resultType]=(u[f.resultType]||0)+1;const d={};for(const f of r)f.warning&&(d[f.warning.type]=(d[f.warning.type]||0)+1);return{totalEvaluations:e.length,uniqueExpressions:n.size,warningCount:r.length,cacheHitRate:e.length>0?i.length/e.length:0,averageDuration:l.length>0?l.reduce((f,g)=>f+g,0)/l.length:0,byType:u,byWarning:d}}clearExpressions(){this.expressionHistory=[],this.expressionIdCounter=0}watchExpressions(e){return this.expressionWatchers.add(e),()=>{this.expressionWatchers.delete(e)}}notifyExpressionWatchers(e){this.expressionWatchers.forEach(n=>{try{n(e)}catch(r){console.error("[G7DevTools] Expression watcher error:",r)}})}setLogLevel(e){this.config.logLevel=e}setMaxHistory(e){this.config.maxHistorySize=Math.max(10,Math.min(1e3,e))}setServerEndpoint(e){this.config.serverEndpoint=e}getConfig(){return{...this.config}}generateId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}sanitizeObject(e,n=new WeakSet){if(e===null||typeof e!="object")return e;if(n.has(e))return"[Circular]";if(n.add(e),Array.isArray(e))return e.map(i=>this.sanitizeObject(i,n));const r={};for(const i of Object.keys(e))try{r[i]=this.sanitizeObject(e[i],n)}catch{r[i]="[Unable to serialize]"}return r}getNestedValue(e,n){if(!n)return e;const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}getLifecycleInfo(){return{mountedComponents:Array.from(this.mountedComponents.values()),orphanedListeners:this.getOrphanedListeners()}}getPerformanceInfo(){return{renderCounts:this.renderCounts,bindingEvalCount:this.bindingEvalCount,memoryWarnings:this.getMemoryWarnings()}}getNetworkInfo(){return{activeRequests:Array.from(this.activeRequests.values()),requestHistory:[...this.requestHistory],pendingDataSources:Array.from(this.pendingDataSources)}}getConditionalInfo(){return{ifConditions:Array.from(this.ifConditions.values()),iterations:Array.from(this.iterations.values())}}getFormInfo(){return Array.from(this.forms.values())}getWebSocketInfo(){return{connections:Array.from(this.wsConnections.values()),messageHistory:[...this.wsMessageHistory],connectionState:this.getWebSocketConnectionState()}}getStateAtTime(e){const n=[...this.stateHistory].reverse().find(r=>r.timestamp<=e);if(n)return{_global:n.next,_local:{}}}reset(){this.stateHistory=[],this.actionHistory=[],this.cacheStats={hits:0,misses:0,entries:0},this.cacheDecisions=[],this.cacheDecisionIdCounter=0,this.currentRenderCycleId=null,this.isInActionExecution=!1,this.isInIteration=!1,this.mountedComponents.clear(),this.eventListeners.clear(),this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[],this.activeRequests.clear(),this.requestHistory=[],this.pendingDataSources.clear(),this.wsConnections.clear(),this.wsMessageHistory=[],this.ifConditions.clear(),this.iterations.clear(),this.forms.clear(),this.expressionHistory=[],this.expressionIdCounter=0,this.dataSources.clear(),this.dataPathTransforms=[],this.nestedContexts=[],this.nestedContextIdCounter=0,this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0,this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0,this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0,this.logHistory=[],this.logIdCounter=0}getExpressionInfo(){return{expressions:this.getExpressions(),stats:this.getExpressionStats()}}trackLog(e,n,r){if(!this.config.enabled)return;const i=r.map(u=>{if(typeof u=="string")return u;try{return JSON.stringify(u)}catch{return String(u)}}).join(" "),l={id:`log-${++this.logIdCounter}`,level:e,prefix:n,message:i,args:this.sanitizeObject(r),timestamp:Date.now(),stack:e==="error"?new Error().stack:void 0};this.logHistory.push(l),this.logHistory.length>this.maxLogHistory&&this.logHistory.shift()}trackAction(e){if(!this.config.enabled)return;const n={id:`debounce-action-${++this.debounceActionIdCounter}`,...e,timestamp:Date.now()};this.debounceActionHistory.push(n),this.debounceActionHistory.length>100&&this.debounceActionHistory.shift()}trackDataSourceUpdate(e){if(!this.config.enabled)return;const n={id:`ds-update-${++this.dataSourceUpdateIdCounter}`,...e};this.dataSourceUpdateHistory.push(n),this.dataSourceUpdateHistory.length>100&&this.dataSourceUpdateHistory.shift()}getDebounceActionHistory(e){let n=[...this.debounceActionHistory];return e?.handler&&(n=n.filter(r=>r.handler.includes(e.handler))),e?.status&&(n=n.filter(r=>r.status===e.status)),e?.limit&&(n=n.slice(-e.limit)),n}getDataSourceUpdateHistory(e){let n=[...this.dataSourceUpdateHistory];return e?.dataSourceId&&(n=n.filter(r=>r.dataSourceId===e.dataSourceId)),e?.updateType&&(n=n.filter(r=>r.updateType===e.updateType)),e?.limit&&(n=n.slice(-e.limit)),n}getLogs(e){let n=[...this.logHistory];if(e?.level){const r=Array.isArray(e.level)?e.level:[e.level];n=n.filter(i=>r.includes(i.level))}if(e?.prefix){const r=e.prefix.toLowerCase();n=n.filter(i=>i.prefix.toLowerCase().includes(r))}if(e?.search){const r=e.search.toLowerCase();n=n.filter(i=>i.message.toLowerCase().includes(r))}return e?.since&&(n=n.filter(r=>r.timestamp>=e.since)),e?.limit&&(n=n.slice(-e.limit)),n}getLogStats(){const n=Date.now()-6e4,r={log:0,warn:0,error:0,debug:0,info:0},i={};let l=0,u=0;for(const d of this.logHistory)r[d.level]++,i[d.prefix]=(i[d.prefix]||0)+1,d.timestamp>=n&&(d.level==="error"&&l++,d.level==="warn"&&u++);return{totalLogs:this.logHistory.length,byLevel:r,byPrefix:i,recentErrors:l,recentWarnings:u}}clearLogs(){this.logHistory=[],this.logIdCounter=0}getLogInfo(){return{entries:this.getLogs(),stats:this.getLogStats()}}trackDataSourceDefinition(e){this.config.enabled&&this.dataSources.set(e.id,{...e,status:"idle"})}trackDataSourceLoading(e){if(!this.config.enabled)return;const n=this.dataSources.get(e);n&&this.dataSources.set(e,{...n,status:"loading"})}trackDataSourceLoaded(e,n,r){if(!this.config.enabled)return;const i=this.dataSources.get(e);if(!i)return;let l,u,d=r;n&&(Array.isArray(n)?(l=n.length,u=n.length>0&&typeof n[0]=="object"?Object.keys(n[0]):void 0):typeof n=="object"&&(Array.isArray(n.data)?(d=d||"data",l=n.data.length,u=n.data.length>0&&typeof n.data[0]=="object"?Object.keys(n.data[0]):void 0):Array.isArray(n.items)?(d=d||"items",l=n.items.length,u=n.items.length>0&&typeof n.items[0]=="object"?Object.keys(n.items[0]):void 0):u=Object.keys(n))),this.dataSources.set(e,{...i,status:"loaded",dataPath:d,itemCount:l,keys:u,lastLoadedAt:Date.now(),error:void 0})}trackDataSourceError(e,n){if(!this.config.enabled)return;const r=this.dataSources.get(e);r&&this.dataSources.set(e,{...r,status:"error",error:n,lastLoadedAt:Date.now()})}untrackDataSource(e){this.dataSources.delete(e)}getDataSources(){return Array.from(this.dataSources.values())}getDataSource(e){return this.dataSources.get(e)}clearDataSources(){this.dataSources.clear()}trackDataPathTransform(e){if(!this.config.enabled)return;let n=this.dataPathTransforms.find(r=>r.dataSourceId===e.dataSourceId);n||(n={dataSourceId:e.dataSourceId,timestamp:Date.now(),transformSteps:[],warnings:[]},this.dataPathTransforms.push(n),this.dataPathTransforms.length>this.maxDataPathTransforms&&this.dataPathTransforms.shift()),n.transformSteps.push({step:e.step,inputPath:e.inputPath,inputValue:this.safeClone(e.inputValue),outputPath:e.outputPath,outputValue:this.safeClone(e.outputValue),config:e.config}),e.warning&&n.warnings.push(e.warning)}setDataPathFinalBinding(e,n){if(!this.config.enabled)return;const r=this.dataPathTransforms.find(i=>i.dataSourceId===e);r&&(r.finalBinding={expression:n.expression,resolvedPath:n.resolvedPath,value:this.safeClone(n.value)})}getDataPathTransformTrackingInfo(){const e=this.getDataPathTransformStats();return{transforms:[...this.dataPathTransforms],stats:e,timestamp:Date.now()}}getDataPathTransformStats(){const e=this.dataPathTransforms,n={};let r=0;const i={};for(const u of e){n[u.dataSourceId]=(n[u.dataSourceId]||0)+1,r+=u.warnings.length;for(const d of u.warnings)i[d]=(i[d]||0)+1}const l=Object.entries(i).map(([u,d])=>({warning:u,count:d})).sort((u,d)=>d.count-u.count).slice(0,10);return{totalTransforms:e.length,byDataSource:n,warningsCount:r,commonWarnings:l}}getDataPathTransformForDataSource(e){return this.dataPathTransforms.find(n=>n.dataSourceId===e)}clearDataPathTransforms(){this.dataPathTransforms=[]}trackNestedContext(e){if(!this.config.enabled)return"";const n=`nested-${++this.nestedContextIdCounter}`,r=[...new Set([...e.parentContext.available,...e.ownContext.added])],i={...e.parentContext.values,...e.ownContext.values},l={id:n,timestamp:Date.now(),componentId:e.componentId,componentType:e.componentType,parentContext:{available:e.parentContext.available,values:this.safeClone(e.parentContext.values)},ownContext:{added:e.ownContext.added,values:this.safeClone(e.ownContext.values)},mergedContext:{all:r,values:this.safeClone(i)},accessAttempts:[],depth:e.depth,parentId:e.parentId};return this.nestedContexts.push(l),this.nestedContexts.length>this.maxNestedContexts&&this.nestedContexts.shift(),n}trackNestedContextAccess(e,n){if(!this.config.enabled)return;const r=this.nestedContexts.find(i=>i.id===e);r&&r.accessAttempts.push({path:n.path,found:n.found,value:n.found?this.safeClone(n.value):void 0,error:n.error})}getNestedContextTrackingInfo(){const e=this.getNestedContextStats();return{contexts:[...this.nestedContexts],stats:e,timestamp:Date.now()}}getNestedContextStats(){const e=this.nestedContexts,n={expandChildren:0,cellChildren:0,iteration:0,modal:0,slot:0};let r=0,i=0;const l={};for(const d of e){n[d.componentType]++,d.depth>r&&(r=d.depth);for(const f of d.accessAttempts)f.found||(i++,l[f.path]=(l[f.path]||0)+1)}const u=Object.entries(l).map(([d,f])=>({path:d,count:f})).sort((d,f)=>f.count-d.count).slice(0,10);return{totalContexts:e.length,byType:n,maxDepth:r,failedAccessCount:i,commonFailedPaths:u}}getNestedContextForComponent(e){return this.nestedContexts.find(n=>n.componentId===e)}clearNestedContexts(){this.nestedContexts=[],this.nestedContextIdCounter=0}trackHandlerRegistration(e,n="custom",r,i){this.isEnabled&&this.handlers.set(e,{name:e,category:n,description:r,registeredAt:Date.now(),source:i})}trackHandlerUnregistration(e){this.isEnabled&&this.handlers.delete(e)}getHandlers(){return Array.from(this.handlers.values())}getHandler(e){return this.handlers.get(e)}getHandlersByCategory(e){return Array.from(this.handlers.values()).filter(n=>n.category===e)}clearHandlers(){this.handlers.clear()}trackEventSubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e),r=Date.now();n?(n.subscriberCount++,n.lastSubscribedAt=r):this.componentEventSubscriptions.set(e,{eventName:e,subscriberCount:1,firstSubscribedAt:r,lastSubscribedAt:r})}trackEventUnsubscribe(e){if(!this.isEnabled)return;const n=this.componentEventSubscriptions.get(e);n&&(n.subscriberCount--,n.subscriberCount<=0&&this.componentEventSubscriptions.delete(e))}trackEventEmit(e,n,r,i,l){if(!this.isEnabled)return;const u={id:`evt_${++this.componentEventIdCounter}`,eventName:e,data:this.sanitizeForLogging(n),timestamp:Date.now(),listenerCount:r,results:i?this.sanitizeForLogging(i):void 0,hasError:!!l,errorMessage:l?.message};this.componentEventEmitHistory.push(u),this.componentEventEmitHistory.length>this.maxComponentEventHistory&&(this.componentEventEmitHistory=this.componentEventEmitHistory.slice(-this.maxComponentEventHistory))}trackEventOff(e){this.isEnabled&&this.componentEventSubscriptions.delete(e)}trackEventClear(){this.isEnabled&&this.componentEventSubscriptions.clear()}getComponentEventInfo(){const e=Array.from(this.componentEventSubscriptions.values());return{subscriptions:e,emitHistory:[...this.componentEventEmitHistory],totalSubscribers:e.reduce((n,r)=>n+r.subscriberCount,0),totalEmits:this.componentEventEmitHistory.length}}getEventEmitHistory(){return[...this.componentEventEmitHistory]}getEventSubscriptions(){return Array.from(this.componentEventSubscriptions.values())}clearComponentEvents(){this.componentEventSubscriptions.clear(),this.componentEventEmitHistory=[],this.componentEventIdCounter=0}sanitizeForLogging(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{return"[Circular or Non-serializable]"}}startStateChange(e,n,r,i){if(!this.config.enabled)return"";const l=`setState_${++this.stateRenderingIdCounter}`;this.currentStateChangeContext={setStateId:l,startTime:performance.now(),changedPath:e,trigger:i||{},renderedComponents:[]};const u={id:`sr_${this.stateRenderingIdCounter}`,setStateId:l,statePath:e,oldValue:this.sanitizeForLogging(n),newValue:this.sanitizeForLogging(r),timestamp:Date.now(),triggeredBy:i||{},renderedComponents:[],totalRenderDuration:0,affectedBindingsCount:0};return this.stateRenderingLogs.push(u),this.stateRenderingLogs.length>this.maxStateRenderingLogs&&this.stateRenderingLogs.shift(),l}trackComponentRender(e,n,r,i=[],l=[],u){if(!this.config.enabled)return;const d=this.componentRenderCounts.get(n)||0;this.componentRenderCounts.set(n,d+1);for(const f of i)this.stateToComponentMap.has(f)||this.stateToComponentMap.set(f,new Set),this.stateToComponentMap.get(f).add(n);if(this.currentStateChangeContext){const f={componentId:e,componentName:n,renderDuration:r,accessedStatePaths:i,evaluatedBindings:l,renderOrder:this.currentStateChangeContext.renderedComponents.length,parentId:u};this.currentStateChangeContext.renderedComponents.push(f)}this.trackRender(n)}completeStateChange(e){if(!this.config.enabled||!this.currentStateChangeContext||this.currentStateChangeContext.setStateId!==e)return;const n=this.currentStateChangeContext,i=performance.now()-n.startTime,l=this.stateRenderingLogs.findIndex(u=>u.setStateId===e);if(l>=0){const u=this.stateRenderingLogs[l];u.renderedComponents=n.renderedComponents,u.totalRenderDuration=i,u.affectedBindingsCount=n.renderedComponents.reduce((d,f)=>d+f.evaluatedBindings.length,0)}this.currentStateChangeContext=null}getCurrentStateChangeContext(){return this.currentStateChangeContext}getStateRenderingLogs(){return[...this.stateRenderingLogs]}getStateRenderingInfo(){const e=this.stateRenderingLogs,n=Object.fromEntries(this.componentRenderCounts),r={};for(const[i,l]of this.stateToComponentMap)r[i]=Array.from(l);return{logs:e,componentRenderCounts:n,stateToComponentMap:r,stats:this.calculateStateRenderingStats()}}calculateStateRenderingStats(){const e=this.stateRenderingLogs;if(e.length===0)return{totalStateChanges:0,totalRenders:0,avgRenderDuration:0,avgComponentsPerChange:0,topRenderedComponents:[],topInfluentialPaths:[]};const n=e.reduce((d,f)=>d+f.renderedComponents.length,0),r=e.reduce((d,f)=>d+f.totalRenderDuration,0)/e.length,i=n/e.length,l=Array.from(this.componentRenderCounts.entries()).sort((d,f)=>f[1]-d[1]).slice(0,5).map(([d,f])=>({name:d,count:f})),u=Array.from(this.stateToComponentMap.entries()).sort((d,f)=>f[1].size-d[1].size).slice(0,5).map(([d,f])=>({path:d,affectedComponents:f.size}));return{totalStateChanges:e.length,totalRenders:n,avgRenderDuration:r,avgComponentsPerChange:i,topRenderedComponents:l,topInfluentialPaths:u}}getComponentsAffectedByState(e){const n=this.stateToComponentMap.get(e);return n?Array.from(n):[]}getComponentRenderHistory(e){return this.stateRenderingLogs.filter(n=>n.renderedComponents.some(r=>r.componentName===e))}clearStateRenderingLogs(){this.stateRenderingLogs=[],this.componentRenderCounts.clear(),this.stateToComponentMap.clear(),this.currentStateChangeContext=null,this.stateRenderingIdCounter=0}trackComponentStateSource(e,n,r,i){this.config.enabled&&this.componentStateSources.set(e,{componentId:e,componentName:n,stateSource:r,stateProvider:i})}trackDynamicState(e,n){this.config.enabled&&this.dynamicStates.set(e,n)}trackContextFlow(e,n,r,i,l,u){if(!this.config.enabled)return;const d={component:n,componentId:e,contextReceived:r,passedToChildren:i,usedInRender:l};if(this.contextFlowNodes.set(e,d),u){const f=this.contextFlowNodes.get(u);f&&(f.children||(f.children=[]),f.children.push(d))}}getStateHierarchyInfo(e=!0){const n=this.buildStateHierarchyLayers(),r=this.detectStateConflicts(n),i=Array.from(this.componentStateSources.values());return{layers:e?n.map(u=>({...u,values:this.summarizeValues(u.values,2)})):n,conflicts:r,componentStateSources:i,timestamp:Date.now()}}summarizeValues(e,n,r=0){if(e==null)return e;if(typeof e!="object")return typeof e=="string"&&e.length>100?e.substring(0,100)+`... (${e.length}자)`:e;if(r>=n){if(Array.isArray(e))return`[Array(${e.length})]`;const u=Object.keys(e);return`{${u.slice(0,5).join(", ")}${u.length>5?`, ... +${u.length-5}`:""}}`}if(Array.isArray(e))return e.length<=3?e.map(u=>this.summarizeValues(u,n,r+1)):{__type:"array",__length:e.length,__preview:e.slice(0,3).map(u=>this.summarizeValues(u,n,r+1))};const i={},l=Object.keys(e);for(const u of l)i[u]=this.summarizeValues(e[u],n,r+1);return i}buildStateHierarchyLayers(){const e=[];try{const r=window.G7Core?.state?.get?.();if(!r)return e;r._local&&e.push({name:"Global _local",type:"global",path:"_local",values:r._local,priority:1}),r._global&&e.push({name:"Global _global",type:"global",path:"_global",values:r._global,priority:1});for(const[l,u]of this.dynamicStates)u&&Object.keys(u).length>0&&e.push({name:`DynamicState (${l})`,type:"dynamicState",componentId:l,values:u,priority:2});const i=this.computeEffectiveState(r._local||{});e.push({name:"Effective _local (merged)",type:"effective",values:i,priority:3})}catch(n){console.warn("[G7DevTools] 상태 계층 빌드 실패:",n)}return e}computeEffectiveState(e){const n={...e};for(const[,r]of this.dynamicStates)r._local&&Object.assign(n,r._local);return n}detectStateConflicts(e){const n=[],r=e.find(l=>l.type==="global"&&l.path==="_local");if(!r)return n;const i=e.filter(l=>l.type==="dynamicState");for(const l of i){const u=l.values._local||l.values;for(const[d,f]of Object.entries(u)){const g=r.values[d];if(JSON.stringify(g)!==JSON.stringify(f)){const m=[],b=[];for(const v of this.componentStateSources.values())v.stateSource.local.some(_=>_===`_local.${d}`||_===d)&&(v.stateProvider.type==="dynamicState"?m.push(v.componentName):v.stateProvider.type==="globalState"&&b.push(v.componentName));n.push({path:`_local.${d}`,globalValue:g,dynamicStateValue:f,effectiveValue:f,usedBy:m,notUsedBy:b,severity:b.length>0?"warning":"info",description:b.length>0?`${b.join(", ")}은(는) globalState._local을 읽어 dynamicState 값을 사용하지 못함`:"dynamicState 값이 globalState와 다르지만 모든 컴포넌트가 dynamicState를 사용"})}}}return n}getContextFlowInfo(){const e=[],n=new Set(this.contextFlowNodes.keys());for(const r of this.contextFlowNodes.values())if(r.children)for(const i of r.children)n.delete(i.componentId);for(const r of n){const i=this.contextFlowNodes.get(r);i&&e.push(i)}return{rootComponent:e.length>0?e[0].component:"Unknown",contextFlow:e,timestamp:Date.now()}}clearStateHierarchyData(){this.componentStateSources.clear(),this.contextFlowNodes.clear(),this.dynamicStates.clear()}trackComponentStyle(e,n,r,i){if(!this.config.enabled)return;const l=this.analyzeTailwindClasses(r),u={componentId:e,componentName:n,classes:r,computedStyles:i,tailwindAnalysis:l};this.componentStyles.set(e,u),this.detectStyleIssues(u)}analyzeTailwindClasses(e){const n=[],r=[],i=[],l=[],u=["sm:","md:","lg:","xl:","2xl:"],d=[/bg-\w+-\d+/,/text-\w+-\d+/,/border-\w+-\d+/];for(const f of e){l.push(f),f.startsWith("dark:")&&n.push(f);for(const g of u)if(f.startsWith(g)){r.push(f);break}for(const g of d)if(g.test(f)){i.push(f);break}}return{usedClasses:l,darkClasses:n,responsiveClasses:r,dynamicClasses:i}}detectStyleIssues(e){const{componentId:n,componentName:r,computedStyles:i,tailwindAnalysis:l}=e;(i.opacity==="0"||i.visibility==="hidden"||i.display==="none"||i.width==="0px"&&i.height==="0px")&&this.addStyleIssue({id:this.generateId(),type:"invisible-element",componentId:n,componentName:r,property:i.opacity==="0"?"opacity":i.visibility==="hidden"?"visibility":i.display==="none"?"display":"width/height",currentValue:i.opacity==="0"?"0":i.visibility==="hidden"?"hidden":i.display==="none"?"none":"0px",severity:"warning",description:"요소가 화면에 보이지 않습니다.",suggestion:"opacity, visibility, display, width/height 값을 확인하세요."});const u=l.usedClasses.filter(f=>f.startsWith("bg-")&&!f.startsWith("dark:")),d=l.darkClasses.filter(f=>f.includes("bg-"));u.length>0&&d.length===0&&this.addStyleIssue({id:this.generateId(),type:"dark-mode-missing",componentId:n,componentName:r,property:"background-color",currentValue:u.join(", "),expectedValue:"dark:bg-* 클래스 필요",severity:"info",description:"다크 모드 배경색 클래스가 누락되었습니다.",suggestion:`${u[0]}에 대응하는 dark: 클래스를 추가하세요.`}),l.dynamicClasses.length>0&&this.addStyleIssue({id:this.generateId(),type:"tailwind-purging",componentId:n,componentName:r,property:"class",currentValue:l.dynamicClasses.join(", "),severity:"info",description:"동적으로 생성된 Tailwind 클래스가 있습니다.",suggestion:"tailwind.config.js의 safelist에 추가하거나 전체 클래스명을 상수로 정의하세요."})}addStyleIssue(e){this.styleIssues.some(r=>r.componentId===e.componentId&&r.type===e.type&&r.property===e.property)||this.styleIssues.push(e)}getStyleValidationInfo(){const e=Array.from(this.componentStyles.values());return{issues:[...this.styleIssues],componentStyles:e,stats:{totalComponents:e.length,invisibleCount:this.styleIssues.filter(n=>n.type==="invisible-element").length,tailwindIssueCount:this.styleIssues.filter(n=>n.type==="tailwind-purging").length,darkModeIssueCount:this.styleIssues.filter(n=>n.type==="dark-mode-missing").length},timestamp:Date.now()}}clearStyleValidationData(){this.styleIssues=[],this.componentStyles.clear()}trackAuthEvent(e,n,r,i){if(!this.config.enabled)return;const l={id:`auth_${++this.authEventIdCounter}`,type:e,timestamp:Date.now(),success:n,error:r,details:i};this.authEvents.push(l),this.authEvents.length>this.maxAuthEventHistory&&(this.authEvents=this.authEvents.slice(-this.maxAuthEventHistory))}trackAuthHeader(e,n,r,i,l){if(!this.config.enabled)return;const u={url:e,hasAuthHeader:n,headerType:r,tokenValid:i??!1,responseStatus:l,timestamp:Date.now()};this.authHeaderHistory.push(u),this.authHeaderHistory.length>this.maxAuthEventHistory&&(this.authHeaderHistory=this.authHeaderHistory.slice(-this.maxAuthEventHistory)),l===401&&this.trackAuthEvent("api-unauthorized",!1,`401 Unauthorized: ${e}`,{url:e})}getAuthState(){try{const e=window.G7Core,n=e?.api;let r=e?.auth;!r&&e?.AuthManager?.getInstance&&(r=e.AuthManager.getInstance());const i=n?.getToken?.()||r?.getAccessToken?.()||localStorage.getItem("auth_token")||localStorage.getItem("access_token"),l=r?.getRefreshToken?.()||localStorage.getItem("refresh_token");let u="unknown",d=r?.getUser?.();if(d&&(u="AuthManager"),!d){const S=e?.state?.get?.();d=S?._global?.user||S?._global?.currentUser||S?.user||S?.currentUser,d&&(u="G7Core.state")}const f=r?.isAuthenticated?.()??!1;let g="memory";localStorage.getItem("auth_token")||localStorage.getItem("access_token")?g="localStorage":(sessionStorage.getItem("auth_token")||sessionStorage.getItem("access_token"))&&(g="sessionStorage");const m=!!i&&(!!d||f);let b;i&&typeof i=="string"&&i.length>20?b=`${i.substring(0,10)}...${i.substring(i.length-6)}`:i&&(b="***");const v=d?{id:d.id,email:d.email,name:d.name,roles:d.roles?.map(S=>typeof S=="string"?{name:S}:{id:S.id,name:S.name,guard_name:S.guard_name}),permissions:d.permissions?.map(S=>typeof S=="string"?{name:S}:{id:S.id,name:S.name,guard_name:S.guard_name}),avatar:d.avatar||d.profile_photo_url||d.profile_image,created_at:d.created_at,updated_at:d.updated_at,last_login_at:d.last_login_at,email_verified_at:d.email_verified_at,...d.phone&&{phone:d.phone},...d.nickname&&{nickname:d.nickname},...d.status&&{status:d.status},...d.locale&&{locale:d.locale}}:void 0;return{isAuthenticated:m,user:v,tokens:{hasAccessToken:!!i,hasRefreshToken:!!l,storage:g,accessTokenPreview:b},lastActivity:Date.now(),source:u}}catch{return{isAuthenticated:!1,tokens:{hasAccessToken:!1,hasRefreshToken:!1,storage:"memory"},source:"unknown"}}}getAuthDebugInfo(){const e=[...this.authEvents];return{state:this.getAuthState(),events:e,headerAnalysis:[...this.authHeaderHistory],stats:{loginAttempts:e.filter(n=>n.type==="login").length,successfulLogins:e.filter(n=>n.type==="login"&&n.success).length,failedLogins:e.filter(n=>n.type==="login"&&!n.success).length,tokenRefreshes:e.filter(n=>n.type==="token-refresh").length,unauthorizedResponses:e.filter(n=>n.type==="api-unauthorized").length},timestamp:Date.now()}}clearAuthData(){this.authEvents=[],this.authHeaderHistory=[],this.authEventIdCounter=0}trackLayoutLoad(e,n,r,i="api"){if(!this.config.enabled)return;const l=Date.now();this.currentLayout={layoutPath:e,templateId:n,layoutJson:this.sanitizeObject(r),loadedAt:l,version:r.version,layoutName:r.layout_name,source:i};const u={id:`layout-${++this.layoutIdCounter}`,layoutPath:e,templateId:n,loadedAt:l,source:i,version:r.version};this.layoutHistory.push(u),this.layoutHistory.length>this.maxLayoutHistory&&this.layoutHistory.shift(),this.layoutStats.totalLoads++,i==="cache"?this.layoutStats.cacheHits++:this.layoutStats.apiLoads++}getCurrentLayout(){return this.currentLayout}getLayoutHistory(){return[...this.layoutHistory]}getLayoutDebugInfo(){return{current:this.currentLayout,history:[...this.layoutHistory],stats:{...this.layoutStats}}}clearLayoutData(){this.currentLayout=null,this.layoutHistory=[],this.layoutIdCounter=0,this.layoutStats={totalLoads:0,cacheHits:0,apiLoads:0}}startHandlerExecution(e){if(!this.config.enabled)return"";const n=`exec_${++this.executionIdCounter}_${Date.now()}`,r={handlerName:e,executionId:n,startTime:Date.now(),stateChanges:[],dataSourceChanges:[],alerts:[]};if(this.executionDetails.set(n,r),this.executionDetails.size>this.maxExecutionDetails){const i=Array.from(this.executionDetails.keys());i.slice(0,i.length-this.maxExecutionDetails).forEach(u=>this.executionDetails.delete(u))}return n}endHandlerExecution(e,n,r,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);if(!l)return;l.endTime=Date.now(),l.duration=l.endTime-l.startTime,l.exitReason=n,l.exitLocation=r,l.exitDescription=i;const u=this.changeExpectations.get(e);u&&(l.expectedChanges=u,this.checkExpectations(e,l,u),this.changeExpectations.delete(e)),n==="normal"&&l.stateChanges.length===0&&l.dataSourceChanges.length===0&&this.addChangeAlert(e,{type:"no-state-change",severity:"warning",message:`핸들러 "${l.handlerName}"이(가) 상태 변경 없이 완료됨`,description:"핸들러가 성공적으로 완료되었지만 상태나 데이터소스 변경이 없습니다. 의도한 동작인지 확인하세요.",handlerName:l.handlerName,suggestion:'핸들러 내부 조건문을 확인하거나 DevTools의 "변경감지" 탭에서 exitLocation을 확인하세요.',docLink:"troubleshooting-state.md"}),(n==="early-return-condition"||n==="early-return-validation")&&this.addChangeAlert(e,{type:"early-return-detected",severity:"info",message:`핸들러 "${l.handlerName}"이(가) early return으로 종료됨`,description:i||`종료 위치: ${r||"알 수 없음"}`,handlerName:l.handlerName,suggestion:n==="early-return-validation"?"검증 실패로 인한 early return입니다. 입력 데이터를 확인하세요.":"조건부 early return입니다. 조건문의 평가 결과를 확인하세요."})}recordExitReason(e,n,r,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);l&&(l.exitReason=n,l.exitLocation=r,l.exitDescription=i)}recordStateChange(e,n,r,i,l){if(!this.config.enabled)return;const u={id:`sc_${++this.stateChangeIdCounter}`,path:n,changeType:r,oldValue:this.safeClone(i),newValue:this.safeClone(l),timestamp:Date.now(),comparison:this.compareValues(i,l),executionId:e};if(this.stateChangeHistory.push(u),e){const d=this.executionDetails.get(e);d&&d.stateChanges.push(u)}this.stateChangeHistory.length>this.maxStateChangeHistory&&this.stateChangeHistory.splice(0,this.stateChangeHistory.length-this.maxStateChangeHistory),u.comparison.isDeepEqual&&typeof i=="object"&&i!==null&&this.addChangeAlert(e||"",{type:"object-reference-same",severity:"warning",message:`상태 "${n}"가 변경되었으나 값이 동일함`,description:"객체 참조가 동일하거나 깊은 비교 결과 변경이 없습니다. 변경 감지가 실패할 수 있습니다.",handlerName:e&&this.executionDetails.get(e)?.handlerName||"unknown",statePath:n,suggestion:"불변성을 유지하여 새 객체를 생성하세요. 예: { ...oldObject, newField: value }",docLink:"troubleshooting-state.md#object-mutation"})}recordDataSourceChange(e,n,r,i,l){if(!this.config.enabled)return;const u={dataSourceId:n,changeType:r,timestamp:Date.now(),previousStatus:i,newStatus:l,executionId:e};if(this.dataSourceChangeHistory.push(u),e){const d=this.executionDetails.get(e);d&&d.dataSourceChanges.push(u)}this.dataSourceChangeHistory.length>this.maxStateChangeHistory&&this.dataSourceChangeHistory.splice(0,this.dataSourceChangeHistory.length-this.maxStateChangeHistory)}expectChange(e,n,r){if(!this.config.enabled||!e)return;const i=this.executionDetails.get(e);if(!i)return;const l={expectedStatePaths:n,expectedDataSources:r,setAt:Date.now(),source:i.handlerName};this.changeExpectations.set(e,l)}checkExpectations(e,n,r){const i=new Set(n.stateChanges.map(u=>u.path)),l=new Set(n.dataSourceChanges.map(u=>u.dataSourceId));for(const u of r.expectedStatePaths)i.has(u)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 상태 변경 "${u}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${u}" 변경을 예고했지만 실제로 변경되지 않았습니다.`,handlerName:n.handlerName,statePath:u,suggestion:"핸들러 내부 로직을 확인하세요. early return이나 조건부 분기로 인해 변경이 생략되었을 수 있습니다.",docLink:"troubleshooting-state.md"});for(const u of r.expectedDataSources)l.has(u)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 데이터소스 갱신 "${u}"가 발생하지 않음`,description:`핸들러 "${n.handlerName}"이(가) "${u}" 갱신을 예고했지만 실제로 갱신되지 않았습니다.`,handlerName:n.handlerName,dataSourceId:u,suggestion:"refetchDataSource 호출이 실행되었는지 확인하세요.",docLink:"data-sources.md"})}addChangeAlert(e,n){const r={...n,id:`alert_${++this.alertIdCounter}`,timestamp:Date.now()};if(this.changeAlerts.push(r),e){const i=this.executionDetails.get(e);i&&i.alerts.push(r)}this.changeAlerts.length>this.maxChangeAlerts&&this.changeAlerts.splice(0,this.changeAlerts.length-this.maxChangeAlerts)}compareValues(e,n){const r=this.getValueType(e),i=this.getValueType(n),l=r!==i;let u=!1,d;return!l&&(r==="object"||r==="array")?(u=this.deepEqual(e,n),!u&&r==="object"&&(d=this.getChangedKeys(e,n))):u=e===n,{typeChanged:l,oldType:r,newType:i,isDeepEqual:u,changedKeys:d}}getValueType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}deepEqual(e,n){if(e===n)return!0;if(typeof e!=typeof n)return!1;if(e===null||n===null||typeof e!="object")return e===n;if(Array.isArray(e)!==Array.isArray(n))return!1;const r=Object.keys(e),i=Object.keys(n);if(r.length!==i.length)return!1;for(const l of r)if(!i.includes(l)||!this.deepEqual(e[l],n[l]))return!1;return!0}getChangedKeys(e,n){const r=[],i=new Set([...Object.keys(e),...Object.keys(n)]);for(const l of i)this.deepEqual(e[l],n[l])||r.push(l);return r}getChangeDetectionInfo(){const e=Array.from(this.executionDetails.values()),n={totalExecutions:e.length,executionsWithStateChange:e.filter(r=>r.stateChanges.length>0).length,executionsWithoutStateChange:e.filter(r=>r.stateChanges.length===0&&r.exitReason==="normal").length,earlyReturnCount:e.filter(r=>r.exitReason==="early-return-condition"||r.exitReason==="early-return-validation").length,alertCount:this.changeAlerts.length,alertsByType:this.getAlertsByType()};return{executionDetails:e,stateChangeHistory:[...this.stateChangeHistory],dataSourceChangeHistory:[...this.dataSourceChangeHistory],alerts:[...this.changeAlerts],stats:n,timestamp:Date.now()}}getAlertsByType(){const e={"no-state-change":0,"no-datasource-change":0,"expected-not-fulfilled":0,"object-reference-same":0,"early-return-detected":0,"async-timing-issue":0};for(const n of this.changeAlerts)e[n.type]++;return e}clearChangeDetectionData(){this.executionDetails.clear(),this.stateChangeHistory=[],this.dataSourceChangeHistory=[],this.changeAlerts=[],this.changeExpectations.clear(),this.executionIdCounter=0,this.stateChangeIdCounter=0,this.alertIdCounter=0}getRecentAlerts(e=10,n){let r=[...this.changeAlerts];return n&&(r=r.filter(i=>i.severity===n)),r.slice(-e)}getHandlerExecutions(e,n=10){return Array.from(this.executionDetails.values()).filter(i=>i.handlerName===e).slice(-n)}findExecutionInStack(e){return this.sequenceExecutionStack.find(n=>n.sequenceId===e)}startSequenceExecution(e){if(!this.config.enabled)return"";const n=`seq_${++this.sequenceIdCounter}_${Date.now()}`,r={sequenceId:n,startTime:Date.now(),totalDuration:0,trigger:e,actions:[],status:"running"};return this.sequenceExecutionStack.push(r),n}captureSequenceActionBefore(e,n,r,i,l){if(!this.config.enabled)return;const u=this.findExecutionInStack(e);if(!u)return;const d={index:n,handler:r,params:this.safeClone(i),stateBeforeAction:{_global:this.safeClone(l._global),_local:this.safeClone(l._local),_isolated:l._isolated?this.safeClone(l._isolated):void 0},stateAfterAction:{_global:{},_local:{}},duration:0,status:"success"};u.actions.push(d)}captureSequenceActionAfter(e,n,r,i,l,u){if(!this.config.enabled)return;const d=this.findExecutionInStack(e);if(!d)return;const f=d.actions.find(m=>m.index===n);if(!f)return;f.stateAfterAction={_global:this.safeClone(r._global),_local:this.safeClone(r._local),_isolated:r._isolated?this.safeClone(r._isolated):void 0},f.duration=i,f.result=this.safeClone(l),f.stateDiff={global:this.computeStateDiff(f.stateBeforeAction._global,f.stateAfterAction._global),local:this.computeStateDiff(f.stateBeforeAction._local,f.stateAfterAction._local)},f.stateBeforeAction._isolated&&f.stateAfterAction._isolated&&(f.stateDiff.isolated=this.computeStateDiff(f.stateBeforeAction._isolated,f.stateAfterAction._isolated));const g=window.__g7PendingLocalState;g&&(f.pendingState=this.safeClone(g)),u&&(f.status="error",f.error={name:u.name,message:u.message,stack:u.stack})}endSequenceExecution(e,n){if(!this.config.enabled)return;const r=this.sequenceExecutionStack.findIndex(l=>l.sequenceId===e);if(r===-1)return;const i=this.sequenceExecutionStack[r];if(i.endTime=Date.now(),i.totalDuration=i.endTime-i.startTime,n){i.status="error",i.error={name:n.name,message:n.message,stack:n.stack};const l=i.actions.find(u=>u.status==="error");l&&(i.failedAtIndex=l.index)}else i.status="success";this.sequenceExecutions.push(i),this.sequenceExecutions.length>this.maxSequenceExecutions&&this.sequenceExecutions.shift(),this.sequenceExecutionStack.splice(r,1)}computeStateDiff(e,n){const r=[],i=[],l=[],u=new Set(Object.keys(e)),d=new Set(Object.keys(n));for(const f of d)u.has(f)||r.push(f);for(const f of u)d.has(f)||i.push(f);for(const f of u)if(d.has(f)){const g=e[f],m=n[f];JSON.stringify(g)!==JSON.stringify(m)&&l.push({path:f,oldValue:g,newValue:m})}return{added:r,removed:i,changed:l}}getSequenceTrackingInfo(){const e=this.getSequenceStats(),n=this.sequenceExecutionStack.length>0?this.sequenceExecutionStack[this.sequenceExecutionStack.length-1]:void 0;return{executions:[...this.sequenceExecutions],currentExecution:n,stats:e}}getSequenceStats(){const e=this.sequenceExecutions,n=e.length,r=e.filter(g=>g.status==="success").length,i=e.filter(g=>g.status==="error").length,l=e.reduce((g,m)=>g+m.actions.length,0),u=n>0?e.reduce((g,m)=>g+m.totalDuration,0)/n:0,d={};for(const g of e)for(const m of g.actions)d[m.handler]=(d[m.handler]||0)+1;const f=Object.entries(d).sort(([,g],[,m])=>m-g).slice(0,5).map(([g,m])=>({handler:g,count:m}));return{totalExecutions:n,successCount:r,errorCount:i,totalActions:l,avgDuration:u,topHandlers:f}}clearSequenceData(){this.sequenceExecutions=[],this.sequenceExecutionStack=[],this.sequenceIdCounter=0}getSequenceExecution(e){const n=this.findExecutionInStack(e);return n||this.sequenceExecutions.find(r=>r.sequenceId===e)}getRecentSequences(e=10){return this.sequenceExecutions.slice(-e)}registerStateCaptureForHandler(e,n,r){if(!this.config.enabled)return;const i=new Map,l=Date.now();for(const u of n)i.set(u,{value:this.safeClone(r[u]),capturedAt:l});this.stateCaptureRegistry.set(e,i)}detectStaleClosure(e,n,r,i,l){if(!this.config.enabled)return[];const u=this.stateCaptureRegistry.get(e);if(!u)return[];const d=Date.now(),f=[];for(const[g,m]of u){const b=this.getValueByPath(r,g),v=d-m.capturedAt;if(!this.isDeepEqual(m.value,b)&&v>0){const S=this.createStaleClosureWarning(i,n,g,m.value,m.capturedAt,b,d,v,l);f.push(S),this.addStaleClosureWarning(S)}}return this.stateCaptureRegistry.delete(e),f}trackStaleClosureWarning(e){if(!this.config.enabled)return;const n=Date.now(),r=this.createStaleClosureWarning(e.type,e.location,e.capturedPath,e.capturedValue,e.capturedAt,e.currentValue,n,n-e.capturedAt,e.actionId,e.stackTrace);this.addStaleClosureWarning(r)}createStaleClosureWarning(e,n,r,i,l,u,d,f,g,m){const b=this.getStaleClosureSeverity(e,f),{description:v,suggestion:S,docLink:_}=this.getStaleClosureMessages(e,r,f);return{id:`stale_${++this.staleClosureIdCounter}_${Date.now()}`,timestamp:Date.now(),type:e,location:n,capturedState:{path:r,capturedValue:this.safeClone(i),capturedAt:l},currentState:{path:r,currentValue:this.safeClone(u),retrievedAt:d},timeDiff:f,severity:b,description:v,suggestion:S,docLink:_,actionId:g,stackTrace:m}}getStaleClosureSeverity(e,n){if(n>5e3)return"error";if(n>1e3)return"warning";switch(e){case"async-state-capture":case"sequence-state-mismatch":return"warning";case"callback-state-capture":case"timeout-state-capture":return"info";case"event-handler-stale":return"warning";default:return"info"}}getStaleClosureMessages(e,n,r){const i=r<1e3?`${r}ms`:`${(r/1e3).toFixed(1)}s`;switch(e){case"async-state-capture":return{description:`await 후 ${i} 경과 시점에 '${n}' 상태가 변경됨. 캡처된 상태 대신 최신 상태를 사용해야 함`,suggestion:"await 이후에는 G7Core.state.get()으로 최신 상태를 다시 조회하거나, useRef + getter 패턴을 사용하세요",docLink:"troubleshooting-state-closure.md"};case"callback-state-capture":return{description:`콜백에서 ${i} 전에 캡처된 '${n}' 상태를 사용 중. 최신 상태와 다름`,suggestion:"콜백에서 상태를 참조할 때는 stateRef.current 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};case"timeout-state-capture":return{description:`setTimeout/setInterval 콜백에서 '${n}' 상태가 ${i} 전 값 사용 중`,suggestion:"타이머 콜백 내에서는 G7Core.state.get()으로 최신 상태를 조회하세요",docLink:"troubleshooting-state-closure.md"};case"sequence-state-mismatch":return{description:`sequence 내 '${n}' 상태가 이전 액션 결과와 불일치. context.state 대신 캡처된 값 사용 의심`,suggestion:"sequence 내에서는 context.state 또는 $prev를 사용하여 최신 상태를 참조하세요",docLink:"troubleshooting-state-setstate.md"};case"event-handler-stale":return{description:`이벤트 핸들러에서 ${i} 전에 바인딩된 '${n}' 상태 사용 중`,suggestion:"이벤트 핸들러에서 상태를 참조할 때는 useRef 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};default:return{description:`'${n}' 상태에서 stale closure 감지됨 (${i} 경과)`,suggestion:"상태 참조 시 최신 값을 사용하는지 확인하세요"}}}addStaleClosureWarning(e){this.staleClosureWarnings.push(e),this.staleClosureWarnings.length>this.maxStaleClosureWarnings&&this.staleClosureWarnings.shift()}getValueByPath(e,n){const r=n.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}isDeepEqual(e,n){if(e===n)return!0;if(e===null||n===null)return e===n;if(typeof e!=typeof n)return!1;if(typeof e!="object")return e===n;try{return JSON.stringify(e)===JSON.stringify(n)}catch{return!1}}getStaleClosureTrackingInfo(){const e=this.getStaleClosureStats();return{warnings:[...this.staleClosureWarnings],stats:e,timestamp:Date.now()}}getStaleClosureStats(){const e=this.staleClosureWarnings,n={"async-state-capture":0,"callback-state-capture":0,"timeout-state-capture":0,"sequence-state-mismatch":0,"event-handler-stale":0};for(const f of e)n[f.type]++;const r={info:0,warning:0,error:0};for(const f of e)r[f.severity]++;const i={};for(const f of e)i[f.location]=(i[f.location]||0)+1;const l=Object.entries(i).sort(([,f],[,g])=>g-f).slice(0,5).map(([f,g])=>({location:f,count:g})),u={};for(const f of e)u[f.capturedState.path]=(u[f.capturedState.path]||0)+1;const d=Object.entries(u).sort(([,f],[,g])=>g-f).slice(0,5).map(([f,g])=>({path:f,count:g}));return{totalWarnings:e.length,warningsByType:n,warningsBySeverity:r,topLocations:l,topAffectedPaths:d}}clearStaleClosureData(){this.staleClosureWarnings=[],this.staleClosureIdCounter=0,this.stateCaptureRegistry.clear()}getRecentStaleClosureWarnings(e=10,n){let r=[...this.staleClosureWarnings];return n&&(r=r.filter(i=>i.severity===n)),r.slice(-e)}safeClone(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{if(Array.isArray(e))return e.map(r=>{try{return JSON.parse(JSON.stringify(r))}catch{return"[Unserializable]"}});const n={};for(const r of Object.keys(e))try{n[r]=JSON.parse(JSON.stringify(e[r]))}catch{n[r]="[Unserializable]"}return n}}};$(bi,"instance");let Tr=bi;const Gn=dt("LayoutLoader");class va extends Error{constructor(n,r,i){super(n);$(this,"code");$(this,"details");this.code=r,this.details=i,this.name="LayoutLoaderError"}}class sf{constructor(e){$(this,"componentRegistry");$(this,"currentLayout",null);$(this,"layoutCache",new Map);$(this,"cacheVersion",0);this.componentRegistry=e}setCacheVersion(e){this.cacheVersion!==e&&(Gn.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.layoutCache.clear())}getCacheVersion(){return this.cacheVersion}async loadLayout(e,n){const r=`${e}:${n}`;if(this.layoutCache.has(r)){Gn.log("Loading layout from cache:",n);const l=await this.layoutCache.get(r),u=JSON.parse(JSON.stringify(l));return this.currentLayout=u,Tr.getInstance().trackLayoutLoad(n,e,u,"cache"),u}const i=this.fetchLayout(e,n);return this.layoutCache.set(r,i),i.catch(()=>{this.layoutCache.get(r)===i&&this.layoutCache.delete(r)}),i}async fetchLayout(e,n,r=!1){try{const i=this.cacheVersion>0?this.cacheVersion:null;let l;if(n.startsWith("__preview__/")){const v=n.replace("__preview__/","");l=Ma(`/api/layouts/preview/${v}`,"json",i)}else l=Ma(`/api/layouts/${e}/${n}`,"json",i);Gn.log("Fetching layout from API:",l);const u=Ia(),d=r?null:u.getToken(),f={Accept:"application/json"};d&&(f.Authorization=`Bearer ${d}`);const g=await Dl(l,{init:{headers:f},label:`layout: ${n}`});let m;try{m=await g.json()}catch{m=null}if(!g.ok){if(g.status===401&&!r&&d){Gn.log("Token invalid, removing and retrying without token"),u.removeToken();try{return await this.fetchLayout(e,n,!0)}catch(S){throw S instanceof va&&S.details?.status===401?new va(S.message,S.code,{...S.details,hadToken:!0}):S}}const v=m?.message||m?.error;throw new va(`Failed to fetch layout: ${g.status} ${g.statusText}`,"FETCH_FAILED",{status:g.status,statusText:g.statusText,url:l,apiMessage:v})}const b=m.data||m;return this.validateLayoutData(b),this.currentLayout=b,Tr.getInstance().trackLayoutLoad(n,e,b,"api"),Gn.log("Layout fetched and cached successfully:",b.layout_name),b}catch(i){const l=`${e}:${n}`;throw this.layoutCache.delete(l),i instanceof va?i:new va("Failed to load layout","LOAD_FAILED",{originalError:i})}}prefetchLayout(e,n){return this.loadLayout(e,n)}validateLayoutData(e){if(!e.version)throw new va("Layout data missing required field: version","VALIDATION_FAILED",{field:"version"});if(!e.layout_name)throw new va("Layout data missing required field: layout_name","VALIDATION_FAILED",{field:"layout_name"});if(!Array.isArray(e.components))throw new va('Layout data field "components" must be an array',"VALIDATION_FAILED",{field:"components",value:e.components});Gn.log("Layout data validation passed")}renderLayout(e,n){try{const r=n||this.currentLayout;if(!r)throw new va("No layout data to render","NO_LAYOUT_DATA");Gn.log("Rendering layout:",r.layout_name),e.innerHTML="",this.renderComponents(e,r.components),Gn.log("Layout rendered successfully")}catch(r){Gn.error("Render error:",r),this.renderErrorState(e,r)}}renderComponents(e,n){for(const r of n){const i=this.renderComponent(r);i&&e.appendChild(i)}}renderComponent(e){try{if(!this.componentRegistry.getComponent(e.type))return Gn.warn(`Component not found: ${e.type}`),this.createPlaceholderElement(e.type);const r=document.createElement("div");return r.setAttribute("data-component",e.type),e.props&&Object.entries(e.props).forEach(([i,l])=>{r.setAttribute(`data-prop-${i}`,JSON.stringify(l))}),e.children&&e.children.length>0?this.renderComponents(r,e.children):e.text&&(r.textContent=e.text),r}catch(n){return Gn.error(`Error rendering component ${e.type}:`,n),this.createErrorElement(e.type,n)}}createPlaceholderElement(e){const n=document.createElement("div");return n.className="component-placeholder",n.setAttribute("data-component-type",e),n.innerHTML=` + `},$C={onDragStart(o){let{active:e}=o;return"Picked up draggable item "+e.id+"."},onDragOver(o){let{active:e,over:a}=o;return a?"Draggable item "+e.id+" was moved over droppable area "+a.id+".":"Draggable item "+e.id+" is no longer over a droppable area."},onDragEnd(o){let{active:e,over:a}=o;return a?"Draggable item "+e.id+" was dropped over droppable area "+a.id:"Draggable item "+e.id+" was dropped."},onDragCancel(o){let{active:e}=o;return"Dragging was cancelled. Draggable item "+e.id+" was dropped."}};function NC(o){let{announcements:e=$C,container:a,hiddenTextDescribedById:r,screenReaderInstructions:i=MC}=o;const{announce:l,announcement:u}=DC(),d=ho("DndLiveRegion"),[f,g]=N.useState(!1);if(N.useEffect(()=>{g(!0)},[]),OC(N.useMemo(()=>({onDragStart(b){let{active:v}=b;l(e.onDragStart({active:v}))},onDragMove(b){let{active:v,over:S}=b;e.onDragMove&&l(e.onDragMove({active:v,over:S}))},onDragOver(b){let{active:v,over:S}=b;l(e.onDragOver({active:v,over:S}))},onDragEnd(b){let{active:v,over:S}=b;l(e.onDragEnd({active:v,over:S}))},onDragCancel(b){let{active:v,over:S}=b;l(e.onDragCancel({active:v,over:S}))}}),[l,e])),!f)return null;const m=We.createElement(We.Fragment,null,We.createElement(RC,{id:r,value:i.draggable}),We.createElement(kC,{id:d,announcement:u}));return a?Ar.createPortal(m,a):m}var en;(function(o){o.DragStart="dragStart",o.DragMove="dragMove",o.DragEnd="dragEnd",o.DragCancel="dragCancel",o.DragOver="dragOver",o.RegisterDroppable="registerDroppable",o.SetDroppableDisabled="setDroppableDisabled",o.UnregisterDroppable="unregisterDroppable"})(en||(en={}));function ec(){}function hm(o,e){return N.useMemo(()=>({sensor:o,options:e??{}}),[o,e])}function IC(){for(var o=arguments.length,e=new Array(o),a=0;a[...e].filter(r=>r!=null),[...e])}const ma=Object.freeze({x:0,y:0});function gm(o,e){return Math.sqrt(Math.pow(o.x-e.x,2)+Math.pow(o.y-e.y,2))}function pm(o,e){let{data:{value:a}}=o,{data:{value:r}}=e;return a-r}function HC(o,e){let{data:{value:a}}=o,{data:{value:r}}=e;return r-a}function mm(o){let{left:e,top:a,height:r,width:i}=o;return[{x:e,y:a},{x:e+i,y:a},{x:e,y:a+r},{x:e+i,y:a+r}]}function ym(o,e){if(!o||o.length===0)return null;const[a]=o;return a[e]}function bm(o,e,a){return e===void 0&&(e=o.left),a===void 0&&(a=o.top),{x:e+o.width*.5,y:a+o.height*.5}}const zC=o=>{let{collisionRect:e,droppableRects:a,droppableContainers:r}=o;const i=bm(e,e.left,e.top),l=[];for(const u of r){const{id:d}=u,f=a.get(d);if(f){const g=gm(bm(f),i);l.push({id:d,data:{droppableContainer:u,value:g}})}}return l.sort(pm)},jC=o=>{let{collisionRect:e,droppableRects:a,droppableContainers:r}=o;const i=mm(e),l=[];for(const u of r){const{id:d}=u,f=a.get(d);if(f){const g=mm(f),m=i.reduce((v,S,E)=>v+gm(g[E],S),0),b=Number((m/4).toFixed(4));l.push({id:d,data:{droppableContainer:u,value:b}})}}return l.sort(pm)};function UC(o,e){const a=Math.max(e.top,o.top),r=Math.max(e.left,o.left),i=Math.min(e.left+e.width,o.left+o.width),l=Math.min(e.top+e.height,o.top+o.height),u=i-r,d=l-a;if(r{let{collisionRect:e,droppableRects:a,droppableContainers:r}=o;const i=[];for(const l of r){const{id:u}=l,d=a.get(u);if(d){const f=UC(d,e);f>0&&i.push({id:u,data:{droppableContainer:l,value:f}})}}return i.sort(HC)};function PC(o,e,a){return{...o,scaleX:e&&a?e.width/a.width:1,scaleY:e&&a?e.height/a.height:1}}function vm(o,e){return o&&e?{x:o.left-e.left,y:o.top-e.top}:ma}function qC(o){return function(a){for(var r=arguments.length,i=new Array(r>1?r-1:0),l=1;l({...u,top:u.top+o*d.y,bottom:u.bottom+o*d.y,left:u.left+o*d.x,right:u.right+o*d.x}),{...a})}}const GC=qC(1);function VC(o){if(o.startsWith("matrix3d(")){const e=o.slice(9,-1).split(/, /);return{x:+e[12],y:+e[13],scaleX:+e[0],scaleY:+e[5]}}else if(o.startsWith("matrix(")){const e=o.slice(7,-1).split(/, /);return{x:+e[4],y:+e[5],scaleX:+e[0],scaleY:+e[3]}}return null}function FC(o,e,a){const r=VC(e);if(!r)return o;const{scaleX:i,scaleY:l,x:u,y:d}=r,f=o.left-u-(1-i)*parseFloat(a),g=o.top-d-(1-l)*parseFloat(a.slice(a.indexOf(" ")+1)),m=i?o.width/i:o.width,b=l?o.height/l:o.height;return{width:m,height:b,top:g,right:f+m,bottom:g+b,left:f}}const KC={ignoreTransform:!1};function os(o,e){e===void 0&&(e=KC);let a=o.getBoundingClientRect();if(e.ignoreTransform){const{transform:g,transformOrigin:m}=Tn(o).getComputedStyle(o);g&&(a=FC(a,g,m))}const{top:r,left:i,width:l,height:u,bottom:d,right:f}=a;return{top:r,left:i,width:l,height:u,bottom:d,right:f}}function Sm(o){return os(o,{ignoreTransform:!0})}function WC(o){const e=o.innerWidth,a=o.innerHeight;return{top:0,left:0,right:e,bottom:a,width:e,height:a}}function YC(o,e){return e===void 0&&(e=Tn(o).getComputedStyle(o)),e.position==="fixed"}function XC(o,e){e===void 0&&(e=Tn(o).getComputedStyle(o));const a=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(i=>{const l=e[i];return typeof l=="string"?a.test(l):!1})}function tc(o,e){const a=[];function r(i){if(e!=null&&a.length>=e||!i)return a;if(Hd(i)&&i.scrollingElement!=null&&!a.includes(i.scrollingElement))return a.push(i.scrollingElement),a;if(!co(i)||cm(i)||a.includes(i))return a;const l=Tn(o).getComputedStyle(i);return i!==o&&XC(i,l)&&a.push(i),YC(i,l)?a:r(i.parentNode)}return o?r(o):a}function wm(o){const[e]=tc(o,1);return e??null}function qd(o){return!Ql||!o?null:rs(o)?o:Id(o)?Hd(o)||o===is(o).scrollingElement?window:co(o)?o:null:null}function Cm(o){return rs(o)?o.scrollX:o.scrollLeft}function Em(o){return rs(o)?o.scrollY:o.scrollTop}function Gd(o){return{x:Cm(o),y:Em(o)}}var ln;(function(o){o[o.Forward=1]="Forward",o[o.Backward=-1]="Backward"})(ln||(ln={}));function _m(o){return!Ql||!o?!1:o===document.scrollingElement}function Am(o){const e={x:0,y:0},a=_m(o)?{height:window.innerHeight,width:window.innerWidth}:{height:o.clientHeight,width:o.clientWidth},r={x:o.scrollWidth-a.width,y:o.scrollHeight-a.height},i=o.scrollTop<=e.y,l=o.scrollLeft<=e.x,u=o.scrollTop>=r.y,d=o.scrollLeft>=r.x;return{isTop:i,isLeft:l,isBottom:u,isRight:d,maxScroll:r,minScroll:e}}const JC={x:.2,y:.2};function QC(o,e,a,r,i){let{top:l,left:u,right:d,bottom:f}=a;r===void 0&&(r=10),i===void 0&&(i=JC);const{isTop:g,isBottom:m,isLeft:b,isRight:v}=Am(o),S={x:0,y:0},E={x:0,y:0},A={height:e.height*i.y,width:e.width*i.x};return!g&&l<=e.top+A.height?(S.y=ln.Backward,E.y=r*Math.abs((e.top+A.height-l)/A.height)):!m&&f>=e.bottom-A.height&&(S.y=ln.Forward,E.y=r*Math.abs((e.bottom-A.height-f)/A.height)),!v&&d>=e.right-A.width?(S.x=ln.Forward,E.x=r*Math.abs((e.right-A.width-d)/A.width)):!b&&u<=e.left+A.width&&(S.x=ln.Backward,E.x=r*Math.abs((e.left+A.width-u)/A.width)),{direction:S,speed:E}}function ZC(o){if(o===document.scrollingElement){const{innerWidth:l,innerHeight:u}=window;return{top:0,left:0,right:l,bottom:u,width:l,height:u}}const{top:e,left:a,right:r,bottom:i}=o.getBoundingClientRect();return{top:e,left:a,right:r,bottom:i,width:o.clientWidth,height:o.clientHeight}}function xm(o){return o.reduce((e,a)=>ss(e,Gd(a)),ma)}function eE(o){return o.reduce((e,a)=>e+Cm(a),0)}function tE(o){return o.reduce((e,a)=>e+Em(a),0)}function nE(o,e){if(e===void 0&&(e=os),!o)return;const{top:a,left:r,bottom:i,right:l}=e(o);wm(o)&&(i<=0||l<=0||a>=window.innerHeight||r>=window.innerWidth)&&o.scrollIntoView({block:"center",inline:"center"})}const aE=[["x",["left","right"],eE],["y",["top","bottom"],tE]];class Vd{constructor(e,a){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const r=tc(a),i=xm(r);this.rect={...e},this.width=e.width,this.height=e.height;for(const[l,u,d]of aE)for(const f of u)Object.defineProperty(this,f,{get:()=>{const g=d(r),m=i[l]-g;return this.rect[f]+m},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class mo{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(a=>{var r;return(r=this.target)==null?void 0:r.removeEventListener(...a)})},this.target=e}add(e,a,r){var i;(i=this.target)==null||i.addEventListener(e,a,r),this.listeners.push([e,a,r])}}function rE(o){const{EventTarget:e}=Tn(o);return o instanceof e?o:is(o)}function Fd(o,e){const a=Math.abs(o.x),r=Math.abs(o.y);return typeof e=="number"?Math.sqrt(a**2+r**2)>e:"x"in e&&"y"in e?a>e.x&&r>e.y:"x"in e?a>e.x:"y"in e?r>e.y:!1}var Qn;(function(o){o.Click="click",o.DragStart="dragstart",o.Keydown="keydown",o.ContextMenu="contextmenu",o.Resize="resize",o.SelectionChange="selectionchange",o.VisibilityChange="visibilitychange"})(Qn||(Qn={}));function Tm(o){o.preventDefault()}function iE(o){o.stopPropagation()}var it;(function(o){o.Space="Space",o.Down="ArrowDown",o.Right="ArrowRight",o.Left="ArrowLeft",o.Up="ArrowUp",o.Esc="Escape",o.Enter="Enter",o.Tab="Tab"})(it||(it={}));const Rm={start:[it.Space,it.Enter],cancel:[it.Esc],end:[it.Space,it.Enter,it.Tab]},sE=(o,e)=>{let{currentCoordinates:a}=e;switch(o.code){case it.Right:return{...a,x:a.x+25};case it.Left:return{...a,x:a.x-25};case it.Down:return{...a,y:a.y+25};case it.Up:return{...a,y:a.y-25}}};class Kd{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:a}}=e;this.props=e,this.listeners=new mo(is(a)),this.windowListeners=new mo(Tn(a)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(Qn.Resize,this.handleCancel),this.windowListeners.add(Qn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(Qn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:a}=this.props,r=e.node.current;r&&nE(r),a(ma)}handleKeyDown(e){if(Bd(e)){const{active:a,context:r,options:i}=this.props,{keyboardCodes:l=Rm,coordinateGetter:u=sE,scrollBehavior:d="smooth"}=i,{code:f}=e;if(l.end.includes(f)){this.handleEnd(e);return}if(l.cancel.includes(f)){this.handleCancel(e);return}const{collisionRect:g}=r.current,m=g?{x:g.left,y:g.top}:ma;this.referenceCoordinates||(this.referenceCoordinates=m);const b=u(e,{active:a,context:r.current,currentCoordinates:m});if(b){const v=go(b,m),S={x:0,y:0},{scrollableAncestors:E}=r.current;for(const A of E){const T=e.code,{isTop:L,isRight:M,isLeft:R,isBottom:D,maxScroll:U,minScroll:j}=Am(A),P=ZC(A),X={x:Math.min(T===it.Right?P.right-P.width/2:P.right,Math.max(T===it.Right?P.left:P.left+P.width/2,b.x)),y:Math.min(T===it.Down?P.bottom-P.height/2:P.bottom,Math.max(T===it.Down?P.top:P.top+P.height/2,b.y))},ge=T===it.Right&&!M||T===it.Left&&!R,Ee=T===it.Down&&!D||T===it.Up&&!L;if(ge&&X.x!==b.x){const Ce=A.scrollLeft+v.x,$e=T===it.Right&&Ce<=U.x||T===it.Left&&Ce>=j.x;if($e&&!v.y){A.scrollTo({left:Ce,behavior:d});return}$e?S.x=A.scrollLeft-Ce:S.x=T===it.Right?A.scrollLeft-U.x:A.scrollLeft-j.x,S.x&&A.scrollBy({left:-S.x,behavior:d});break}else if(Ee&&X.y!==b.y){const Ce=A.scrollTop+v.y,$e=T===it.Down&&Ce<=U.y||T===it.Up&&Ce>=j.y;if($e&&!v.x){A.scrollTo({top:Ce,behavior:d});return}$e?S.y=A.scrollTop-Ce:S.y=T===it.Down?A.scrollTop-U.y:A.scrollTop-j.y,S.y&&A.scrollBy({top:-S.y,behavior:d});break}}this.handleMove(e,ss(go(b,this.referenceCoordinates),S))}}}handleMove(e,a){const{onMove:r}=this.props;e.preventDefault(),r(a)}handleEnd(e){const{onEnd:a}=this.props;e.preventDefault(),this.detach(),a()}handleCancel(e){const{onCancel:a}=this.props;e.preventDefault(),this.detach(),a()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}Kd.activators=[{eventName:"onKeyDown",handler:(o,e,a)=>{let{keyboardCodes:r=Rm,onActivation:i}=e,{active:l}=a;const{code:u}=o.nativeEvent;if(r.start.includes(u)){const d=l.activatorNode.current;return d&&o.target!==d?!1:(o.preventDefault(),i?.({event:o.nativeEvent}),!0)}return!1}}];function km(o){return!!(o&&"distance"in o)}function Dm(o){return!!(o&&"delay"in o)}class Wd{constructor(e,a,r){var i;r===void 0&&(r=rE(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=a;const{event:l}=e,{target:u}=l;this.props=e,this.events=a,this.document=is(u),this.documentListeners=new mo(this.document),this.listeners=new mo(r),this.windowListeners=new mo(Tn(u)),this.initialCoordinates=(i=Pd(l))!=null?i:ma,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:a,bypassActivationConstraint:r}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(Qn.Resize,this.handleCancel),this.windowListeners.add(Qn.DragStart,Tm),this.windowListeners.add(Qn.VisibilityChange,this.handleCancel),this.windowListeners.add(Qn.ContextMenu,Tm),this.documentListeners.add(Qn.Keydown,this.handleKeydown),a){if(r!=null&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Dm(a)){this.timeoutId=setTimeout(this.handleStart,a.delay),this.handlePending(a);return}if(km(a)){this.handlePending(a);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,a){const{active:r,onPending:i}=this.props;i(r,e,this.initialCoordinates,a)}handleStart(){const{initialCoordinates:e}=this,{onStart:a}=this.props;e&&(this.activated=!0,this.documentListeners.add(Qn.Click,iE,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(Qn.SelectionChange,this.removeTextSelection),a(e))}handleMove(e){var a;const{activated:r,initialCoordinates:i,props:l}=this,{onMove:u,options:{activationConstraint:d}}=l;if(!i)return;const f=(a=Pd(e))!=null?a:ma,g=go(i,f);if(!r&&d){if(km(d)){if(d.tolerance!=null&&Fd(g,d.tolerance))return this.handleCancel();if(Fd(g,d.distance))return this.handleStart()}if(Dm(d)&&Fd(g,d.tolerance))return this.handleCancel();this.handlePending(d,g);return}e.cancelable&&e.preventDefault(),u(f)}handleEnd(){const{onAbort:e,onEnd:a}=this.props;this.detach(),this.activated||e(this.props.active),a()}handleCancel(){const{onAbort:e,onCancel:a}=this.props;this.detach(),this.activated||e(this.props.active),a()}handleKeydown(e){e.code===it.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}}const oE={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Yd extends Wd{constructor(e){const{event:a}=e,r=is(a.target);super(e,oE,r)}}Yd.activators=[{eventName:"onPointerDown",handler:(o,e)=>{let{nativeEvent:a}=o,{onActivation:r}=e;return!a.isPrimary||a.button!==0?!1:(r?.({event:a}),!0)}}];const lE={move:{name:"mousemove"},end:{name:"mouseup"}};var Xd;(function(o){o[o.RightClick=2]="RightClick"})(Xd||(Xd={}));class cE extends Wd{constructor(e){super(e,lE,is(e.event.target))}}cE.activators=[{eventName:"onMouseDown",handler:(o,e)=>{let{nativeEvent:a}=o,{onActivation:r}=e;return a.button===Xd.RightClick?!1:(r?.({event:a}),!0)}}];const Jd={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class uE extends Wd{constructor(e){super(e,Jd)}static setup(){return window.addEventListener(Jd.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(Jd.move.name,e)};function e(){}}}uE.activators=[{eventName:"onTouchStart",handler:(o,e)=>{let{nativeEvent:a}=o,{onActivation:r}=e;const{touches:i}=a;return i.length>1?!1:(r?.({event:a}),!0)}}];var yo;(function(o){o[o.Pointer=0]="Pointer",o[o.DraggableRect=1]="DraggableRect"})(yo||(yo={}));var nc;(function(o){o[o.TreeOrder=0]="TreeOrder",o[o.ReversedTreeOrder=1]="ReversedTreeOrder"})(nc||(nc={}));function dE(o){let{acceleration:e,activator:a=yo.Pointer,canScroll:r,draggingRect:i,enabled:l,interval:u=5,order:d=nc.TreeOrder,pointerCoordinates:f,scrollableAncestors:g,scrollableAncestorRects:m,delta:b,threshold:v}=o;const S=hE({delta:b,disabled:!l}),[E,A]=EC(),T=N.useRef({x:0,y:0}),L=N.useRef({x:0,y:0}),M=N.useMemo(()=>{switch(a){case yo.Pointer:return f?{top:f.y,bottom:f.y,left:f.x,right:f.x}:null;case yo.DraggableRect:return i}},[a,i,f]),R=N.useRef(null),D=N.useCallback(()=>{const j=R.current;if(!j)return;const P=T.current.x*L.current.x,X=T.current.y*L.current.y;j.scrollBy(P,X)},[]),U=N.useMemo(()=>d===nc.TreeOrder?[...g].reverse():g,[d,g]);N.useEffect(()=>{if(!l||!g.length||!M){A();return}for(const j of U){if(r?.(j)===!1)continue;const P=g.indexOf(j),X=m[P];if(!X)continue;const{direction:ge,speed:Ee}=QC(j,X,M,e,v);for(const Ce of["x","y"])S[Ce][ge[Ce]]||(Ee[Ce]=0,ge[Ce]=0);if(Ee.x>0||Ee.y>0){A(),R.current=j,E(D,u),T.current=Ee,L.current=ge;return}}T.current={x:0,y:0},L.current={x:0,y:0},A()},[e,D,r,A,l,u,JSON.stringify(M),JSON.stringify(S),E,g,U,m,JSON.stringify(v)])}const fE={x:{[ln.Backward]:!1,[ln.Forward]:!1},y:{[ln.Backward]:!1,[ln.Forward]:!1}};function hE(o){let{delta:e,disabled:a}=o;const r=jd(e);return fo(i=>{if(a||!r||!i)return fE;const l={x:Math.sign(e.x-r.x),y:Math.sign(e.y-r.y)};return{x:{[ln.Backward]:i.x[ln.Backward]||l.x===-1,[ln.Forward]:i.x[ln.Forward]||l.x===1},y:{[ln.Backward]:i.y[ln.Backward]||l.y===-1,[ln.Forward]:i.y[ln.Forward]||l.y===1}}},[a,e,r])}function gE(o,e){const a=e!=null?o.get(e):void 0,r=a?a.node.current:null;return fo(i=>{var l;return e==null?null:(l=r??i)!=null?l:null},[r,e])}function pE(o,e){return N.useMemo(()=>o.reduce((a,r)=>{const{sensor:i}=r,l=i.activators.map(u=>({eventName:u.eventName,handler:e(u.handler,r)}));return[...a,...l]},[]),[o,e])}var bo;(function(o){o[o.Always=0]="Always",o[o.BeforeDragging=1]="BeforeDragging",o[o.WhileDragging=2]="WhileDragging"})(bo||(bo={}));var Qd;(function(o){o.Optimized="optimized"})(Qd||(Qd={}));const Om=new Map;function mE(o,e){let{dragging:a,dependencies:r,config:i}=e;const[l,u]=N.useState(null),{frequency:d,measure:f,strategy:g}=i,m=N.useRef(o),b=T(),v=uo(b),S=N.useCallback(function(L){L===void 0&&(L=[]),!v.current&&u(M=>M===null?L:M.concat(L.filter(R=>!M.includes(R))))},[v]),E=N.useRef(null),A=fo(L=>{if(b&&!a)return Om;if(!L||L===Om||m.current!==o||l!=null){const M=new Map;for(let R of o){if(!R)continue;if(l&&l.length>0&&!l.includes(R.id)&&R.rect.current){M.set(R.id,R.rect.current);continue}const D=R.node.current,U=D?new Vd(f(D),D):null;R.rect.current=U,U&&M.set(R.id,U)}return M}return L},[o,l,a,b,f]);return N.useEffect(()=>{m.current=o},[o]),N.useEffect(()=>{b||S()},[a,b]),N.useEffect(()=>{l&&l.length>0&&u(null)},[JSON.stringify(l)]),N.useEffect(()=>{b||typeof d!="number"||E.current!==null||(E.current=setTimeout(()=>{S(),E.current=null},d))},[d,b,S,...r]),{droppableRects:A,measureDroppableContainers:S,measuringScheduled:l!=null};function T(){switch(g){case bo.Always:return!1;case bo.BeforeDragging:return a;default:return!a}}}function Lm(o,e){return fo(a=>o?a||(typeof e=="function"?e(o):o):null,[e,o])}function yE(o,e){return Lm(o,e)}function bE(o){let{callback:e,disabled:a}=o;const r=zd(e),i=N.useMemo(()=>{if(a||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:l}=window;return new l(r)},[r,a]);return N.useEffect(()=>()=>i?.disconnect(),[i]),i}function ac(o){let{callback:e,disabled:a}=o;const r=zd(e),i=N.useMemo(()=>{if(a||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:l}=window;return new l(r)},[a]);return N.useEffect(()=>()=>i?.disconnect(),[i]),i}function vE(o){return new Vd(os(o),o)}function Mm(o,e,a){e===void 0&&(e=vE);const[r,i]=N.useState(null);function l(){i(f=>{if(!o)return null;if(o.isConnected===!1){var g;return(g=f??a)!=null?g:null}const m=e(o);return JSON.stringify(f)===JSON.stringify(m)?f:m})}const u=bE({callback(f){if(o)for(const g of f){const{type:m,target:b}=g;if(m==="childList"&&b instanceof HTMLElement&&b.contains(o)){l();break}}}}),d=ac({callback:l});return Ha(()=>{l(),o?(d?.observe(o),u?.observe(document.body,{childList:!0,subtree:!0})):(d?.disconnect(),u?.disconnect())},[o]),r}function SE(o){const e=Lm(o);return vm(o,e)}const $m=[];function wE(o){const e=N.useRef(o),a=fo(r=>o?r&&r!==$m&&o&&e.current&&o.parentNode===e.current.parentNode?r:tc(o):$m,[o]);return N.useEffect(()=>{e.current=o},[o]),a}function CE(o){const[e,a]=N.useState(null),r=N.useRef(o),i=N.useCallback(l=>{const u=qd(l.target);u&&a(d=>d?(d.set(u,Gd(u)),new Map(d)):null)},[]);return N.useEffect(()=>{const l=r.current;if(o!==l){u(l);const d=o.map(f=>{const g=qd(f);return g?(g.addEventListener("scroll",i,{passive:!0}),[g,Gd(g)]):null}).filter(f=>f!=null);a(d.length?new Map(d):null),r.current=o}return()=>{u(o),u(l)};function u(d){d.forEach(f=>{const g=qd(f);g?.removeEventListener("scroll",i)})}},[i,o]),N.useMemo(()=>o.length?e?Array.from(e.values()).reduce((l,u)=>ss(l,u),ma):xm(o):ma,[o,e])}function Nm(o,e){e===void 0&&(e=[]);const a=N.useRef(null);return N.useEffect(()=>{a.current=null},e),N.useEffect(()=>{const r=o!==ma;r&&!a.current&&(a.current=o),!r&&a.current&&(a.current=null)},[o]),a.current?go(o,a.current):ma}function EE(o){N.useEffect(()=>{if(!Ql)return;const e=o.map(a=>{let{sensor:r}=a;return r.setup==null?void 0:r.setup()});return()=>{for(const a of e)a?.()}},o.map(e=>{let{sensor:a}=e;return a}))}function _E(o,e){return N.useMemo(()=>o.reduce((a,r)=>{let{eventName:i,handler:l}=r;return a[i]=u=>{l(u,e)},a},{}),[o,e])}function Im(o){return N.useMemo(()=>o?WC(o):null,[o])}const Hm=[];function AE(o,e){e===void 0&&(e=os);const[a]=o,r=Im(a?Tn(a):null),[i,l]=N.useState(Hm);function u(){l(()=>o.length?o.map(f=>_m(f)?r:new Vd(e(f),f)):Hm)}const d=ac({callback:u});return Ha(()=>{d?.disconnect(),u(),o.forEach(f=>d?.observe(f))},[o]),i}function xE(o){if(!o)return null;if(o.children.length>1)return o;const e=o.children[0];return co(e)?e:o}function TE(o){let{measure:e}=o;const[a,r]=N.useState(null),i=N.useCallback(g=>{for(const{target:m}of g)if(co(m)){r(b=>{const v=e(m);return b?{...b,width:v.width,height:v.height}:v});break}},[e]),l=ac({callback:i}),u=N.useCallback(g=>{const m=xE(g);l?.disconnect(),m&&l?.observe(m),r(m?e(m):null)},[e,l]),[d,f]=Zl(u);return N.useMemo(()=>({nodeRef:d,rect:a,setRef:f}),[a,d,f])}const RE=[{sensor:Yd,options:{}},{sensor:Kd,options:{}}],kE={current:{}},rc={draggable:{measure:Sm},droppable:{measure:Sm,strategy:bo.WhileDragging,frequency:Qd.Optimized},dragOverlay:{measure:os}};class vo extends Map{get(e){var a;return e!=null&&(a=super.get(e))!=null?a:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:a}=e;return!a})}getNodeFor(e){var a,r;return(a=(r=this.get(e))==null?void 0:r.node.current)!=null?a:void 0}}const DE={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new vo,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:ec},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:rc,measureDroppableContainers:ec,windowRect:null,measuringScheduled:!1},OE={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:ec,draggableNodes:new Map,over:null,measureDroppableContainers:ec},ic=N.createContext(OE),zm=N.createContext(DE);function LE(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new vo}}}function ME(o,e){switch(e.type){case en.DragStart:return{...o,draggable:{...o.draggable,initialCoordinates:e.initialCoordinates,active:e.active}};case en.DragMove:return o.draggable.active==null?o:{...o,draggable:{...o.draggable,translate:{x:e.coordinates.x-o.draggable.initialCoordinates.x,y:e.coordinates.y-o.draggable.initialCoordinates.y}}};case en.DragEnd:case en.DragCancel:return{...o,draggable:{...o.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case en.RegisterDroppable:{const{element:a}=e,{id:r}=a,i=new vo(o.droppable.containers);return i.set(r,a),{...o,droppable:{...o.droppable,containers:i}}}case en.SetDroppableDisabled:{const{id:a,key:r,disabled:i}=e,l=o.droppable.containers.get(a);if(!l||r!==l.key)return o;const u=new vo(o.droppable.containers);return u.set(a,{...l,disabled:i}),{...o,droppable:{...o.droppable,containers:u}}}case en.UnregisterDroppable:{const{id:a,key:r}=e,i=o.droppable.containers.get(a);if(!i||r!==i.key)return o;const l=new vo(o.droppable.containers);return l.delete(a),{...o,droppable:{...o.droppable,containers:l}}}default:return o}}function $E(o){let{disabled:e}=o;const{active:a,activatorEvent:r,draggableNodes:i}=N.useContext(ic),l=jd(r),u=jd(a?.id);return N.useEffect(()=>{if(!e&&!r&&l&&u!=null){if(!Bd(l)||document.activeElement===l.target)return;const d=i.get(u);if(!d)return;const{activatorNode:f,node:g}=d;if(!f.current&&!g.current)return;requestAnimationFrame(()=>{for(const m of[f.current,g.current]){if(!m)continue;const b=xC(m);if(b){b.focus();break}}})}},[r,e,i,u,l]),null}function NE(o,e){let{transform:a,...r}=e;return o!=null&&o.length?o.reduce((i,l)=>l({transform:i,...r}),a):a}function IE(o){return N.useMemo(()=>({draggable:{...rc.draggable,...o?.draggable},droppable:{...rc.droppable,...o?.droppable},dragOverlay:{...rc.dragOverlay,...o?.dragOverlay}}),[o?.draggable,o?.droppable,o?.dragOverlay])}function HE(o){let{activeNode:e,measure:a,initialRect:r,config:i=!0}=o;const l=N.useRef(!1),{x:u,y:d}=typeof i=="boolean"?{x:i,y:i}:i;Ha(()=>{if(!u&&!d||!e){l.current=!1;return}if(l.current||!r)return;const g=e?.node.current;if(!g||g.isConnected===!1)return;const m=a(g),b=vm(m,r);if(u||(b.x=0),d||(b.y=0),l.current=!0,Math.abs(b.x)>0||Math.abs(b.y)>0){const v=wm(g);v&&v.scrollBy({top:b.y,left:b.x})}},[e,u,d,r,a])}const jm=N.createContext({...ma,scaleX:1,scaleY:1});var xr;(function(o){o[o.Uninitialized=0]="Uninitialized",o[o.Initializing=1]="Initializing",o[o.Initialized=2]="Initialized"})(xr||(xr={}));const zE=N.memo(function(e){var a,r,i,l;let{id:u,accessibility:d,autoScroll:f=!0,children:g,sensors:m=RE,collisionDetection:b=BC,measuring:v,modifiers:S,...E}=e;const A=N.useReducer(ME,void 0,LE),[T,L]=A,[M,R]=LC(),[D,U]=N.useState(xr.Uninitialized),j=D===xr.Initialized,{draggable:{active:P,nodes:X,translate:ge},droppable:{containers:Ee}}=T,Ce=P!=null?X.get(P):null,$e=N.useRef({initial:null,translated:null}),Pe=N.useMemo(()=>{var Te;return P!=null?{id:P,data:(Te=Ce?.data)!=null?Te:kE,rect:$e}:null},[P,Ce]),Ye=N.useRef(null),[ht,Q]=N.useState(null),[fe,ve]=N.useState(null),te=uo(E,Object.values(E)),he=ho("DndDescribedBy",u),x=N.useMemo(()=>Ee.getEnabled(),[Ee]),K=IE(v),{droppableRects:re,measureDroppableContainers:de,measuringScheduled:ie}=mE(x,{dragging:j,dependencies:[ge.x,ge.y],config:K.droppable}),ye=gE(X,P),Ne=N.useMemo(()=>fe?Pd(fe):null,[fe]),Se=pe(),He=yE(ye,K.draggable.measure);HE({activeNode:P!=null?X.get(P):null,config:Se.layoutShiftCompensation,initialRect:He,measure:K.draggable.measure});const Ae=Mm(ye,K.draggable.measure,He),Et=Mm(ye?ye.parentElement:null),kt=N.useRef({activatorEvent:null,active:null,activeNode:ye,collisionRect:null,collisions:null,droppableRects:re,draggableNodes:X,draggingNode:null,draggingNodeRect:null,droppableContainers:Ee,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),_t=Ee.getNodeFor((a=kt.current.over)==null?void 0:a.id),ft=TE({measure:K.dragOverlay.measure}),un=(r=ft.nodeRef.current)!=null?r:ye,gt=j?(i=ft.rect)!=null?i:Ae:null,Ua=!!(ft.nodeRef.current&&ft.rect),kn=SE(Ua?null:Ae),Dn=Im(un?Tn(un):null),jt=wE(j?_t??ye:null),On=AE(jt),ea=NE(S,{transform:{x:ge.x-kn.x,y:ge.y-kn.y,scaleX:1,scaleY:1},activatorEvent:fe,active:Pe,activeNodeRect:Ae,containerNodeRect:Et,draggingNodeRect:gt,over:kt.current.over,overlayNodeRect:ft.rect,scrollableAncestors:jt,scrollableAncestorRects:On,windowRect:Dn}),Ba=Ne?ss(Ne,ge):null,Kt=CE(jt),Dr=Nm(Kt),tr=Nm(Kt,[Ae]),bn=ss(ea,Dr),tn=gt?GC(gt,ea):null,et=Pe&&tn?b({active:Pe,collisionRect:tn,droppableRects:re,droppableContainers:x,pointerCoordinates:Ba}):null,ta=ym(et,"id"),[En,nr]=N.useState(null),Ea=Ua?ea:ss(ea,tr),nn=PC(Ea,(l=En?.rect)!=null?l:null,Ae),_n=N.useRef(null),q=N.useCallback((Te,xe)=>{let{sensor:je,options:ze}=xe;if(Ye.current==null)return;const Le=X.get(Ye.current);if(!Le)return;const ke=Te.nativeEvent,Je=new je({active:Ye.current,activeNode:Le,event:ke,options:ze,context:kt,onAbort(_e){if(!X.get(_e))return;const{onDragAbort:Ue}=te.current,st={id:_e};Ue?.(st),M({type:"onDragAbort",event:st})},onPending(_e,Re,Ue,st){if(!X.get(_e))return;const{onDragPending:Wt}=te.current,qt={id:_e,constraint:Re,initialCoordinates:Ue,offset:st};Wt?.(qt),M({type:"onDragPending",event:qt})},onStart(_e){const Re=Ye.current;if(Re==null)return;const Ue=X.get(Re);if(!Ue)return;const{onDragStart:st}=te.current,At={activatorEvent:ke,active:{id:Re,data:Ue.data,rect:$e}};Ar.unstable_batchedUpdates(()=>{st?.(At),U(xr.Initializing),L({type:en.DragStart,initialCoordinates:_e,active:Re}),M({type:"onDragStart",event:At}),Q(_n.current),ve(ke)})},onMove(_e){L({type:en.DragMove,coordinates:_e})},onEnd:Ve(en.DragEnd),onCancel:Ve(en.DragCancel)});_n.current=Je;function Ve(_e){return async function(){const{active:Ue,collisions:st,over:At,scrollAdjustedTranslate:Wt}=kt.current;let qt=null;if(Ue&&Wt){const{cancelDrop:qn}=te.current;qt={activatorEvent:ke,active:Ue,collisions:st,delta:Wt,over:At},_e===en.DragEnd&&typeof qn=="function"&&await Promise.resolve(qn(qt))&&(_e=en.DragCancel)}Ye.current=null,Ar.unstable_batchedUpdates(()=>{L({type:_e}),U(xr.Uninitialized),nr(null),Q(null),ve(null),_n.current=null;const qn=_e===en.DragEnd?"onDragEnd":"onDragCancel";if(qt){const Ln=te.current[qn];Ln?.(qt),M({type:qn,event:qt})}})}}},[X]),oe=N.useCallback((Te,xe)=>(je,ze)=>{const Le=je.nativeEvent,ke=X.get(ze);if(Ye.current!==null||!ke||Le.dndKit||Le.defaultPrevented)return;const Je={active:ke};Te(je,xe.options,Je)===!0&&(Le.dndKit={capturedBy:xe.sensor},Ye.current=ze,q(je,xe))},[X,q]),ce=pE(m,oe);EE(m),Ha(()=>{Ae&&D===xr.Initializing&&U(xr.Initialized)},[Ae,D]),N.useEffect(()=>{const{onDragMove:Te}=te.current,{active:xe,activatorEvent:je,collisions:ze,over:Le}=kt.current;if(!xe||!je)return;const ke={active:xe,activatorEvent:je,collisions:ze,delta:{x:bn.x,y:bn.y},over:Le};Ar.unstable_batchedUpdates(()=>{Te?.(ke),M({type:"onDragMove",event:ke})})},[bn.x,bn.y]),N.useEffect(()=>{const{active:Te,activatorEvent:xe,collisions:je,droppableContainers:ze,scrollAdjustedTranslate:Le}=kt.current;if(!Te||Ye.current==null||!xe||!Le)return;const{onDragOver:ke}=te.current,Je=ze.get(ta),Ve=Je&&Je.rect.current?{id:Je.id,rect:Je.rect.current,data:Je.data,disabled:Je.disabled}:null,_e={active:Te,activatorEvent:xe,collisions:je,delta:{x:Le.x,y:Le.y},over:Ve};Ar.unstable_batchedUpdates(()=>{nr(Ve),ke?.(_e),M({type:"onDragOver",event:_e})})},[ta]),Ha(()=>{kt.current={activatorEvent:fe,active:Pe,activeNode:ye,collisionRect:tn,collisions:et,droppableRects:re,draggableNodes:X,draggingNode:un,draggingNodeRect:gt,droppableContainers:Ee,over:En,scrollableAncestors:jt,scrollAdjustedTranslate:bn},$e.current={initial:gt,translated:tn}},[Pe,ye,et,tn,X,un,gt,re,Ee,En,jt,bn]),dE({...Se,delta:ge,draggingRect:tn,pointerCoordinates:Ba,scrollableAncestors:jt,scrollableAncestorRects:On});const Z=N.useMemo(()=>({active:Pe,activeNode:ye,activeNodeRect:Ae,activatorEvent:fe,collisions:et,containerNodeRect:Et,dragOverlay:ft,draggableNodes:X,droppableContainers:Ee,droppableRects:re,over:En,measureDroppableContainers:de,scrollableAncestors:jt,scrollableAncestorRects:On,measuringConfiguration:K,measuringScheduled:ie,windowRect:Dn}),[Pe,ye,Ae,fe,et,Et,ft,X,Ee,re,En,de,jt,On,K,ie,Dn]),be=N.useMemo(()=>({activatorEvent:fe,activators:ce,active:Pe,activeNodeRect:Ae,ariaDescribedById:{draggable:he},dispatch:L,draggableNodes:X,over:En,measureDroppableContainers:de}),[fe,ce,Pe,Ae,L,he,X,En,de]);return We.createElement(fm.Provider,{value:R},We.createElement(ic.Provider,{value:be},We.createElement(zm.Provider,{value:Z},We.createElement(jm.Provider,{value:nn},g)),We.createElement($E,{disabled:d?.restoreFocus===!1})),We.createElement(NC,{...d,hiddenTextDescribedById:he}));function pe(){const Te=ht?.autoScrollEnabled===!1,xe=typeof f=="object"?f.enabled===!1:f===!1,je=j&&!Te&&!xe;return typeof f=="object"?{...f,enabled:je}:{enabled:je}}}),jE=N.createContext(null),Um="button",UE="Draggable";function BE(o){let{id:e,data:a,disabled:r=!1,attributes:i}=o;const l=ho(UE),{activators:u,activatorEvent:d,active:f,activeNodeRect:g,ariaDescribedById:m,draggableNodes:b,over:v}=N.useContext(ic),{role:S=Um,roleDescription:E="draggable",tabIndex:A=0}=i??{},T=f?.id===e,L=N.useContext(T?jm:jE),[M,R]=Zl(),[D,U]=Zl(),j=_E(u,e),P=uo(a);Ha(()=>(b.set(e,{id:e,key:l,node:M,activatorNode:D,data:P}),()=>{const ge=b.get(e);ge&&ge.key===l&&b.delete(e)}),[b,e]);const X=N.useMemo(()=>({role:S,tabIndex:A,"aria-disabled":r,"aria-pressed":T&&S===Um?!0:void 0,"aria-roledescription":E,"aria-describedby":m.draggable}),[r,S,A,T,E,m.draggable]);return{active:f,activatorEvent:d,activeNodeRect:g,attributes:X,isDragging:T,listeners:r?void 0:j,node:M,over:v,setNodeRef:R,setActivatorNodeRef:U,transform:L}}function PE(){return N.useContext(zm)}const qE="Droppable",GE={timeout:25};function VE(o){let{data:e,disabled:a=!1,id:r,resizeObserverConfig:i}=o;const l=ho(qE),{active:u,dispatch:d,over:f,measureDroppableContainers:g}=N.useContext(ic),m=N.useRef({disabled:a}),b=N.useRef(!1),v=N.useRef(null),S=N.useRef(null),{disabled:E,updateMeasurementsFor:A,timeout:T}={...GE,...i},L=uo(A??r),M=N.useCallback(()=>{if(!b.current){b.current=!0;return}S.current!=null&&clearTimeout(S.current),S.current=setTimeout(()=>{g(Array.isArray(L.current)?L.current:[L.current]),S.current=null},T)},[T]),R=ac({callback:M,disabled:E||!u}),D=N.useCallback((X,ge)=>{R&&(ge&&(R.unobserve(ge),b.current=!1),X&&R.observe(X))},[R]),[U,j]=Zl(D),P=uo(e);return N.useEffect(()=>{!R||!U.current||(R.disconnect(),b.current=!1,R.observe(U.current))},[U,R]),N.useEffect(()=>(d({type:en.RegisterDroppable,element:{id:r,key:l,disabled:a,node:U,rect:v,data:P}}),()=>d({type:en.UnregisterDroppable,key:l,id:r})),[r]),N.useEffect(()=>{a!==m.current.disabled&&(d({type:en.SetDroppableDisabled,id:r,key:l,disabled:a}),m.current.disabled=a)},[r,l,a,d]),{active:u,rect:v,isOver:f?.id===r,node:U,over:f,setNodeRef:j}}function Zd(o,e,a){const r=o.slice();return r.splice(a<0?r.length+a:a,0,r.splice(e,1)[0]),r}function FE(o,e){return o.reduce((a,r,i)=>{const l=e.get(r);return l&&(a[i]=l),a},Array(o.length))}function sc(o){return o!==null&&o>=0}function KE(o,e){if(o===e)return!0;if(o.length!==e.length)return!1;for(let a=0;a{var e;let{rects:a,activeNodeRect:r,activeIndex:i,overIndex:l,index:u}=o;const d=(e=a[i])!=null?e:r;if(!d)return null;const f=XE(a,u,i);if(u===i){const g=a[l];return g?{x:ii&&u<=l?{x:-d.width-f,y:0,...oc}:u=l?{x:d.width+f,y:0,...oc}:{x:0,y:0,...oc}};function XE(o,e,a){const r=o[e],i=o[e-1],l=o[e+1];return!r||!i&&!l?0:a{let{rects:e,activeIndex:a,overIndex:r,index:i}=o;const l=Zd(e,r,a),u=e[i],d=l[i];return!d||!u?null:{x:d.left-u.left,y:d.top-u.top,scaleX:d.width/u.width,scaleY:d.height/u.height}},lc={scaleX:1,scaleY:1},JE=o=>{var e;let{activeIndex:a,activeNodeRect:r,index:i,rects:l,overIndex:u}=o;const d=(e=l[a])!=null?e:r;if(!d)return null;if(i===a){const g=l[u];return g?{x:0,y:aa&&i<=u?{x:0,y:-d.height-f,...lc}:i=u?{x:0,y:d.height+f,...lc}:{x:0,y:0,...lc}};function QE(o,e,a){const r=o[e],i=o[e-1],l=o[e+1];return r?ar.map(j=>typeof j=="object"&&"id"in j?j.id:j),[r]),E=u!=null,A=u?S.indexOf(u.id):-1,T=g?S.indexOf(g.id):-1,L=N.useRef(S),M=!KE(S,L.current),R=T!==-1&&A===-1||M,D=WE(l);Ha(()=>{M&&E&&m(S)},[M,S,E,m]),N.useEffect(()=>{L.current=S},[S]);const U=N.useMemo(()=>({activeIndex:A,containerId:b,disabled:D,disableTransforms:R,items:S,overIndex:T,useDragOverlay:v,sortedRects:FE(S,f),strategy:i}),[A,b,D.draggable,D.droppable,R,S,T,f,v,i]);return We.createElement(Pm.Provider,{value:U},e)}const e_=o=>{let{id:e,items:a,activeIndex:r,overIndex:i}=o;return Zd(a,r,i).indexOf(e)},t_=o=>{let{containerId:e,isSorting:a,wasDragging:r,index:i,items:l,newIndex:u,previousItems:d,previousContainerId:f,transition:g}=o;return!g||!r||d!==l&&i===u?!1:a?!0:u!==i&&e===f},n_={duration:200,easing:"ease"},qm="transform",a_=po.Transition.toString({property:qm,duration:0,easing:"linear"}),r_={roleDescription:"sortable"};function i_(o){let{disabled:e,index:a,node:r,rect:i}=o;const[l,u]=N.useState(null),d=N.useRef(a);return Ha(()=>{if(!e&&a!==d.current&&r.current){const f=i.current;if(f){const g=os(r.current,{ignoreTransform:!0}),m={x:f.left-g.left,y:f.top-g.top,scaleX:f.width/g.width,scaleY:f.height/g.height};(m.x||m.y)&&u(m)}}a!==d.current&&(d.current=a)},[e,a,r,i]),N.useEffect(()=>{l&&u(null)},[l]),l}function s_(o){let{animateLayoutChanges:e=t_,attributes:a,disabled:r,data:i,getNewIndex:l=e_,id:u,strategy:d,resizeObserverConfig:f,transition:g=n_}=o;const{items:m,containerId:b,activeIndex:v,disabled:S,disableTransforms:E,sortedRects:A,overIndex:T,useDragOverlay:L,strategy:M}=N.useContext(Pm),R=o_(r,S),D=m.indexOf(u),U=N.useMemo(()=>({sortable:{containerId:b,index:D,items:m},...i}),[b,i,D,m]),j=N.useMemo(()=>m.slice(m.indexOf(u)),[m,u]),{rect:P,node:X,isOver:ge,setNodeRef:Ee}=VE({id:u,data:U,disabled:R.droppable,resizeObserverConfig:{updateMeasurementsFor:j,...f}}),{active:Ce,activatorEvent:$e,activeNodeRect:Pe,attributes:Ye,setNodeRef:ht,listeners:Q,isDragging:fe,over:ve,setActivatorNodeRef:te,transform:he}=BE({id:u,data:U,attributes:{...r_,...a},disabled:R.draggable}),x=CC(Ee,ht),K=!!Ce,re=K&&!E&&sc(v)&&sc(T),de=!L&&fe,ie=de&&re?he:null,Ne=re?ie??(d??M)({rects:A,activeNodeRect:Pe,activeIndex:v,overIndex:T,index:D}):null,Se=sc(v)&&sc(T)?l({id:u,items:m,activeIndex:v,overIndex:T}):D,He=Ce?.id,Ae=N.useRef({activeId:He,items:m,newIndex:Se,containerId:b}),Et=m!==Ae.current.items,kt=e({active:Ce,containerId:b,isDragging:fe,isSorting:K,id:u,index:D,items:m,newIndex:Ae.current.newIndex,previousItems:Ae.current.items,previousContainerId:Ae.current.containerId,transition:g,wasDragging:Ae.current.activeId!=null}),_t=i_({disabled:!kt,index:D,node:X,rect:P});return N.useEffect(()=>{K&&Ae.current.newIndex!==Se&&(Ae.current.newIndex=Se),b!==Ae.current.containerId&&(Ae.current.containerId=b),m!==Ae.current.items&&(Ae.current.items=m)},[K,Se,b,m]),N.useEffect(()=>{if(He===Ae.current.activeId)return;if(He!=null&&Ae.current.activeId==null){Ae.current.activeId=He;return}const un=setTimeout(()=>{Ae.current.activeId=He},50);return()=>clearTimeout(un)},[He]),{active:Ce,activeIndex:v,attributes:Ye,data:U,rect:P,index:D,newIndex:Se,items:m,isOver:ge,isSorting:K,isDragging:fe,listeners:Q,node:X,overIndex:T,over:ve,setNodeRef:x,setActivatorNodeRef:te,setDroppableNodeRef:Ee,setDraggableNodeRef:ht,transform:_t??Ne,transition:ft()};function ft(){if(_t||Et&&Ae.current.newIndex===D)return a_;if(!(de&&!Bd($e)||!g)&&(K||kt))return po.Transition.toString({...g,property:qm})}}function o_(o,e){var a,r;return typeof o=="boolean"?{draggable:o,droppable:!1}:{draggable:(a=o?.draggable)!=null?a:e.draggable,droppable:(r=o?.droppable)!=null?r:e.droppable}}function cc(o){if(!o)return!1;const e=o.data.current;return!!(e&&"sortable"in e&&typeof e.sortable=="object"&&"containerId"in e.sortable&&"items"in e.sortable&&"index"in e.sortable)}const l_=[it.Down,it.Right,it.Up,it.Left],c_=(o,e)=>{let{context:{active:a,collisionRect:r,droppableRects:i,droppableContainers:l,over:u,scrollableAncestors:d}}=e;if(l_.includes(o.code)){if(o.preventDefault(),!a||!r)return;const f=[];l.getEnabled().forEach(b=>{if(!b||b!=null&&b.disabled)return;const v=i.get(b.id);if(v)switch(o.code){case it.Down:r.topv.top&&f.push(b);break;case it.Left:r.left>v.left&&f.push(b);break;case it.Right:r.left1&&(m=g[1].id),m!=null){const b=l.get(a.id),v=l.get(m),S=v?i.get(v.id):null,E=v?.node.current;if(E&&S&&b&&v){const T=tc(E).some((j,P)=>d[P]!==j),L=Gm(b,v),M=u_(b,v),R=T||!L?{x:0,y:0}:{x:M?r.width-S.width:0,y:M?r.height-S.height:0},D={x:S.left,y:S.top};return R.x&&R.y?D:go(D,R)}}}};function Gm(o,e){return!cc(o)||!cc(e)?!1:o.data.current.sortable.containerId===e.data.current.sortable.containerId}function u_(o,e){return!cc(o)||!cc(e)||!Gm(o,e)?!1:o.data.current.sortable.index{const d=N.useId(),f=N.useRef({inProgress:!1,lastSortTime:0,lastActiveId:null}),g=IC(hm(Yd,{activationConstraint:{distance:5}}),hm(Kd,{coordinateGetter:c_})),m=N.useCallback(S=>{f.current={inProgress:!0,lastSortTime:Date.now(),lastActiveId:S.active.id},l?.({activeId:S.active.id})},[l]),b=N.useCallback(S=>{const{active:E,over:A}=S,T=Date.now();if(!A||E.id===A.id){f.current.inProgress=!1;return}const{lastSortTime:L,lastActiveId:M,inProgress:R}=f.current;if(M===E.id&&T-L<500&&!R)return;f.current={inProgress:!1,lastSortTime:T,lastActiveId:E.id};const D=o.findIndex(j=>String(j[e])===String(E.id)),U=o.findIndex(j=>String(j[e])===String(A.id));if(D!==-1&&U!==-1&&D!==U){const j=Zd(o,D,U);i?.(j,{oldIndex:D,newIndex:U})}},[o,e,i]),v=o.map(S=>String(S[e]));return ut.jsx(zE,{id:d,sensors:g,collisionDetection:zC,onDragStart:m,onDragEnd:b,children:ut.jsx(ZE,{items:v,strategy:d_[a],children:r})},u!==void 0?`${d}-v${u}`:d)},Vm=N.createContext(null),h_=({value:o,children:e})=>ut.jsx(Vm.Provider,{value:o,children:e}),g_=()=>N.useContext(Vm),p_=({id:o,handle:e,as:a="div",children:r})=>{const{attributes:i,listeners:l,setNodeRef:u,transform:d,transition:f,isDragging:g}=s_({id:o}),m={transform:po.Transform.toString(d),transition:f,opacity:g?.5:1,position:"relative"};return ut.jsx(a,{ref:u,style:m,...i,...e?{}:l,"data-sortable-item":!0,"data-sortable-id":String(o),"data-dragging":g,children:ut.jsx(h_,{value:{listeners:l,handle:e,isDragging:g},children:r})})},dt=ct("DynamicRenderer");function Za(){try{return window.G7Core?.devTools}catch{return}}function tf(o,e,a,r){if(typeof o=="string"){if(/\$t:[a-zA-Z_][a-zA-Z0-9_.\-]*/.test(o))try{return e.resolveTranslations(o,a,r)}catch{return o}return o}if(Array.isArray(o))return o.map(i=>tf(i,e,a,r));if(o&&typeof o=="object"){const i={};for(const[l,u]of Object.entries(o))i[l]=tf(u,e,a,r);return i}return o}function m_(o){const e=Object.keys(o);return e.length>0&&e.every(a=>/^\d+$/.test(a))}const ya=(o,e)=>{const a=["errors"],r={...o};for(const i of Object.keys(e)){const l=e[i],u=o[i];if(l===null){r[i]=null;continue}if(a.includes(i)){r[i]=l;continue}if(Array.isArray(l)){r[i]=l;continue}if(Array.isArray(u)&&l!==null&&typeof l=="object"&&!Array.isArray(l)&&m_(l)){const d=Object.keys(l).map(g=>parseInt(g,10));if((d.length>0?Math.max(...d):0)>=u.length+d.length+10)r[i]={...l};else{const g=[...u];for(const[m,b]of Object.entries(l)){const v=parseInt(m,10);v>=0&&v=g.length&&(g[v]=b)}r[i]=g}}else l!==null&&typeof l=="object"&&u!==null&&typeof u=="object"&&!Array.isArray(u)?r[i]=ya(u,l):r[i]=l}return r},Fm=(o,e)=>{const a={...o};for(const r of Object.keys(e)){if(!(r in a))continue;const i=e[r],l=a[r];if(i!==null&&typeof i=="object"&&!Array.isArray(i)&&l!==null&&typeof l=="object"&&!Array.isArray(l)){const u=Fm(l,i);Object.keys(u).length===0?delete a[r]:a[r]=u}else delete a[r]}return a},y_=o=>{const e={},a=o.split(";").filter(r=>r.trim());for(const r of a){const i=r.indexOf(":");if(i===-1)continue;const l=r.substring(0,i).trim(),u=r.substring(i+1).trim();if(l&&u){const d=l.replace(/-([a-z])/g,(f,g)=>g.toUpperCase());e[d]=u}}return e};class b_ extends N.Component{constructor(e){super(e),this.state={hasError:!1,error:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,a){dt.error(`컴포넌트 렌더링 에러 (ID: ${this.props.componentId}, Name: ${this.props.componentName}):`,e,a)}render(){return this.state.hasError?this.props.fallback?this.props.fallback:ut.jsxs("div",{className:"p-4 my-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-red-800 dark:text-red-400",children:[ut.jsx("div",{className:"font-semibold mb-1",children:"컴포넌트 로드 실패"}),ut.jsxs("div",{className:"text-xs opacity-80",children:[this.props.componentName&&`[${this.props.componentName}] `,"데이터를 표시할 수 없습니다."]}),this.state.error&&ut.jsxs("details",{className:"mt-2",children:[ut.jsx("summary",{className:"cursor-pointer text-xs opacity-70 hover:opacity-100",children:"상세 정보"}),ut.jsx("div",{className:"mt-1 p-2 bg-red-100 dark:bg-red-900/30 rounded text-xs font-mono whitespace-pre-wrap break-all",children:this.state.error.message})]})]}):this.props.children}}const za=N.memo(({componentDef:o,dataContext:e,translationContext:a,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:u,parentComponentContext:d,parentFormContextProp:f,parentDataContext:g,isRootRenderer:m=!1,isInsideIteration:b=!1,isEditMode:v=!1,onComponentSelect:S,onComponentHover:E,onDragStart:A,onDragEnd:T,componentPath:L,layoutKey:M})=>{const[R,D]=N.useState({loadingActions:{}});N.useLayoutEffect(()=>{d||(i.startRenderCycle(),window.__g7SetLocalOverrideKeys||(window.__g7ForcedLocalFields=void 0),window.__g7PendingLocalState=null)});const U=N.useRef(null);N.useLayoutEffect(()=>{if(!d&&e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:q,_merge:oe,...ce}=e._localInit,Z=`${JSON.stringify(ce)}:${q||"no-force"}`;if(U.current!==Z){U.current=Z;const be=oe||"shallow",Te=window.G7Core?.state?.get?.()?._local||{},xe=window.__g7PendingLocalState||{},je={...Te,...xe};be==="replace"?window.__g7PendingLocalState={loadingActions:je.loadingActions||{},...ce,hasChanges:!1}:be==="deep"?window.__g7PendingLocalState=ya(je,{...ce,hasChanges:!1}):window.__g7PendingLocalState={...je,...ce,hasChanges:!1};const ze=window.__g7SetLocalOverrideKeys;ze&&typeof ze=="object"&&(be==="replace"?window.__g7SetLocalOverrideKeys={...ce,hasChanges:!1}:window.__g7SetLocalOverrideKeys=ya(ze,{...ce,hasChanges:!1}))}}}),N.useLayoutEffect(()=>{if(!d){const q=window.__g7SetLocalOverrideKeys;if(q){D(be=>Fm(be,q));const oe=q,ce=window.__g7ForcedLocalFields,Z=window.__g7LastSetLocalSnapshot;queueMicrotask(()=>{window.__g7SetLocalOverrideKeys===oe&&(window.__g7SetLocalOverrideKeys=void 0),window.__g7ForcedLocalFields===ce&&(window.__g7ForcedLocalFields=void 0),window.__g7LastSetLocalSnapshot===Z&&(window.__g7LastSetLocalSnapshot=void 0)})}}},[e._local]);const j=N.useRef(M);N.useEffect(()=>{o._fromBase&&M&&j.current!==M&&(j.current=M,D({loadingActions:{}}))},[M,o._fromBase]);const P=N.useRef(""),X=N.useRef(!1),ge=N.useRef(null),Ee=N.useRef({}),Ce=N.useRef({}),$e=N.useRef(g),Pe=wC(),Ye=Q0(),ht=Ye?.getParentDataContext()??g;N.useEffect(()=>{if(e._localInit&&typeof e._localInit=="object"){const{_forceLocalInit:q,...oe}=e._localInit,ce=JSON.stringify(oe),Z=q!==void 0,be=Md(),pe=`${ce}:${q||"no-force"}`;if(be.hash!==pe){be.hash=pe,be.timestamp=q,P.current=ce,Z&&(X.current=!0);const{_merge:je,...ze}=oe,Le=je||"shallow";D(ke=>Le==="replace"?{loadingActions:ke.loadingActions||{},...ze,hasChanges:!1}:Le==="deep"?ya(ke,{...ze,hasChanges:!1}):{...ke,...ze,hasChanges:!1}),e._globalSetState&&e._globalSetState(ke=>{const Je=ke?._local||{};let Ve;return Le==="replace"?Ve={...ze,hasChanges:!1}:Le==="deep"?Ve=ya(Je,{...ze,hasChanges:!1}):Ve={...Je,...ze,hasChanges:!1},{...ke,_local:Ve}}),i.invalidateCacheByKeys(["_local"]),dt.log("_localInit applied (data changed):",Object.keys(oe))}ge.current=e._localInit,rC(e._localInit)}},[e._localInit,g,i]);const{isTransitioning:Q}=am(),fe=fC(),ve=f??fe,{width:te}=Nd(),he=g_(),x=N.useMemo(()=>{let q=o;if(o.type==="iterator"&&o.data){const{data:Z,itemName:be,indexName:pe,...Te}=o;q={...Te,iteration:{source:Z,item_var:be||"item",index_var:pe}}}if(!q.responsive)return q;const oe=ui.getMatchingKey(q.responsive,te);if(!oe)return q;const ce=q.responsive[oe];return{...q,props:{...q.props,...ce.props},children:ce.children??q.children,text:ce.text??q.text,if:ce.if??q.if,iteration:ce.iteration??q.iteration,__responsiveChildrenKey:ce.children!==void 0?oe:void 0}},[o,te]),K=N.useCallback(q=>{const oe=q?.__setStateId;if(D(ce=>{const Z=window.__g7PendingLocalState,pe=window.__g7SetLocalOverrideKeys||Z,Te=pe?ya(ce,pe):ce;if(typeof q=="function"){const ke=q(Te);return ya(Te,ke)}const{__mergeMode:xe,__setStateId:je,...ze}=q;let Le;if(xe==="replace"?Le=ze:xe==="shallow"?Le={...Te,...ze}:Le=ya(Te,ze),xe!==void 0){const ke=window.__g7ForcedLocalFields;ke&&(window.__g7ForcedLocalFields={...ke,...ze})}return Le}),oe){const ce=Za();if(ce?.isEnabled()){const Z=x.id||"unknown",be=x.name||"Unknown";setTimeout(()=>{ce.trackComponentRender?.(Z,be,0,["_local"],[]),ce.completeStateChange?.(oe)},0)}}},[x.id,x.name]),re=d?.state??R,de=d?.setState??K,ie=N.useMemo(()=>{const q=e._localInit&&e._localInit!==ge.current;let oe;q&&e._local?oe=e._local:oe=e._local?ya(e._local,re):re;const ce=window.__g7ForcedLocalFields;ce&&(oe=ya(oe,ce));const Z={...e,_local:oe},be=e._computedDefinitions;if(be&&Object.keys(be).length>0){const pe=new wn,Te={...Z,...Pe?{_isolated:Pe.state}:{}},xe={},je=Za(),ze=je?.isEnabled()??!1;for(const[Le,ke]of Object.entries(be)){const Je=ze?Date.now():0,Ve=ze?e._computed?.[Le]:void 0;let _e;try{if(typeof ke=="string")if(ke.startsWith("{{")&&ke.endsWith("}}")){const Re=ke.slice(2,-2).trim(),Ue=pe.evaluateExpression(Re,Te,{skipCache:!0});xe[Le]=Ue}else xe[Le]=ke;else if(ke&&typeof ke=="object"&&"$switch"in ke){const Re=pe.resolveSwitch(ke,Te,{skipCache:!0});xe[Le]=Re}}catch(Re){dt.warn(`Failed to recalculate computed value: ${Le}`,Re),xe[Le]=e._computed?.[Le],_e=Re instanceof Error?Re.message:String(Re)}if(ze&&(_e||Ve!==xe[Le])){const Ue=Date.now()-Je;je.trackComputedProperty?.(Le,typeof ke=="string"?ke:JSON.stringify(ke),[{type:"_local",path:"_local",value:Z._local}],xe[Le],Ue,x.id,_e),Ve!==xe[Le]&&je.trackComputedRecalc?.(Le,"dependency-change",Ve,xe[Le],Ue,{type:"_local",path:"_local"},x.id)}}Z._computed=xe}if(Pe&&(Z._isolated=Pe.state),ht&&(Z.$parent=ht),o.extensionPointProps){const pe=i.resolveObject(o.extensionPointProps,Z,b?{skipCache:!0}:void 0);Z.extensionPointProps=v&&l?tf(pe,l,a,Z):pe}return o.extensionPointCallbacks&&(Z.extensionPointCallbacks=o.extensionPointCallbacks),v&&(Z.$templateId===void 0&&a?.templateId&&(Z.$templateId=a.templateId),Z.$locale===void 0&&a?.locale&&(Z.$locale=a.locale)),Z},[e,re,Pe?.state,ht,Ye?.version,o.extensionPointProps,o.extensionPointCallbacks]);Ee.current=ie._local,Ce.current=ie._computed||{},$e.current=ht,N.useEffect(()=>{const q=Za();if(!q?.isEnabled())return;const oe=ie._local;oe&&q.updateLocalState(oe);const ce=ie._computed;ce&&q.updateComputedState(ce),ht&&q.updateParentContext?.(ht);const Z=x.id||`comp-${Date.now()}`,be=x.name||"Unknown",pe={global:Object.keys(e._global||{}),local:Object.keys(oe||{}),context:d?Object.keys(d.state||{}):[]},Te={type:d?"componentContext":"dynamicState",hasComponentContext:!!d,parentId:d?"parent":void 0};q.trackComponentStateSource?.(Z,be,pe,Te),re&&Object.keys(re).length>0&&q.trackDynamicState?.(Z,re);const xe=!!(x.expandChildren||x.props?.expandChildren);q.trackContextFlow?.(Z,be,!!d,xe,!0)},[ie._local,e,x.id,x.name,d,re,x.expandChildren,x.props?.expandChildren,ht]);const ye=N.useMemo(()=>({state:{...ie._local},setState:de,stateRef:Ee,computedRef:Ce,isolatedContext:Pe,parentDataContext:ie}),[ie._local,de,R,Pe,ie]),Ne=N.useMemo(()=>{const q=Ji({if:x.if,condition:x.condition,conditions:x.conditions},ie,i,x.id);if(x.if||x.condition!==void 0||x.conditions){const ce=Za();ce?.isEnabled()&&x.if&&ce.trackIfCondition(x.id||`if-${Date.now()}`,x.if,q)}return q},[x.if,x.conditions,ie,i,x.id]),Se=N.useMemo(()=>{if(!x.slot)return null;const q=x.slot;if(q.startsWith("{{")&&q.endsWith("}}")){const oe=q.slice(2,-2).trim();try{const ce={componentId:x.id,componentName:x.name,propName:"slot"},Z=i.evaluateExpression(oe,ie,ce);return dt.log(`[Slot] 표현식 평가: "${oe}" => "${Z}" (컴포넌트: ${x.id})`),typeof Z=="string"?Z:null}catch(ce){return dt.warn(`slot 표현식 평가 실패 (컴포넌트: ${x.id}):`,ce),null}}return dt.log(`[Slot] 정적 슬롯 ID: "${q}" (컴포넌트: ${x.id})`),q},[x.slot,x.id,ie,i]),He=N.useMemo(()=>!Se||!x.id?"":`${x.id}-${Se}-${JSON.stringify(x.slotOrder??0)}`,[Se,x.id,x.slotOrder]),Ae=N.useRef(null);N.useEffect(()=>{const q=window.__slotContextValue,oe=q?.isEnabled??!1;if(x.slot&&dt.log(`[Slot Registration] 컴포넌트: ${x.id}, isSlotEnabled: ${oe}, shouldRender: ${Ne}, resolvedSlotId: ${Se}`),!oe||!Ne){x.slot&&dt.log(`[Slot Registration] 등록 스킵 - isSlotEnabled: ${oe}, shouldRender: ${Ne}`),Ae.current&&x.id&&q&&(q.unregisterFromSlot(Ae.current,x.id),Ae.current=null);return}if(!Se||!x.id){x.slot&&dt.log(`[Slot Registration] 등록 스킵 - resolvedSlotId: ${Se}, id: ${x.id}`),Ae.current&&x.id&&q&&(q.unregisterFromSlot(Ae.current,x.id),Ae.current=null);return}Ae.current&&Ae.current!==Se&&q.unregisterFromSlot(Ae.current,x.id);const ce={...x,slot:void 0,slotOrder:void 0};return q.registerToSlot(Se,x.id,{componentDef:ce,dataContext:e,order:x.slotOrder??0,parentFormContext:f??null,getParentComponentContext:()=>ye,translationContext:a,registrationKey:He}),Ae.current=Se,()=>{const Z=window.__slotContextValue;Se&&x.id&&Z&&Z.unregisterFromSlot(Se,x.id)}},[Ne,Se,x,e,f,ye,a,He]);const Et=N.useCallback(q=>{if(typeof q=="string"){if(ip(q))return op(q);if(sp(q)){const oe=[],ce=q.replace(new RegExp(`${Rl}([^${Xs}]*)${Xs}`,"g"),(be,pe)=>(oe.push(pe),`${_r}${oe.length-1}${_r}`));let Z=ce;return/\$t:[a-zA-Z0-9._-]+/.test(ce)&&(Z=l.resolveTranslations(ce,a,ie)),Z.replace(new RegExp(`${_r}(\\d+)${_r}`,"g"),(be,pe)=>oe[parseInt(pe)])}if(q.startsWith("$t:defer:"))return q;if(/\$t:[a-zA-Z0-9._-]+/.test(q)){const oe=q.trim();return oe.startsWith("{")&&oe.endsWith("}")||oe.startsWith("[")&&oe.endsWith("]")?q:l.resolveTranslations(q,a,ie)}return q}if(Array.isArray(q))return q.map(oe=>Et(oe));if(q&&typeof q=="object"){const oe={};for(const[ce,Z]of Object.entries(q))oe[ce]=Et(Z);return oe}return q},[l,a,ie]),kt=N.useCallback(q=>/[?:|&!+\-*/<>=()]/.test(q),[]),_t=N.useCallback(q=>{if(!q.startsWith("{{")||!q.endsWith("}}"))return null;const oe=q.slice(2,-2);let ce=null,Z=0;for(let be=0;be0?oe[be-1]:"")!=="\\"&&((pe==='"'||pe==="'")&&!ce?ce=pe:pe===ce&&(ce=null),!ce&&(pe==="{"&&Z++,pe==="}"&&Z--,Z<0)))return null}return Z===0?oe.trim():null},[]),ft=N.useMemo(()=>{let q={...x.props||{}};const oe=["cellChildren","expandChildren","expandContext","render"],Z=r.getMetadata(x.name)?.skipBindingKeys||[],be=[...new Set([...oe,...Z])],pe=b?{skipCache:!0}:void 0,Te=_e=>{if(pe)return pe;if(_e.startsWith("_computed")||_e.startsWith("$computed"))return{skipCache:!0}};for(const[_e,Re]of Object.entries(q))if(!be.includes(_e))if(typeof Re=="string"){const Ue=_t(Re);if(Ue!==null){let st=!1,At=Ue;Ue.startsWith($a)&&(st=!0,At=Ue.slice($a.length));const Wt={componentId:x.id,componentName:x.name,propName:_e};let qt;if(ci(At))qt=i.resolveBindings(`{{${At}}}`,ie,pe,Wt);else if(kt(At))try{qt=i.evaluateExpression(At,ie,Wt)}catch(qn){dt.warn(`표현식 평가 실패 (컴포넌트: ${x.id}, prop: ${_e}):`,qn),qt=void 0}else qt=i.resolve(At,ie,Te(At),Wt);q[_e]=st&&qt!=null?Xi(qt):qt}else{const st={componentId:x.id,componentName:x.name,propName:_e};q[_e]=i.resolveBindings(Re,ie,pe,st)}}else Array.isArray(Re)?q[_e]=Re.map(Ue=>typeof Ue=="string"?i.resolveBindings(Ue,ie,pe):typeof Ue=="object"&&Ue!==null?i.resolveObject(Ue,ie,pe):Ue):Re&&typeof Re=="object"&&(q[_e]=i.resolveObject(Re,ie,pe));for(const[_e,Re]of Object.entries(q))q[_e]=Et(Re);if(typeof q.style=="string"&&(q.style=y_(q.style)),q=cd(q,v?void 0:x.actions,ie,{componentContext:ye,actionDispatcher:u}),x.classMap){const _e=ow(x.classMap,ie,i,{skipCache:!0});if(_e){const Re=q.className||"";q.className=Re?`${Re} ${_e}`:_e}}x.style&&(q.style=x.style),x.actions&&x.actions.length>0&&x.type==="composite"&&Object.assign(q,re),x.component_layout&&(q.__componentLayoutDefs=x.component_layout);const xe=x.expandChildren||x.props?.expandChildren,je=x.props?.cellChildren,ze=x.children&&x.children.length>0,Le=x.component_layout,ke=x.slot,Je=x.props?.modalId,Ve=x.type==="composite";return(xe||je||ze||Le||ke||Je||Ve)&&(q.__componentContext=ye),q},[x.props,x.actions,x.style,x.classMap,x.id,ie,a,i,l,ye,re,Et,kt,_t,b]),un=N.useRef(null),gt=N.useMemo(()=>un.current&&im(un.current,ft)?un.current:(un.current=ft,ft),[ft]),Ua=N.useMemo(()=>{const q=x.id;if(typeof q!="string"||!q.includes("{{"))return q;const oe=i.resolveBindings(q,ie,{skipCache:!0});return typeof oe=="string"?oe:q},[x.id,ie,i]),kn=N.useMemo(()=>{const q=x.dataKey,oe=x.trackChanges,ce=x.debounce;if(q){const Z=q.startsWith("_global."),be=Z?q.slice(8):q,pe=Z?e._global:ie._local;return{dataKey:be,trackChanges:oe??!1,debounce:ce,setState:Z?xe=>{e._globalSetState?e._globalSetState(xe):dt.warn("_global.formData 사용 시 _globalSetState가 필요합니다.")}:de,state:pe??{},_isGlobal:Z}}if(ve.dataKey)return ve},[x.dataKey,x.trackChanges,x.debounce,x.id,de,ie._local,e._global,e._globalSetState,ve]),Dn=N.useRef(new Set);N.useEffect(()=>{Dn.current.clear()},[e._global?._remountKeys]);const jt=N.useCallback((q,oe)=>{const ce=e._global?._remountKeys;return q&&ce?.[q]&&!Dn.current.has(q)?(Dn.current.add(q),`${q}-remount-${ce[q]}`):q||oe},[e._global?._remountKeys]),On=N.useMemo(()=>{if(x.text!==void 0){if(typeof x.text=="string"){const q=x.text;let oe=q;const ce={componentId:x.id,componentName:x.name,propName:"text",skipCache:!0},Z=_t(q);if(Z!==null){let be=!1,pe=Z;if(Z.startsWith($a)&&(be=!0,pe=Z.slice($a.length)),ci(pe))oe=i.resolveBindings(`{{${pe}}}`,ie,{skipCache:!0},ce);else if(kt(pe))try{oe=i.evaluateExpression(pe,ie,ce)}catch(Te){dt.warn(`text 표현식 평가 실패 (컴포넌트: ${x.id}):`,Te),oe=""}else oe=i.resolve(pe,ie,{skipCache:!0},ce);be&&oe!=null&&(oe=Xi(oe))}else q.includes("{{")&&(oe=i.resolveBindings(q,ie,{skipCache:!0},ce));return oe=Et(oe),oe??""}return x.text}return!x.children||x.children.length===0?null:x.children.map((q,oe)=>{if(typeof q=="string")return q;const ce=x.__responsiveChildrenKey,Z=ce?`responsive.${ce}.children`:"children",be=L?`${L}.${Z}.${oe}`:`${oe}`,pe=q._fromBase?q.id||`child-${oe}`:jt(q.id,`child-${oe}`),Te=!q._fromBase&&M?`${pe}_${M}`:pe;return ut.jsx(za,{componentDef:q,dataContext:ie,translationContext:a,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:u,parentComponentContext:ye,parentFormContextProp:kn,isInsideIteration:b,isEditMode:v,onComponentSelect:S,onComponentHover:E,componentPath:be,onDragStart:A,onDragEnd:T,layoutKey:M},Te)})},[x.text,x.children,x.id,e,ie,a,r,i,l,u,ye,kn,_t,kt,Et,jt,b,v,S,E,L,A,T,M]),ea=N.useMemo(()=>{if(!x.iteration)return null;try{const q=ld(x.iteration.source,ie,i);if(!Array.isArray(q))return dt.warn(`iteration source가 배열이 아닙니다 (컴포넌트: ${x.id}):`,q),null;const oe=Za();return oe?.isEnabled()&&oe.trackIteration(x.id||`iteration-${Date.now()}`,x.iteration.source,x.iteration.item_var,x.iteration.index_var,q.length),q.map((ce,Z)=>{const be={...ie,[x.iteration.item_var]:ce};x.iteration.index_var&&(be[x.iteration.index_var]=Z);const pe=Z===0||Z===q.length-1;oe?.isEnabled()&&pe&&oe.trackNestedContext?.({componentId:`${x.id||"unknown"}-iter-${Z}`,componentType:"iteration",parentContext:{available:Object.keys(ie),values:{}},ownContext:{added:[x.iteration.item_var,...x.iteration.index_var?[x.iteration.index_var]:[]],values:{[x.iteration.item_var]:ce,...x.iteration.index_var?{[x.iteration.index_var]:Z}:{}}},depth:1});const Te={...x,iteration:void 0,responsive:void 0},xe=L?`${L}.iteration.${Z}`:`iteration.${Z}`,je=`${jt(x.id,"item")}-${Z}`;return ut.jsx(za,{componentDef:Te,dataContext:be,translationContext:a,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:u,parentComponentContext:ye,parentFormContextProp:kn,isInsideIteration:!0,isEditMode:v,onComponentSelect:S,onComponentHover:E,componentPath:xe,onDragStart:A,onDragEnd:T,layoutKey:M},je)})}catch(q){return dt.error(`iteration 렌더링 실패 (컴포넌트: ${x.id}):`,q),null}},[x.iteration,x.children,x.id,e,ie,a,r,i,l,u,ye,kn,jt,v,S,E,L,A,T,M]),[Ba,Kt]=N.useState(0),Dr=N.useMemo(()=>{if(!x.sortable||!x.itemTemplate)return null;const{sortable:q,itemTemplate:oe}=x;try{const ce=ld(q.source,ie,i);if(!Array.isArray(ce))return dt.warn(`sortable source가 배열이 아닙니다 (컴포넌트: ${x.id}):`,ce),null;if(ce.length===0)return null;const Z=q.itemKey||"id",be=q.strategy||"verticalList",pe=q.itemVar||"$item",Te=q.indexVar||"$index",xe=q.handle,je=q.wrapperElement,ze=(Je,Ve)=>{const _e=x.actions?.filter(Re=>Re.event==="onSortEnd"||Re.type==="onSortEnd");if(Kt(Re=>Re+1),_e&&_e.length>0&&u){const Re={...ie,$sortedItems:Je,$sortEvent:Ve};_e.forEach(Ue=>{try{const st=u.createHandler(Ue,Re,ye),At=new Event("sortend");st(At)}catch(st){dt.error(`onSortEnd action failed: ${Ue.handler}`,st)}})}},Le=Je=>{const Ve=x.actions?.filter(_e=>_e.event==="onSortStart"||_e.type==="onSortStart");if(Ve&&Ve.length>0&&u){const _e={...ie,$activeId:Je.activeId};Ve.forEach(Re=>{try{const Ue=u.createHandler(Re,_e,ye),st=new Event("sortstart");Ue(st)}catch(Ue){dt.error(`onSortStart action failed: ${Re.handler}`,Ue)}})}},ke=ce.map((Je,Ve)=>{const _e=String(Je[Z]),Re={...ie,[pe]:Je,[Te]:Ve},Ue=L?`${L}.sortable.${Ve}`:`sortable.${Ve}`,st=`${jt(x.id,"sortable-item")}-v${Ba}-${_e}`;return ut.jsx(p_,{id:_e,handle:xe,...je?{as:je}:{},children:(je&&oe.children?oe.children:[oe]).map((At,Wt)=>ut.jsx(za,{componentDef:At,dataContext:Re,translationContext:a,registry:r,bindingEngine:i,translationEngine:l,actionDispatcher:u,parentComponentContext:ye,parentFormContextProp:void 0,isInsideIteration:!0,isEditMode:v,onComponentSelect:S,onComponentHover:E,componentPath:Ue,onDragStart:A,onDragEnd:T,layoutKey:M},`${st}-child-${Wt}`))},st)});return ut.jsx(f_,{items:ce,itemKey:Z,strategy:be,onSortEnd:ze,onSortStart:Le,sortVersion:Ba,children:ke})}catch(ce){return dt.error(`sortable 렌더링 실패 (컴포넌트: ${x.id}):`,ce),null}},[x.sortable,x.itemTemplate,x.actions,x.id,e,ie,a,r,i,l,u,ye,jt,v,S,E,L,A,T,Ba,M]),tr=N.useRef(!1);N.useEffect(()=>{if(tr.current)return;tr.current=!0;const q=Za();if(q?.isEnabled()&&q.trackMount(o.id,{name:o.name,type:o.type||"component",props:gt}),o.lifecycle?.onMount&&o.lifecycle.onMount.length>0)for(const ce of o.lifecycle.onMount)try{const Z=u.createHandler(ce,ie,ye),be=new Event("mount");Z(be),dt.log(`Lifecycle onMount executed: ${ce.handler} (Component: ${o.id})`)}catch(Z){dt.error(`Lifecycle onMount failed: ${ce.handler} (Component: ${o.id})`,Z)}const oe=[];if(o.onComponentEvent&&o.onComponentEvent.length>0){const ce=window.G7Core;if(ce?.componentEvent?.on)for(const Z of o.onComponentEvent){if(!Z.event){dt.warn(`onComponentEvent: event name is required (Component: ${o.id})`);continue}const be=async Te=>{try{const xe={...ie,_eventData:Te},ze=await u.createHandler({handler:Z.handler,params:Z.params,onSuccess:Z.onSuccess,onError:Z.onError},xe,ye)(new CustomEvent(Z.event,{detail:Te}));return dt.log(`onComponentEvent "${Z.event}" handled: ${Z.handler} (Component: ${o.id})`,{eventData:Te,result:ze}),ze}catch(xe){throw dt.error(`onComponentEvent "${Z.event}" failed: ${Z.handler} (Component: ${o.id})`,xe),xe}},pe=ce.componentEvent.on(Z.event,be);oe.push(pe),dt.log(`onComponentEvent: Subscribed to "${Z.event}" (Component: ${o.id})`)}else dt.warn(`onComponentEvent: G7Core.componentEvent is not available (Component: ${o.id})`)}return()=>{const ce=Za();ce?.isEnabled()&&ce.trackUnmount(o.id);for(const Z of oe)try{Z()}catch(be){dt.error(`onComponentEvent: Failed to unsubscribe (Component: ${o.id})`,be)}if(oe.length>0&&dt.log(`onComponentEvent: Unsubscribed ${oe.length} event(s) (Component: ${o.id})`),o.lifecycle?.onUnmount&&o.lifecycle.onUnmount.length>0)for(const Z of o.lifecycle.onUnmount)try{const be=u.createHandler(Z,ie,ye),pe=new Event("unmount");be(pe),dt.log(`Lifecycle onUnmount executed: ${Z.handler} (Component: ${o.id})`)}catch(be){dt.error(`Lifecycle onUnmount failed: ${Z.handler} (Component: ${o.id})`,be)}}},[]);const bn=N.useMemo(()=>{const q=gt.dataKey,oe=gt.trackChanges,ce=gt.debounce;return q?{dataKey:q,trackChanges:oe??!1,debounce:ce,setState:de,state:re}:null},[gt.dataKey,gt.trackChanges,gt.debounce,de,re]);N.useEffect(()=>{const q=x.dataKey||gt.dataKey;if(!q)return;const oe=Za();if(!oe?.isEnabled())return;const ce=[],Z=pe=>{if(pe)for(const Te of pe){const xe=Te?.props?.name;if(xe&&ce.push({name:xe,type:Te.name||Te.type||"unknown"}),Te?.children&&Z(Te.children),Te?.slots)for(const je of Object.values(Te.slots))Array.isArray(je)&&Z(je)}};if(o.children&&Z(o.children),o.slots)for(const pe of Object.values(o.slots))Array.isArray(pe)&&Z(pe);const be=o.id||`form-${q}`;return oe.trackForm(be,q,ce),()=>{oe.untrackForm(be)}},[x,gt.dataKey,o.id,o.children]);const tn=N.useRef(null),et=N.useRef(void 0);N.useEffect(()=>{const q=gt?.name;if(!q||!ve.dataKey||!ve.setState||gt?.autoBinding===!1)return;const oe=`${ve.dataKey}.${q}`,ce=window.__g7AutoBindingPaths??new Map;return window.__g7AutoBindingPaths=ce,ce.set(oe,(ce.get(oe)??0)+1),()=>{const Z=ce.get(oe)??0;Z<=1?ce.delete(oe):ce.set(oe,Z-1)}},[gt?.name,gt?.autoBinding,ve.dataKey,ve.setState]);const ta=N.useMemo(()=>{const{dataKey:q,trackChanges:oe,debounce:ce,...Z}=gt,be=Z.name;if(!be||!ve.dataKey||!ve.setState)return Z;if(Z.autoBinding===!1){const{autoBinding:_e,...Re}=Z;return Re}const pe=`${ve.dataKey}.${be}`,Te=hC(ve.state,pe),xe=Te!==void 0?Te:Z.value,ze=r.getMetadata(o.name)?.bindingType,Le=typeof xe=="boolean"&&(ze==="checked"||ze==="checkable"&&["checkbox","radio"].includes(Z?.type)),ke=ve.debounce,Je=_e=>{const Re=ve.state||{},Ue=gC(Re,pe,_e);if(ve.trackChanges&&(Ue.hasChanges=!0),window.__g7PendingLocalState=Ue,ve.setState(Ue),ve._isGlobal)return;const st=window.G7Core,At={[pe]:_e};ve.trackChanges&&(At.hasChanges=!0),st?.state?.setLocal?.(At,{render:!1})},Ve=(_e,Re)=>{ke&&ke>0?(tn.current&&clearTimeout(tn.current),tn.current=setTimeout(()=>{Je(_e),tn.current=null,Re&&(Re(),window.__g7PendingLocalState=null)},ke)):(Je(_e),Re&&(Re(),window.__g7PendingLocalState=null))};if(Le)return{...Z,checked:xe,onChange:Re=>{const Ue=Re.target.checked;Je(Ue),Z.onChange&&Z.onChange(Re)}};{const _e=Ue=>{const st=Ue?.target?.value!==void 0?Ue.target.value:Ue;ke&&ke>0&&(et.current=st);const At=Ue?.target?{target:{value:st,name:Ue.target.name,type:Ue.target.type,checked:Ue.target.checked},currentTarget:Ue.currentTarget?{value:st,name:Ue.currentTarget.name}:void 0}:Ue;Ve(st,()=>{Z.onChange&&Z.onChange(At)})};let Re;return et.current!==void 0?tn.current!==null?Re=et.current:String(xe??"")===String(et.current)?(et.current=void 0,Re=xe??""):Re=et.current:Re=xe??"",{...Z,value:Re,onChange:_e}}},[gt,ve,ve.state,ve.debounce]),En=N.useMemo(()=>{const q=Za();if(q?.isEnabled()&&q.trackRender(o.name),!Ne)return null;if(x.sortable&&x.itemTemplate)return Dr;if(x.iteration)return ea;if(x.type==="extension_point"){const{layout:ze,columns:Le,gap:ke,className:Je,...Ve}=x.props||{};let _e=Je||"";return ze==="grid"&&Le?_e=`${_e} grid grid-cols-${Le} gap-${ke||4}`.trim():ze==="flex"&&(_e=`${_e} flex flex-col gap-${ke||4}`.trim()),ut.jsx("div",{id:Ua,className:_e||void 0,...Ve,children:On})}const oe=r.getComponent(x.name);if(!oe)return dt.error(`컴포넌트를 찾을 수 없습니다: ${x.name} (ID: ${x.id})`,"componentDef:",JSON.stringify(x,null,2)),null;const ce=e._global?._remountKeys,Z={...ta,id:ta.id??Ua,...ce&&Object.keys(ce).length>0&&{__remountTrigger:JSON.stringify(ce)}};if(he?.listeners&&he.handle&&ta["data-drag-handle"]!==void 0&&(Object.assign(Z,he.listeners),Z.style={...Z.style,cursor:he.isDragging?"grabbing":"grab"}),v){const ze=x.id||`auto_${x.name||x.type||"unknown"}_${Math.random().toString(36).substring(2,8)}`,Le=Ve=>{typeof Ve.preventDefault=="function"&&Ve.preventDefault(),S&&(Ve.stopPropagation(),S(ze,Ve))},ke={"data-editor-id":ze,"data-editor-name":x.name,"data-editor-type":x.type,onClick:Le};L&&(ke["data-editor-path"]=L);const Je=x.__editorSlotName??x.slot;typeof Je=="string"&&(ke["data-editor-slot"]=Je),E&&(ke.onMouseMove=Ve=>{Ve.stopPropagation(),E(ze,Ve)},ke.onMouseLeave=Ve=>{E(null,Ve)}),Object.assign(Z,ke),Z.editorAttrs=ke}const be=x.type==="basic",pe=x.type==="layout",xe=be||pe?(()=>{const ze={};for(const[Le,ke]of Object.entries(Z))Le.startsWith("__")||Le==="editorAttrs"&&be||(ze[Le]=ke);return ze})():Z;let je=ut.jsx(oe,{...xe,children:On});if(kn&&(je=ut.jsx(dC,{value:kn,children:je})),x.isolatedState){const ze=typeof x.isolatedState=="object"?x.isolatedState:{},Le=e?._isolatedInit,ke=Le?{...ze,...Le}:ze;je=ut.jsx(SC,{initialState:ke,scopeId:x.isolatedScopeId,children:je})}return je},[Ne,x.iteration,x.sortable,x.itemTemplate,x.type,x.name,x.id,x.props,x.isolatedState,x.isolatedScopeId,r,ta,On,ea,Dr,he,bn,v,S,E,A,T,L,e._global?._remountKeys]),nr=N.useMemo(()=>{const q=x.blur_until_loaded;if(!q||e?._global?.__isPreview)return!1;if(typeof q=="string"){const Z=_t(q);if(Z!==null)try{const be={componentId:x.id,componentName:x.name,propName:"blur_until_loaded"};return!!i.evaluateExpression(Z,ie,be)}catch(be){return dt.warn(`blur_until_loaded 표현식 평가 실패 (컴포넌트: ${x.id}):`,be),!1}return!1}if(typeof q=="object"&&q!==null){if(!q.enabled)return!1;if(q.data_sources)return(Array.isArray(q.data_sources)?q.data_sources:[q.data_sources]).some(Te=>e[Te]===void 0);if(Q)return!0;const Z=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(pe=>!Z.includes(pe)&&!pe.startsWith("_")).some(pe=>e[pe]===void 0)}if(Q)return!0;const oe=["route","query","_global","_local","_dataSourceErrors"];return Object.keys(e).filter(Z=>!oe.includes(Z)&&!Z.startsWith("_")).some(Z=>e[Z]===void 0)},[x.blur_until_loaded,x.id,Q,e,ie,i,_t]);if(nr){const q=x.blur_until_loaded;if(typeof q=="object"&&q?.data_sources){const ce=(Array.isArray(q.data_sources)?q.data_sources:[q.data_sources]).reduce((be,pe)=>(be[pe]=e[pe]===void 0?"UNDEFINED":"LOADED",be),{}),Z=Object.keys(e).filter(be=>!be.startsWith("_"));dt.log(`[BLUR APPLIED] Component: ${x.id}, data_sources status:`,ce,"Available keys:",Z)}else dt.log(`[BLUR APPLIED] Component: ${x.id}, blur_until_loaded:`,q)}const Ea=N.useMemo(()=>{if(!x.blur_until_loaded)return{wrapperClasses:"",innerClasses:""};const q=ft?.className||"";if(!q)return{wrapperClasses:"",innerClasses:""};const oe=[/^(sm:|md:|lg:|xl:|2xl:)?(col-span-|row-span-|col-start-|col-end-|row-start-|row-end-)/,/^(sm:|md:|lg:|xl:|2xl:)?(self-|place-self-|order-)/,/^(sm:|md:|lg:|xl:|2xl:)?(flex-shrink|flex-grow|flex-\d|basis-)/],ce=q.split(/\s+/).filter(Boolean),Z=[],be=[];for(const pe of ce)oe.some(xe=>xe.test(pe))?Z.push(pe):be.push(pe);return{wrapperClasses:Z.join(" "),innerClasses:be.join(" ")}},[x.blur_until_loaded,ft?.className]),nn=window.__slotContextValue;return Se&&nn?.isEnabled?null:ut.jsx(b_,{componentId:o.id,componentName:o.name,children:x.blur_until_loaded?ut.jsx("div",{className:[Ea.wrapperClasses,nr?"opacity-50 blur-sm transition-all duration-300 pointer-events-none":void 0].filter(Boolean).join(" ")||void 0,children:En}):En})});za.displayName="DynamicRenderer",typeof window<"u"&&(window.__DynamicRenderer=za);function v_(o){const e={};try{const r=new URL(o,typeof window<"u"?window.location.origin:"http://localhost").searchParams,i=new Set;for(const l of r.keys()){if(i.has(l))continue;i.add(l);const u=r.getAll(l);u.length>1||l.endsWith("[]")?e[l]=u:e[l]=u[0]}}catch{}return e}function S_(o){try{return new URL(o,typeof window<"u"?window.location.origin:"http://localhost").pathname}catch{return o.split("?")[0]}}const yi=class yi{constructor(){$(this,"config",{enabled:!1,maxHistorySize:100,logLevel:"info",serverEndpoint:"/_boost/g7-debug/dump-state",autoCapture:!0});$(this,"stateHistory",[]);$(this,"snapshotIdCounter",0);$(this,"stateWatchers",new Map);$(this,"currentLocalState",{});$(this,"currentComputedState",{});$(this,"currentParentContext");$(this,"actionHistory",[]);$(this,"actionWatchers",new Set);$(this,"cacheStats",{hits:0,misses:0,entries:0});$(this,"cacheDecisions",[]);$(this,"cacheDecisionIdCounter",0);$(this,"maxCacheDecisions",200);$(this,"currentRenderCycleId",null);$(this,"isInActionExecution",!1);$(this,"isInIteration",!1);$(this,"mountedComponents",new Map);$(this,"eventListeners",new Map);$(this,"mountWatchers",new Set);$(this,"unmountWatchers",new Set);$(this,"renderCounts",new Map);$(this,"bindingEvalCount",0);$(this,"profilingData",[]);$(this,"isProfiling",!1);$(this,"profilingStartTime",0);$(this,"activeRequests",new Map);$(this,"requestHistory",[]);$(this,"pendingDataSources",new Set);$(this,"requestWatchers",new Set);$(this,"wsConnections",new Map);$(this,"wsMessageHistory",[]);$(this,"wsMessageWatchers",new Set);$(this,"ifConditions",new Map);$(this,"iterations",new Map);$(this,"conditionWatchers",new Set);$(this,"forms",new Map);$(this,"formWatchers",new Set);$(this,"expressionHistory",[]);$(this,"expressionWatchers",new Set);$(this,"expressionIdCounter",0);$(this,"dataSources",new Map);$(this,"dataPathTransforms",[]);$(this,"maxDataPathTransforms",100);$(this,"nestedContexts",[]);$(this,"nestedContextIdCounter",0);$(this,"maxNestedContexts",100);$(this,"formBindingIssues",[]);$(this,"formBindingValidations",[]);$(this,"formBindingIssueIdCounter",0);$(this,"maxFormBindingIssues",100);$(this,"computedProperties",new Map);$(this,"computedRecalcLogs",[]);$(this,"computedRecalcIdCounter",0);$(this,"maxComputedRecalcLogs",100);$(this,"handlers",new Map);$(this,"componentEventSubscriptions",new Map);$(this,"componentEventEmitHistory",[]);$(this,"componentEventIdCounter",0);$(this,"maxComponentEventHistory",100);$(this,"stateRenderingLogs",[]);$(this,"currentStateChangeContext",null);$(this,"stateRenderingIdCounter",0);$(this,"componentRenderCounts",new Map);$(this,"stateToComponentMap",new Map);$(this,"maxStateRenderingLogs",100);$(this,"componentStateSources",new Map);$(this,"contextFlowNodes",new Map);$(this,"dynamicStates",new Map);$(this,"styleIssues",[]);$(this,"componentStyles",new Map);$(this,"authEvents",[]);$(this,"authHeaderHistory",[]);$(this,"authEventIdCounter",0);$(this,"maxAuthEventHistory",50);$(this,"logHistory",[]);$(this,"logIdCounter",0);$(this,"maxLogHistory",500);$(this,"currentLayout",null);$(this,"layoutHistory",[]);$(this,"layoutIdCounter",0);$(this,"layoutStats",{totalLoads:0,cacheHits:0,apiLoads:0});$(this,"maxLayoutHistory",50);$(this,"executionDetails",new Map);$(this,"stateChangeHistory",[]);$(this,"dataSourceChangeHistory",[]);$(this,"changeAlerts",[]);$(this,"changeExpectations",new Map);$(this,"executionIdCounter",0);$(this,"stateChangeIdCounter",0);$(this,"alertIdCounter",0);$(this,"maxExecutionDetails",100);$(this,"maxStateChangeHistory",200);$(this,"maxChangeAlerts",100);$(this,"sequenceExecutions",[]);$(this,"sequenceExecutionStack",[]);$(this,"sequenceIdCounter",0);$(this,"maxSequenceExecutions",50);$(this,"staleClosureWarnings",[]);$(this,"staleClosureIdCounter",0);$(this,"maxStaleClosureWarnings",100);$(this,"stateCaptureRegistry",new Map);$(this,"modalStates",new Map);$(this,"modalStateIssues",[]);$(this,"modalStateRelations",[]);$(this,"modalStateChangeLogs",[]);$(this,"modalStateIssueIdCounter",0);$(this,"modalStateChangeLogIdCounter",0);$(this,"maxModalStateIssues",100);$(this,"maxModalStateChangeLogs",200);$(this,"namedActionDefinitions",{});$(this,"namedActionRefLogs",[]);$(this,"namedActionRefIdCounter",0);$(this,"maxNamedActionRefLogs",200);$(this,"debounceActionHistory",[]);$(this,"dataSourceUpdateHistory",[]);$(this,"debounceActionIdCounter",0);$(this,"dataSourceUpdateIdCounter",0)}static getInstance(){return yi.instance||(yi.instance=new yi),yi.instance}initialize(){if(this.config.enabled=this.checkDebugMode(),!this.config.enabled){console.log("[G7DevTools] 비활성화됨 (환경설정 > 고급 설정 > 디버그 모드를 켜세요)");return}console.log("[G7DevTools] 활성화됨"),this.setupGlobalErrorHandler()}checkDebugMode(){try{if(window.G7Config?.debug===!0)return!0;const r=window.G7Core?.state?.get?.();return r?.settings?.advanced?.debug_mode===!0||r?._global?.settings?.advanced?.debug_mode===!0}catch{return!1}}isEnabled(){return this.config.enabled}setupGlobalErrorHandler(){window.addEventListener("error",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"globalError",startTime:Date.now(),status:"error",error:{name:"Error",message:e.message,stack:e.error?.stack}})}),window.addEventListener("unhandledrejection",e=>{this.config.enabled&&this.logAction({id:this.generateId(),type:"unhandledRejection",startTime:Date.now(),status:"error",error:{name:"UnhandledRejection",message:String(e.reason)}})})}captureStateSnapshot(e){if(!this.config.enabled)return;const a={id:++this.snapshotIdCounter,timestamp:Date.now(),source:e.source,prev:this.sanitizeObject(e.prev),next:this.sanitizeObject(e.next),diff:e.diff};this.stateHistory.push(a),this.stateHistory.length>this.config.maxHistorySize&&this.stateHistory.shift(),this.notifyStateWatchers(a)}getState(){try{const e=window.G7Core,a=e?.state?.get?.()||{},r={};if(e?._isolatedStates&&typeof e._isolatedStates=="object")for(const[i,l]of Object.entries(e._isolatedStates))r[i]=this.sanitizeObject(l);return{_global:a._global||a,_local:this.currentLocalState,_computed:this.currentComputedState,_isolated:Object.keys(r).length>0?r:void 0,$parent:this.currentParentContext}}catch{return{_global:{},_local:this.currentLocalState,_computed:this.currentComputedState}}}updateLocalState(e){this.config.enabled&&(this.currentLocalState=this.sanitizeObject(e))}updateComputedState(e){this.config.enabled&&(this.currentComputedState=this.sanitizeObject(e))}updateParentContext(e){this.config.enabled&&(this.currentParentContext=e?this.sanitizeObject(e):void 0)}getStateHistory(){return[...this.stateHistory]}getDualStorageMismatch(){const e=this.currentLocalState||{};let a={};try{a=(window.G7Core?.state?.get?.()||{})._local||{}}catch{a={}}const r=this.findMismatchedLeafPaths(e,a);return{hasMismatch:r.length>0,mismatchedPaths:r,storageA:e,storageB:a}}findMismatchedLeafPaths(e,a,r=""){const i=[],l=new Set([...Object.keys(e||{}),...Object.keys(a||{})]);for(const u of l){const d=r?`${r}.${u}`:u,f=e?.[u],g=a?.[u],m=f&&typeof f=="object"&&!Array.isArray(f),b=g&&typeof g=="object"&&!Array.isArray(g);if(m&&b)i.push(...this.findMismatchedLeafPaths(f,g,d));else{const v=f===void 0?"__undefined__":JSON.stringify(f),S=g===void 0?"__undefined__":JSON.stringify(g);v!==S&&i.push(d)}}return i}watchState(e,a){return this.stateWatchers.has(e)||this.stateWatchers.set(e,new Set),this.stateWatchers.get(e).add(a),()=>{this.stateWatchers.get(e)?.delete(a)}}notifyStateWatchers(e){if(this.stateWatchers.get("*")?.forEach(a=>{try{a(e.next,e.prev,"*")}catch(r){console.error("[G7DevTools] State watcher error:",r)}}),e.diff)for(const a of e.diff.changed)this.stateWatchers.get(a.path)?.forEach(r=>{try{r(a.newValue,a.oldValue,a.path)}catch(i){console.error("[G7DevTools] State watcher error:",i)}})}diffSnapshots(e,a){const r=this.stateHistory.find(l=>l.id===e),i=this.stateHistory.find(l=>l.id===a);return!r||!i?null:this.calculateDiff(r.next,i.next)}calculateDiff(e,a,r=""){const i={added:[],removed:[],changed:[]},l=new Set(Object.keys(e)),u=new Set(Object.keys(a));for(const d of u){const f=r?`${r}.${d}`:d;l.has(d)?JSON.stringify(e[d])!==JSON.stringify(a[d])&&i.changed.push({path:f,oldValue:e[d],newValue:a[d]}):i.added.push(f)}for(const d of l){const f=r?`${r}.${d}`:d;u.has(d)||i.removed.push(f)}return i}logAction(e){if(!this.config.enabled)return;const a=this.actionHistory.findIndex(r=>r.id===e.id);a>=0?this.actionHistory[a]={...this.actionHistory[a],...e}:(this.actionHistory.push(e),this.actionHistory.length>this.config.maxHistorySize&&this.actionHistory.shift()),this.isProfiling&&this.profilingData.push({type:"action",action:e.type,timestamp:performance.now(),duration:e.duration}),this.notifyActionWatchers(e)}getActionHistory(){return[...this.actionHistory]}watchActions(e){return this.actionWatchers.add(e),()=>{this.actionWatchers.delete(e)}}notifyActionWatchers(e){this.actionWatchers.forEach(a=>{try{a(e)}catch(r){console.error("[G7DevTools] Action watcher error:",r)}})}getActionMetrics(){const e=this.actionHistory.filter(l=>l.status==="success"),a=this.actionHistory.filter(l=>l.status==="error"),r={};for(const l of this.actionHistory)r[l.type]=(r[l.type]||0)+1;const i=e.reduce((l,u)=>l+(u.duration||0),0);return{totalActions:this.actionHistory.length,successCount:e.length,errorCount:a.length,averageDuration:e.length>0?i/e.length:0,actionsByType:r}}recordCacheHit(){this.config.enabled&&this.cacheStats.hits++}recordCacheMiss(){this.config.enabled&&this.cacheStats.misses++}updateCacheEntries(e){this.config.enabled&&(this.cacheStats.entries=e)}getCacheStats(){const e=this.cacheStats.hits+this.cacheStats.misses;return{...this.cacheStats,hitRate:e>0?this.cacheStats.hits/e:0}}resetCacheStats(){this.cacheStats={hits:0,misses:0,entries:0}}trackCacheDecision(e){if(!this.config.enabled)return;const a={id:`cache-${++this.cacheDecisionIdCounter}`,timestamp:Date.now(),expression:e.expression,decision:e.decision,reason:e.reason,context:{isInIteration:this.isInIteration,isInAction:this.isInActionExecution,renderCycleId:this.currentRenderCycleId||void 0,componentId:e.componentId,skipCacheOption:e.skipCacheOption},cachedValue:e.cachedValue!==void 0?this.safeClone(e.cachedValue):void 0,freshValue:e.freshValue!==void 0?this.safeClone(e.freshValue):void 0,valueMatch:e.cachedValue!==void 0&&e.freshValue!==void 0?JSON.stringify(e.cachedValue)===JSON.stringify(e.freshValue):void 0,duration:e.duration};this.cacheDecisions.push(a),this.cacheDecisions.length>this.maxCacheDecisions&&this.cacheDecisions.shift(),e.decision==="cache_hit"?this.cacheStats.hits++:(e.decision==="cache_miss"||e.decision==="skip_cache")&&this.cacheStats.misses++}startRenderCycle(e){return this.currentRenderCycleId=`render-${Date.now()}-${e||"root"}`,this.currentRenderCycleId}endRenderCycle(){this.currentRenderCycleId=null}startActionContext(){this.isInActionExecution=!0}endActionContext(){this.isInActionExecution=!1}startIterationContext(){this.isInIteration=!0}endIterationContext(){this.isInIteration=!1}getCacheDecisionTrackingInfo(){const e=this.getCacheDecisionStats();return{decisions:[...this.cacheDecisions],stats:e,timestamp:Date.now()}}getCacheDecisionStats(){const e=this.cacheDecisions;let a=0,r=0,i=0,l=0;const u={},d={};for(const g of e)g.decision==="cache_hit"?a++:g.decision==="cache_miss"?r++:g.decision==="skip_cache"?i++:g.decision==="invalidate"&&l++,u[g.reason]=(u[g.reason]||0)+1,g.context.componentId&&(d[g.context.componentId]=(d[g.context.componentId]||0)+1);const f=a+r+i;return{totalDecisions:e.length,cacheHits:a,cacheMisses:r,skipCacheCount:i,invalidateCount:l,byReason:u,byComponent:d,avgHitRate:f>0?a/f:0}}getRecentCacheDecisions(e=20,a){let r=[...this.cacheDecisions];return a&&(r=r.filter(i=>i.decision===a)),r.slice(-e)}clearCacheDecisionData(){this.cacheDecisions=[],this.cacheDecisionIdCounter=0}summarizePropsForLifecycle(e){if(!e||typeof e!="object")return e;const a={},r=["children","ref","key"];for(const[i,l]of Object.entries(e))if(!r.includes(i))if(l==null)a[i]=l;else if(Array.isArray(l))a[i]=`[Array(${l.length})]`;else if(typeof l=="object"){const u=Object.keys(l);u.length<=3?a[i]=`{${u.join(", ")}}`:a[i]=`{${u.slice(0,3).join(", ")}, ... +${u.length-3}}`}else typeof l=="string"&&l.length>50?a[i]=l.substring(0,50)+"...":typeof l=="function"?a[i]="[Function]":a[i]=l;return a}trackMount(e,a){if(!this.config.enabled)return;const r=this.summarizePropsForLifecycle(a.props),i={...a,props:r,id:e,mountTime:Date.now()};this.mountedComponents.set(e,i),this.mountWatchers.forEach(l=>{try{l({componentId:e,componentName:a.name,timestamp:i.mountTime})}catch(u){console.error("[G7DevTools] Mount watcher error:",u)}})}trackUnmount(e){if(!this.config.enabled)return;const a=this.mountedComponents.get(e),r=this.eventListeners.get(e)||[];r.length>0&&console.warn(`[G7DevTools] 컴포넌트 ${e} 언마운트 시 ${r.length}개 리스너 미정리`),a&&this.unmountWatchers.forEach(i=>{try{i({componentId:e,componentName:a.name,timestamp:Date.now(),orphanedListeners:r.length})}catch(l){console.error("[G7DevTools] Unmount watcher error:",l)}}),this.mountedComponents.delete(e)}trackListener(e,a,r){if(!this.config.enabled)return;const i=this.eventListeners.get(e)||[];i.push({type:a,target:r,addedAt:Date.now()}),this.eventListeners.set(e,i)}removeListener(e,a){if(!this.config.enabled)return;const r=this.eventListeners.get(e)||[],i=r.findIndex(l=>l.type===a);i>=0&&r.splice(i,1)}getMountedComponents(){return Array.from(this.mountedComponents.values())}getOrphanedListeners(){const e=[];for(const[a,r]of this.eventListeners)this.mountedComponents.has(a)||e.push(...r.map(i=>({...i,componentId:a})));return e}watchMount(e){return this.mountWatchers.add(e),()=>{this.mountWatchers.delete(e)}}watchUnmount(e){return this.unmountWatchers.add(e),()=>{this.unmountWatchers.delete(e)}}trackRender(e){if(!this.config.enabled)return;const a=this.renderCounts.get(e)||0;this.renderCounts.set(e,a+1),this.isProfiling&&this.profilingData.push({type:"render",component:e,timestamp:performance.now()})}trackBindingEval(e){this.config.enabled&&(this.bindingEvalCount++,this.isProfiling&&e&&this.profilingData.push({type:"binding",expression:e,timestamp:performance.now()}))}getRenderCount(){return new Map(this.renderCounts)}getBindingEvalCount(){return this.bindingEvalCount}getMemoryWarnings(){const e=[];try{const i=this.getState(),l=JSON.stringify(i).length;l>1e6&&e.push({type:"large-state",message:`상태 크기가 ${(l/1024/1024).toFixed(2)}MB입니다`,suggestion:"불필요한 데이터 정리 필요",severity:l>5e6?"error":"warning"})}catch{}this.stateHistory.length>this.config.maxHistorySize*.8&&e.push({type:"large-history",message:`상태 이력이 ${this.stateHistory.length}개입니다`,suggestion:"maxHistory 설정 조정 필요",severity:"warning"});const a=this.getOrphanedListeners().length;a>0&&e.push({type:"orphaned-listeners",message:`${a}개의 정리되지 않은 이벤트 리스너`,suggestion:"useEffect cleanup 또는 removeEventListener 확인",severity:a>10?"error":"warning"});const r=Array.from(this.renderCounts.entries()).filter(([,i])=>i>50);return r.length>0&&e.push({type:"excessive-renders",message:`${r.length}개 컴포넌트가 50회 이상 렌더링`,suggestion:"React.memo, useMemo, useCallback 사용 고려",severity:"warning"}),e}startProfiling(){this.config.enabled&&(this.isProfiling=!0,this.profilingData=[],this.profilingStartTime=performance.now(),console.log("[G7DevTools] 프로파일링 시작"))}stopProfiling(){this.isProfiling=!1;const e=performance.now()-this.profilingStartTime,a=this.analyzeProfile(e);return console.log("[G7DevTools] 프로파일링 완료",a),a}analyzeProfile(e){const a=this.profilingData.filter(g=>g.type==="render"),r=this.profilingData.filter(g=>g.type==="binding"),i=this.profilingData.filter(g=>g.type==="action"),l={};for(const g of a)g.component&&(l[g.component]||(l[g.component]={count:0,durations:[]}),l[g.component].count++,g.duration&&l[g.component].durations.push(g.duration));const u=Object.entries(l).map(([g,m])=>({name:g,renderCount:m.count,avgDuration:m.durations.length>0?m.durations.reduce((b,v)=>b+v,0)/m.durations.length:0})).sort((g,m)=>m.renderCount-g.renderCount).slice(0,10),d={};for(const g of r)g.expression&&(d[g.expression]=(d[g.expression]||0)+1);const f=Object.entries(d).sort(([,g],[,m])=>m-g).slice(0,10).map(([g])=>g);return{duration:e,entries:this.profilingData,summary:{totalRenders:a.length,totalBindings:r.length,totalActions:i.length,slowestComponents:u,hotPaths:f}}}resetPerformanceStats(){this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[]}trackRequest(e,a,r){if(!this.config.enabled)return"";const i=this.generateId(),l=S_(e),u=v_(e);return this.activeRequests.set(i,{id:i,url:l,fullUrl:e,queryParams:u,method:a,startTime:Date.now(),status:"pending",requestBody:r?.requestBody?this.sanitizeObject(r.requestBody):void 0,dataSourceId:r?.dataSourceId}),this.isProfiling&&this.profilingData.push({type:"network",timestamp:performance.now()}),i}completeRequest(e,a,r){if(!this.config.enabled)return;const i=this.activeRequests.get(e);if(!i)return;const l={...i,status:a>=200&&a<300?"success":"error",statusCode:a,duration:Date.now()-i.startTime,endTime:Date.now(),response:this.sanitizeObject(r)};this.requestHistory.push(l),this.activeRequests.delete(e),this.requestHistory.length>this.config.maxHistorySize&&this.requestHistory.shift(),this.notifyRequestWatchers(l)}failRequest(e,a){if(!this.config.enabled)return;const r=this.activeRequests.get(e);if(!r)return;const i={...r,status:"error",error:a,duration:Date.now()-r.startTime,endTime:Date.now()};this.requestHistory.push(i),this.activeRequests.delete(e),this.notifyRequestWatchers(i)}trackDataSource(e){this.config.enabled&&this.pendingDataSources.add(e)}completeDataSource(e){this.config.enabled&&this.pendingDataSources.delete(e)}getActiveRequests(){return Array.from(this.activeRequests.values())}getRequestHistory(){return[...this.requestHistory]}getPendingDataSources(){return Array.from(this.pendingDataSources)}watchRequest(e){return this.requestWatchers.add(e),()=>{this.requestWatchers.delete(e)}}notifyRequestWatchers(e){this.requestWatchers.forEach(a=>{try{a(e)}catch(r){console.error("[G7DevTools] Request watcher error:",r)}})}trackWebSocketConnection(e,a,r){this.config.enabled&&this.wsConnections.set(e,{id:e,url:a,state:r,connectedAt:r==="open"?Date.now():void 0})}trackWebSocketMessage(e,a,r,i){if(!this.config.enabled)return;const l={id:this.generateId(),connectionId:e,direction:a,type:r,payload:this.sanitizeObject(i),timestamp:Date.now(),sequence:this.wsMessageHistory.length};this.wsMessageHistory.push(l),this.wsMessageHistory.length>this.config.maxHistorySize&&this.wsMessageHistory.shift(),this.notifyWebSocketWatchers(l)}getWebSocketConnections(){return Array.from(this.wsConnections.values())}getWebSocketMessageHistory(){return[...this.wsMessageHistory]}getWebSocketConnectionState(){const e=Array.from(this.wsConnections.values());return e.some(a=>a.state==="open")?"connected":e.some(a=>a.state==="connecting")?"reconnecting":"disconnected"}watchWebSocketMessage(e){return this.wsMessageWatchers.add(e),()=>{this.wsMessageWatchers.delete(e)}}notifyWebSocketWatchers(e){this.wsMessageWatchers.forEach(a=>{try{a(e)}catch(r){console.error("[G7DevTools] WebSocket watcher error:",r)}})}trackIfCondition(e,a,r){if(!this.config.enabled)return;const i=this.ifConditions.get(e),l=i?.evaluatedValue;this.ifConditions.set(e,{id:e,expression:a,evaluatedValue:r,evaluationCount:(i?.evaluationCount||0)+1,lastEvaluated:Date.now()}),i&&l!==r&&this.notifyConditionWatchers({id:e,expression:a,oldValue:l,newValue:r,timestamp:Date.now()})}trackIteration(e,a){this.config.enabled&&this.iterations.set(e,{id:e,...a,lastRendered:Date.now()})}getIfConditions(){return Array.from(this.ifConditions.values())}getIterations(){return Array.from(this.iterations.values())}getScopeChain(e){const a=[],r=this.getState();return a.push({name:"_global",value:r._global,source:"global"}),a.push({name:"_local",value:r._local,source:"local"}),a.push({name:"_computed",value:r._computed,source:"computed"}),a}watchConditionChange(e){return this.conditionWatchers.add(e),()=>{this.conditionWatchers.delete(e)}}notifyConditionWatchers(e){this.conditionWatchers.forEach(a=>{try{a(e)}catch(r){console.error("[G7DevTools] Condition watcher error:",r)}})}trackForm(e,a,r){this.config.enabled&&this.forms.set(e,{id:e,dataKey:a,inputs:r,trackedAt:Date.now()})}trackFormChange(e,a,r){if(!this.config.enabled)return;const i={formId:e,inputName:a,value:r,timestamp:Date.now()};this.notifyFormWatchers(i)}untrackForm(e){this.forms.delete(e)}getForms(){return Array.from(this.forms.values())}getFormState(e){const a=this.getState();return this.getNestedValue(a,e)||{}}getBindingPath(e,a){return`${e}.${a}`}watchFormChange(e){return this.formWatchers.add(e),()=>{this.formWatchers.delete(e)}}notifyFormWatchers(e){this.formWatchers.forEach(a=>{try{a(e)}catch(r){console.error("[G7DevTools] Form watcher error:",r)}})}trackFormBindingIssue(e,a,r){if(!this.config.enabled)return;const i={id:`form-issue-${++this.formBindingIssueIdCounter}`,timestamp:Date.now(),type:e,severity:a,formId:r.formId,formDataKey:r.formDataKey,inputInfo:r.inputInfo,contextInfo:r.contextInfo,description:r.description,suggestion:r.suggestion,docLink:r.docLink};this.formBindingIssues.push(i),this.formBindingIssues.length>this.maxFormBindingIssues&&this.formBindingIssues.shift()}validateFormBinding(e,a,r,i){const l=r.map(f=>{const g=[];let m=!0;return f.name||(g.push("Input에 name 속성이 없습니다"),m=!1,this.trackFormBindingIssue("missing-input-name","error",{formId:e,formDataKey:a,inputInfo:{name:f.name,type:f.type},description:"Input에 name 속성이 없어 자동 바인딩이 동작하지 않습니다",suggestion:"Input에 name 속성을 추가하세요",docLink:"troubleshooting-components-form.md"})),a||(g.push("Form에 dataKey가 없습니다"),m=!1),{inputName:f.name||"(unnamed)",inputType:f.type,bindingPath:a&&f.name?`${a}.${f.name}`:"-",isValid:m,currentValue:f.value,issues:g}});if(a||this.trackFormBindingIssue("missing-datakey","error",{formId:e,description:"Form에 dataKey가 설정되지 않아 자동 바인딩이 동작하지 않습니다",suggestion:"Form 컴포넌트에 dataKey props를 설정하세요",docLink:"troubleshooting-components-form.md"}),i.isContextBroken){const f=i.breakReason?.includes("sortable")?"sortable-context-break":i.breakReason?.includes("modal")?"modal-context-isolation":"context-not-propagated";this.trackFormBindingIssue(f,"warning",{formId:e,formDataKey:a,contextInfo:{hasParentFormContext:i.hasParentContext,parentFormContextProp:i.parentContextProp,isInsideSortable:i.breakReason?.includes("sortable")||!1,isInsideModal:i.breakReason?.includes("modal")||!1,depth:0},description:i.breakReason||"Form 컨텍스트가 제대로 전파되지 않았습니다",suggestion:"parentFormContextProp={undefined}를 확인하거나 중첩 구조를 검토하세요",docLink:"troubleshooting-components-form.md"})}const u={formId:e,dataKey:a,timestamp:Date.now(),contextPropagation:i,inputBindings:l,isValid:l.every(f=>f.isValid)&&!!a&&!i.isContextBroken,issueCount:l.filter(f=>!f.isValid).length+(a?0:1)+(i.isContextBroken?1:0)},d=this.formBindingValidations.findIndex(f=>f.formId===e);return d>=0?this.formBindingValidations[d]=u:this.formBindingValidations.push(u),u}getFormBindingValidationTrackingInfo(){const e=this.getFormBindingValidationStats();return{issues:[...this.formBindingIssues],validations:[...this.formBindingValidations],stats:e,timestamp:Date.now()}}getFormBindingValidationStats(){const e={"missing-datakey":0,"missing-input-name":0,"context-not-propagated":0,"sortable-context-break":0,"modal-context-isolation":0,"duplicate-input-name":0,"value-type-mismatch":0,"binding-path-invalid":0},a={info:0,warning:0,error:0};for(const i of this.formBindingIssues)e[i.type]++,a[i.severity]++;const r=Object.entries(e).filter(([,i])=>i>0).sort((i,l)=>l[1]-i[1]).slice(0,5).map(([i,l])=>({type:i,count:l,description:this.getIssueTypeDescription(i)}));return{totalFormsValidated:this.formBindingValidations.length,validForms:this.formBindingValidations.filter(i=>i.isValid).length,formsWithIssues:this.formBindingValidations.filter(i=>!i.isValid).length,totalIssues:this.formBindingIssues.length,byIssueType:e,bySeverity:a,topIssues:r}}getIssueTypeDescription(e){return{"missing-datakey":"Form에 dataKey 누락","missing-input-name":"Input에 name 속성 누락","context-not-propagated":"Form 컨텍스트 전파 실패","sortable-context-break":"Sortable 컨테이너에서 컨텍스트 단절","modal-context-isolation":"모달에서 부모 Form 컨텍스트 격리","duplicate-input-name":"동일 Form 내 중복 Input name","value-type-mismatch":"값 타입과 Input 타입 불일치","binding-path-invalid":"바인딩 경로 유효하지 않음"}[e]||e}clearFormBindingIssues(){this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0}trackComputedProperty(e,a,r,i,l,u,d){if(!this.config.enabled)return;const f=`computed-${e}-${u||"global"}`,g={id:f,name:e,expression:a,componentId:u,dependencies:r,currentValue:this.safeClone(i),lastComputedAt:Date.now(),computationTime:l,error:d};this.computedProperties.set(f,g)}trackComputedRecalc(e,a,r,i,l,u,d){if(!this.config.enabled)return;const f=`computed-${e}-${d||"global"}`,g=JSON.stringify(r)!==JSON.stringify(i),m={id:`recalc-${++this.computedRecalcIdCounter}`,computedId:f,computedName:e,timestamp:Date.now(),trigger:a,triggeredBy:u,previousValue:this.safeClone(r),newValue:this.safeClone(i),valueChanged:g,computationTime:l,cascadeCount:0};this.computedRecalcLogs.push(m),this.computedRecalcLogs.length>this.maxComputedRecalcLogs&&this.computedRecalcLogs.shift();const b=this.computedProperties.get(f);b&&(b.currentValue=this.safeClone(i),b.lastComputedAt=Date.now(),b.computationTime=l)}analyzeComputedDependencyChain(e,a){const r=new Set,i=[];let l=!1,u,d=0;const f=(m,b)=>{if(d=Math.max(d,b),r.has(m))return l=!0,u=[...i,m],{name:m,type:"computed",children:[],depth:b};r.add(m),i.push(m);const v=this.computedProperties.get(`computed-${m}-${a||"global"}`),S=[];if(v)for(const E of v.dependencies)E.type==="computed"?S.push(f(E.path,b+1)):S.push({name:E.path,type:E.type,children:[],depth:b+1});return i.pop(),r.delete(m),{name:m,type:"computed",children:S,depth:b}},g=f(e,0);return{root:e,tree:g,hasCycle:l,cyclePath:u,maxDepth:d}}getComputedDependencyTrackingInfo(){const e=Array.from(this.computedProperties.values()),a=this.getComputedDependencyStats(),r=[];for(const i of e){const l=this.analyzeComputedDependencyChain(i.name,i.componentId);r.push(l)}return{properties:e,recalcLogs:[...this.computedRecalcLogs],dependencyChains:r,stats:a,timestamp:Date.now()}}getComputedDependencyStats(){const e={"state-change":0,"datasource-update":0,"dependency-change":0,manual:0,initial:0},a={},r={};let i=0,l=0;for(const g of this.computedRecalcLogs)e[g.trigger]++,g.valueChanged||l++,a[g.computedName]=(a[g.computedName]||0)+1,r[g.computedName]||(r[g.computedName]=[]),r[g.computedName].push(g.computationTime),i+=g.computationTime;const u=Object.entries(a).sort((g,m)=>m[1]-g[1]).slice(0,5).map(([g,m])=>({name:g,count:m})),d=Object.entries(r).map(([g,m])=>({name:g,avgTime:m.reduce((b,v)=>b+v,0)/m.length})).sort((g,m)=>m.avgTime-g.avgTime).slice(0,5);let f=0;for(const g of this.computedProperties.values())this.analyzeComputedDependencyChain(g.name,g.componentId).hasCycle&&f++;return{totalComputed:this.computedProperties.size,totalRecalculations:this.computedRecalcLogs.length,unnecessaryRecalculations:l,avgComputationTime:this.computedRecalcLogs.length>0?i/this.computedRecalcLogs.length:0,topRecalculated:u,slowestComputed:d,cycleDetectionCount:f,byTrigger:e}}clearComputedTracking(){this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0}trackModalOpen(e){if(!this.config.enabled)return;const a={modalId:e.modalId,modalName:e.modalName,openedAt:Date.now(),closedAt:null,scopeType:e.scopeType||"isolated",parentModalId:e.parentModalId,componentId:e.componentId,initialState:e.initialState?{...e.initialState}:{},currentState:e.initialState?{...e.initialState}:{},stateChangeCount:0,isolatedStateKeys:e.isolatedStateKeys||[],sharedStateKeys:e.sharedStateKeys||[]};this.modalStates.set(e.modalId,a),this.validateModalDefinitionExists(e.modalId,e.modalName),e.parentModalId&&(this.modalStateRelations.find(i=>i.parentModalId===e.parentModalId&&i.childModalId===e.modalId)||this.modalStateRelations.push({parentModalId:e.parentModalId,childModalId:e.modalId,sharedKeys:e.sharedStateKeys||[],isolatedKeys:e.isolatedStateKeys||[],relationType:"parent-child"}))}trackModalClose(e,a){if(!this.config.enabled)return;const r=this.modalStates.get(e);r&&(r.closedAt=Date.now(),a&&(r.currentState={...a}),this.detectStateLeakage(r))}validateModalDefinitionExists(e,a){const r=this.currentLayout;if(!r?.layoutJson)return;const i=r.layoutJson.modals;if(!i){this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:a,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 현재 레이아웃에 modals 섹션이 없습니다. 레이아웃 경로: ${r.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 섹션에 id="${e}" 정의 존재`,actualValue:"modals 섹션 없음"});return}const l=Array.isArray(i)?i:Object.values(i);l.some(d=>d?.id===e||d?.props?.id===e)||this.recordModalStateIssue({type:"missing-definition",modalId:e,modalName:a,severity:"error",description:`모달 "${e}"이(가) modalStack에 추가되었으나, 렌더링된 레이아웃의 modals 섹션에 해당 ID의 정의가 없습니다. partial 로딩 실패, 레이아웃 병합 문제, extends 상속 누락 등을 확인하세요. 레이아웃 경로: ${r.layoutPath}`,affectedStateKeys:[],expectedValue:`modals 배열에 id="${e}" 항목 존재`,actualValue:`modals 배열에 ${l.length}개 모달 정의 (${l.map(d=>d?.id||"unknown").join(", ")})`})}trackModalStateChange(e){if(!this.config.enabled)return;const a=this.modalStates.get(e.modalId);if(!a)return;a.stateChangeCount++,a.currentState[e.stateKey]=e.newValue;const r=this.checkIsolationViolation(a,e.stateKey),i={id:`modal-change-${++this.modalStateChangeLogIdCounter}`,modalId:e.modalId,modalName:a.modalName,timestamp:Date.now(),stateKey:e.stateKey,previousValue:e.previousValue,newValue:e.newValue,changeSource:e.changeSource||"user-action",violatesIsolation:r};this.modalStateChangeLogs.push(i),this.modalStateChangeLogs.length>this.maxModalStateChangeLogs&&this.modalStateChangeLogs.shift(),r&&this.recordModalStateIssue({type:"isolation-violation",modalId:e.modalId,modalName:a.modalName,severity:"warning",description:`모달 '${a.modalName}'에서 격리된 상태 키 '${e.stateKey}'가 변경됨`,affectedStateKeys:[e.stateKey]})}detectStateLeakage(e){if(e.scopeType==="isolated")for(const a of e.isolatedStateKeys){const r=e.initialState[a],i=e.currentState[a];if(r!==i&&e.parentModalId){const l=this.modalStates.get(e.parentModalId);l&&l.currentState.hasOwnProperty(a)&&this.recordModalStateIssue({type:"state-leakage",modalId:e.modalId,modalName:e.modalName,severity:"error",description:`모달 닫힘 후 상태 '${a}'가 부모 모달로 유출될 수 있음`,affectedStateKeys:[a],leakedValue:i,expectedValue:r})}}}checkIsolationViolation(e,a){return e.scopeType==="isolated",!1}recordModalStateIssue(e){const a={id:`modal-issue-${++this.modalStateIssueIdCounter}`,type:e.type,modalId:e.modalId,modalName:e.modalName,timestamp:Date.now(),severity:e.severity,description:e.description,affectedStateKeys:e.affectedStateKeys,leakedValue:e.leakedValue,expectedValue:e.expectedValue,actualValue:e.actualValue,stackInfo:e.stackInfo};this.modalStateIssues.push(a),this.modalStateIssues.length>this.maxModalStateIssues&&this.modalStateIssues.shift()}getModalStateScopeTrackingInfo(){const e=Array.from(this.modalStates.values()),a=this.getModalStateScopeStats();return{modals:e,issues:[...this.modalStateIssues],relations:[...this.modalStateRelations],changeLogs:[...this.modalStateChangeLogs],stats:a,timestamp:Date.now()}}getModalStateScopeStats(){const e=Array.from(this.modalStates.values()),a={warning:0,error:0},r={"state-leakage":0,"isolation-violation":0,"parent-mutation":0,"orphaned-state":0,"scope-mismatch":0,"cleanup-failure":0,"missing-definition":0};for(const l of this.modalStateIssues)a[l.severity]++,r[l.type]++;const i={isolated:0,shared:0,inherited:0};for(const l of e)i[l.scopeType]++;return{totalModals:e.length,openModals:e.filter(l=>l.closedAt===null).length,nestedModals:e.filter(l=>l.parentModalId!==void 0).length,totalIssues:this.modalStateIssues.length,issuesBySeverity:a,issuesByType:r,byScope:i,leakageDetectionCount:r["state-leakage"],cleanupFailureCount:r["cleanup-failure"]}}clearModalStateScopeTracking(){this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0}setNamedActionDefinitions(e){this.config.enabled&&(this.namedActionDefinitions=e||{})}trackNamedActionRef(e){if(!this.config.enabled)return;const a={...e,id:`named_action_ref_${++this.namedActionRefIdCounter}`};this.namedActionRefLogs.push(a),this.namedActionRefLogs.length>this.maxNamedActionRefLogs&&(this.namedActionRefLogs=this.namedActionRefLogs.slice(-this.maxNamedActionRefLogs))}getNamedActionTrackingInfo(){const e={};for(const r of this.namedActionRefLogs)e[r.actionRefName]=(e[r.actionRefName]||0)+1;const a=Object.keys(this.namedActionDefinitions).filter(r=>!e[r]);return{definitions:this.namedActionDefinitions,refLogs:[...this.namedActionRefLogs],stats:{totalDefinitions:Object.keys(this.namedActionDefinitions).length,totalRefs:this.namedActionRefLogs.length,refCountByName:e,unusedDefinitions:a},timestamp:Date.now()}}clearNamedActionTracking(){this.namedActionDefinitions={},this.namedActionRefLogs=[],this.namedActionRefIdCounter=0}trackExpressionEval(e){if(!this.config.enabled)return;const a={...e,id:`expr-${++this.expressionIdCounter}`,timestamp:Date.now(),warning:this.detectExpressionWarning(e)};this.expressionHistory.push(a),this.expressionHistory.length>500&&this.expressionHistory.shift(),this.isProfiling&&this.profilingData.push({type:"binding",expression:e.expression,timestamp:performance.now(),duration:e.duration}),this.notifyExpressionWatchers(a)}detectExpressionWarning(e){const{expression:a,result:r,resultType:i,method:l,duration:u}=e;if(i==="undefined"&&!a.includes("??")&&!a.includes("||")&&/^[a-zA-Z_$][a-zA-Z0-9_$.]*$/.test(a.replace(/{{|}}/g,"").trim()))return{type:"undefined-result",message:"표현식 결과가 undefined입니다",suggestion:`데이터가 로드되었는지 확인하거나 fallback 값을 추가하세요: ${a} ?? ''`};if(i==="null")return{type:"null-result",message:"표현식 결과가 null입니다",suggestion:`nullish coalescing 연산자를 사용하세요: ${a} ?? ''`};if(l==="resolveBindings"&&i==="string"){const d=String(r);if(d.includes("[object Object]")||/^\[.*\]$/.test(d))return{type:"array-to-string",message:"배열/객체가 문자열로 변환되었습니다",suggestion:"evaluateExpression()을 사용하여 원본 타입을 유지하세요"}}if(l==="resolveBindings"&&i==="string"&&String(r).includes("[object Object]"))return{type:"object-to-string",message:"객체가 [object Object]로 변환되었습니다",suggestion:"evaluateExpression()을 사용하거나 특정 속성에 접근하세요"};if(i==="undefined"&&a.includes(".")&&!a.includes("?.")){const d=a.replace(/{{|}}/g,"").trim().split(".");if(d.length>=2)return{type:"missing-optional-chain",message:"Optional chaining(?.) 누락 가능성",suggestion:`안전한 접근을 위해 ?. 사용을 고려하세요: ${d.join("?.")}`}}if((a.includes("{{item.")||a.includes("{{index}}"))&&Array.from(this.iterations.values()).some(g=>g.itemVar!=="item"))return{type:"wrong-iteration-var",message:"'item'/'index' 대신 iteration에서 정의한 변수명을 사용해야 합니다",suggestion:"iteration의 item_var/index_var에 정의된 변수명을 확인하세요"};if(u&&u>10)return{type:"slow-evaluation",message:`표현식 평가에 ${u.toFixed(2)}ms 소요됨`,suggestion:"복잡한 표현식을 단순화하거나 computed 값으로 분리하세요"}}getExpressions(){return[...this.expressionHistory]}getExpressionWarnings(){return this.expressionHistory.filter(e=>e.warning!=null)}searchExpressions(e){const a=e.toLowerCase();return this.expressionHistory.filter(r=>r.expression.toLowerCase().includes(a)||r.componentName?.toLowerCase().includes(a)||r.propName?.toLowerCase().includes(a))}getExpressionStats(){const e=this.expressionHistory,a=new Set(e.map(f=>f.expression)),r=e.filter(f=>f.warning!=null),i=e.filter(f=>f.fromCache),l=e.filter(f=>f.duration!=null).map(f=>f.duration),u={};for(const f of e)u[f.resultType]=(u[f.resultType]||0)+1;const d={};for(const f of r)f.warning&&(d[f.warning.type]=(d[f.warning.type]||0)+1);return{totalEvaluations:e.length,uniqueExpressions:a.size,warningCount:r.length,cacheHitRate:e.length>0?i.length/e.length:0,averageDuration:l.length>0?l.reduce((f,g)=>f+g,0)/l.length:0,byType:u,byWarning:d}}clearExpressions(){this.expressionHistory=[],this.expressionIdCounter=0}watchExpressions(e){return this.expressionWatchers.add(e),()=>{this.expressionWatchers.delete(e)}}notifyExpressionWatchers(e){this.expressionWatchers.forEach(a=>{try{a(e)}catch(r){console.error("[G7DevTools] Expression watcher error:",r)}})}setLogLevel(e){this.config.logLevel=e}setMaxHistory(e){this.config.maxHistorySize=Math.max(10,Math.min(1e3,e))}setServerEndpoint(e){this.config.serverEndpoint=e}getConfig(){return{...this.config}}generateId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}sanitizeObject(e,a=new WeakSet){if(e===null||typeof e!="object")return e;if(a.has(e))return"[Circular]";if(a.add(e),Array.isArray(e))return e.map(i=>this.sanitizeObject(i,a));const r={};for(const i of Object.keys(e))try{r[i]=this.sanitizeObject(e[i],a)}catch{r[i]="[Unable to serialize]"}return r}getNestedValue(e,a){if(!a)return e;const r=a.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}getLifecycleInfo(){return{mountedComponents:Array.from(this.mountedComponents.values()),orphanedListeners:this.getOrphanedListeners()}}getPerformanceInfo(){return{renderCounts:this.renderCounts,bindingEvalCount:this.bindingEvalCount,memoryWarnings:this.getMemoryWarnings()}}getNetworkInfo(){return{activeRequests:Array.from(this.activeRequests.values()),requestHistory:[...this.requestHistory],pendingDataSources:Array.from(this.pendingDataSources)}}getConditionalInfo(){return{ifConditions:Array.from(this.ifConditions.values()),iterations:Array.from(this.iterations.values())}}getFormInfo(){return Array.from(this.forms.values())}getWebSocketInfo(){return{connections:Array.from(this.wsConnections.values()),messageHistory:[...this.wsMessageHistory],connectionState:this.getWebSocketConnectionState()}}getStateAtTime(e){const a=[...this.stateHistory].reverse().find(r=>r.timestamp<=e);if(a)return{_global:a.next,_local:{}}}reset(){this.stateHistory=[],this.actionHistory=[],this.cacheStats={hits:0,misses:0,entries:0},this.cacheDecisions=[],this.cacheDecisionIdCounter=0,this.currentRenderCycleId=null,this.isInActionExecution=!1,this.isInIteration=!1,this.mountedComponents.clear(),this.eventListeners.clear(),this.renderCounts.clear(),this.bindingEvalCount=0,this.profilingData=[],this.activeRequests.clear(),this.requestHistory=[],this.pendingDataSources.clear(),this.wsConnections.clear(),this.wsMessageHistory=[],this.ifConditions.clear(),this.iterations.clear(),this.forms.clear(),this.expressionHistory=[],this.expressionIdCounter=0,this.dataSources.clear(),this.dataPathTransforms=[],this.nestedContexts=[],this.nestedContextIdCounter=0,this.formBindingIssues=[],this.formBindingValidations=[],this.formBindingIssueIdCounter=0,this.computedProperties.clear(),this.computedRecalcLogs=[],this.computedRecalcIdCounter=0,this.modalStates.clear(),this.modalStateIssues=[],this.modalStateRelations=[],this.modalStateChangeLogs=[],this.modalStateIssueIdCounter=0,this.modalStateChangeLogIdCounter=0,this.logHistory=[],this.logIdCounter=0}getExpressionInfo(){return{expressions:this.getExpressions(),stats:this.getExpressionStats()}}trackLog(e,a,r){if(!this.config.enabled)return;const i=r.map(u=>{if(typeof u=="string")return u;try{return JSON.stringify(u)}catch{return String(u)}}).join(" "),l={id:`log-${++this.logIdCounter}`,level:e,prefix:a,message:i,args:this.sanitizeObject(r),timestamp:Date.now(),stack:e==="error"?new Error().stack:void 0};this.logHistory.push(l),this.logHistory.length>this.maxLogHistory&&this.logHistory.shift()}trackAction(e){if(!this.config.enabled)return;const a={id:`debounce-action-${++this.debounceActionIdCounter}`,...e,timestamp:Date.now()};this.debounceActionHistory.push(a),this.debounceActionHistory.length>100&&this.debounceActionHistory.shift()}trackDataSourceUpdate(e){if(!this.config.enabled)return;const a={id:`ds-update-${++this.dataSourceUpdateIdCounter}`,...e};this.dataSourceUpdateHistory.push(a),this.dataSourceUpdateHistory.length>100&&this.dataSourceUpdateHistory.shift()}getDebounceActionHistory(e){let a=[...this.debounceActionHistory];return e?.handler&&(a=a.filter(r=>r.handler.includes(e.handler))),e?.status&&(a=a.filter(r=>r.status===e.status)),e?.limit&&(a=a.slice(-e.limit)),a}getDataSourceUpdateHistory(e){let a=[...this.dataSourceUpdateHistory];return e?.dataSourceId&&(a=a.filter(r=>r.dataSourceId===e.dataSourceId)),e?.updateType&&(a=a.filter(r=>r.updateType===e.updateType)),e?.limit&&(a=a.slice(-e.limit)),a}getLogs(e){let a=[...this.logHistory];if(e?.level){const r=Array.isArray(e.level)?e.level:[e.level];a=a.filter(i=>r.includes(i.level))}if(e?.prefix){const r=e.prefix.toLowerCase();a=a.filter(i=>i.prefix.toLowerCase().includes(r))}if(e?.search){const r=e.search.toLowerCase();a=a.filter(i=>i.message.toLowerCase().includes(r))}return e?.since&&(a=a.filter(r=>r.timestamp>=e.since)),e?.limit&&(a=a.slice(-e.limit)),a}getLogStats(){const a=Date.now()-6e4,r={log:0,warn:0,error:0,debug:0,info:0},i={};let l=0,u=0;for(const d of this.logHistory)r[d.level]++,i[d.prefix]=(i[d.prefix]||0)+1,d.timestamp>=a&&(d.level==="error"&&l++,d.level==="warn"&&u++);return{totalLogs:this.logHistory.length,byLevel:r,byPrefix:i,recentErrors:l,recentWarnings:u}}clearLogs(){this.logHistory=[],this.logIdCounter=0}getLogInfo(){return{entries:this.getLogs(),stats:this.getLogStats()}}trackDataSourceDefinition(e){this.config.enabled&&this.dataSources.set(e.id,{...e,status:"idle"})}trackDataSourceLoading(e){if(!this.config.enabled)return;const a=this.dataSources.get(e);a&&this.dataSources.set(e,{...a,status:"loading"})}trackDataSourceLoaded(e,a,r){if(!this.config.enabled)return;const i=this.dataSources.get(e);if(!i)return;let l,u,d=r;a&&(Array.isArray(a)?(l=a.length,u=a.length>0&&typeof a[0]=="object"?Object.keys(a[0]):void 0):typeof a=="object"&&(Array.isArray(a.data)?(d=d||"data",l=a.data.length,u=a.data.length>0&&typeof a.data[0]=="object"?Object.keys(a.data[0]):void 0):Array.isArray(a.items)?(d=d||"items",l=a.items.length,u=a.items.length>0&&typeof a.items[0]=="object"?Object.keys(a.items[0]):void 0):u=Object.keys(a))),this.dataSources.set(e,{...i,status:"loaded",dataPath:d,itemCount:l,keys:u,lastLoadedAt:Date.now(),error:void 0})}trackDataSourceError(e,a){if(!this.config.enabled)return;const r=this.dataSources.get(e);r&&this.dataSources.set(e,{...r,status:"error",error:a,lastLoadedAt:Date.now()})}untrackDataSource(e){this.dataSources.delete(e)}getDataSources(){return Array.from(this.dataSources.values())}getDataSource(e){return this.dataSources.get(e)}clearDataSources(){this.dataSources.clear()}trackDataPathTransform(e){if(!this.config.enabled)return;let a=this.dataPathTransforms.find(r=>r.dataSourceId===e.dataSourceId);a||(a={dataSourceId:e.dataSourceId,timestamp:Date.now(),transformSteps:[],warnings:[]},this.dataPathTransforms.push(a),this.dataPathTransforms.length>this.maxDataPathTransforms&&this.dataPathTransforms.shift()),a.transformSteps.push({step:e.step,inputPath:e.inputPath,inputValue:this.safeClone(e.inputValue),outputPath:e.outputPath,outputValue:this.safeClone(e.outputValue),config:e.config}),e.warning&&a.warnings.push(e.warning)}setDataPathFinalBinding(e,a){if(!this.config.enabled)return;const r=this.dataPathTransforms.find(i=>i.dataSourceId===e);r&&(r.finalBinding={expression:a.expression,resolvedPath:a.resolvedPath,value:this.safeClone(a.value)})}getDataPathTransformTrackingInfo(){const e=this.getDataPathTransformStats();return{transforms:[...this.dataPathTransforms],stats:e,timestamp:Date.now()}}getDataPathTransformStats(){const e=this.dataPathTransforms,a={};let r=0;const i={};for(const u of e){a[u.dataSourceId]=(a[u.dataSourceId]||0)+1,r+=u.warnings.length;for(const d of u.warnings)i[d]=(i[d]||0)+1}const l=Object.entries(i).map(([u,d])=>({warning:u,count:d})).sort((u,d)=>d.count-u.count).slice(0,10);return{totalTransforms:e.length,byDataSource:a,warningsCount:r,commonWarnings:l}}getDataPathTransformForDataSource(e){return this.dataPathTransforms.find(a=>a.dataSourceId===e)}clearDataPathTransforms(){this.dataPathTransforms=[]}trackNestedContext(e){if(!this.config.enabled)return"";const a=`nested-${++this.nestedContextIdCounter}`,r=[...new Set([...e.parentContext.available,...e.ownContext.added])],i={...e.parentContext.values,...e.ownContext.values},l={id:a,timestamp:Date.now(),componentId:e.componentId,componentType:e.componentType,parentContext:{available:e.parentContext.available,values:this.safeClone(e.parentContext.values)},ownContext:{added:e.ownContext.added,values:this.safeClone(e.ownContext.values)},mergedContext:{all:r,values:this.safeClone(i)},accessAttempts:[],depth:e.depth,parentId:e.parentId};return this.nestedContexts.push(l),this.nestedContexts.length>this.maxNestedContexts&&this.nestedContexts.shift(),a}trackNestedContextAccess(e,a){if(!this.config.enabled)return;const r=this.nestedContexts.find(i=>i.id===e);r&&r.accessAttempts.push({path:a.path,found:a.found,value:a.found?this.safeClone(a.value):void 0,error:a.error})}getNestedContextTrackingInfo(){const e=this.getNestedContextStats();return{contexts:[...this.nestedContexts],stats:e,timestamp:Date.now()}}getNestedContextStats(){const e=this.nestedContexts,a={expandChildren:0,cellChildren:0,iteration:0,modal:0,slot:0};let r=0,i=0;const l={};for(const d of e){a[d.componentType]++,d.depth>r&&(r=d.depth);for(const f of d.accessAttempts)f.found||(i++,l[f.path]=(l[f.path]||0)+1)}const u=Object.entries(l).map(([d,f])=>({path:d,count:f})).sort((d,f)=>f.count-d.count).slice(0,10);return{totalContexts:e.length,byType:a,maxDepth:r,failedAccessCount:i,commonFailedPaths:u}}getNestedContextForComponent(e){return this.nestedContexts.find(a=>a.componentId===e)}clearNestedContexts(){this.nestedContexts=[],this.nestedContextIdCounter=0}trackHandlerRegistration(e,a="custom",r,i){this.isEnabled&&this.handlers.set(e,{name:e,category:a,description:r,registeredAt:Date.now(),source:i})}trackHandlerUnregistration(e){this.isEnabled&&this.handlers.delete(e)}getHandlers(){return Array.from(this.handlers.values())}getHandler(e){return this.handlers.get(e)}getHandlersByCategory(e){return Array.from(this.handlers.values()).filter(a=>a.category===e)}clearHandlers(){this.handlers.clear()}trackEventSubscribe(e){if(!this.isEnabled)return;const a=this.componentEventSubscriptions.get(e),r=Date.now();a?(a.subscriberCount++,a.lastSubscribedAt=r):this.componentEventSubscriptions.set(e,{eventName:e,subscriberCount:1,firstSubscribedAt:r,lastSubscribedAt:r})}trackEventUnsubscribe(e){if(!this.isEnabled)return;const a=this.componentEventSubscriptions.get(e);a&&(a.subscriberCount--,a.subscriberCount<=0&&this.componentEventSubscriptions.delete(e))}trackEventEmit(e,a,r,i,l){if(!this.isEnabled)return;const u={id:`evt_${++this.componentEventIdCounter}`,eventName:e,data:this.sanitizeForLogging(a),timestamp:Date.now(),listenerCount:r,results:i?this.sanitizeForLogging(i):void 0,hasError:!!l,errorMessage:l?.message};this.componentEventEmitHistory.push(u),this.componentEventEmitHistory.length>this.maxComponentEventHistory&&(this.componentEventEmitHistory=this.componentEventEmitHistory.slice(-this.maxComponentEventHistory))}trackEventOff(e){this.isEnabled&&this.componentEventSubscriptions.delete(e)}trackEventClear(){this.isEnabled&&this.componentEventSubscriptions.clear()}getComponentEventInfo(){const e=Array.from(this.componentEventSubscriptions.values());return{subscriptions:e,emitHistory:[...this.componentEventEmitHistory],totalSubscribers:e.reduce((a,r)=>a+r.subscriberCount,0),totalEmits:this.componentEventEmitHistory.length}}getEventEmitHistory(){return[...this.componentEventEmitHistory]}getEventSubscriptions(){return Array.from(this.componentEventSubscriptions.values())}clearComponentEvents(){this.componentEventSubscriptions.clear(),this.componentEventEmitHistory=[],this.componentEventIdCounter=0}sanitizeForLogging(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{return"[Circular or Non-serializable]"}}startStateChange(e,a,r,i){if(!this.config.enabled)return"";const l=`setState_${++this.stateRenderingIdCounter}`;this.currentStateChangeContext={setStateId:l,startTime:performance.now(),changedPath:e,trigger:i||{},renderedComponents:[]};const u={id:`sr_${this.stateRenderingIdCounter}`,setStateId:l,statePath:e,oldValue:this.sanitizeForLogging(a),newValue:this.sanitizeForLogging(r),timestamp:Date.now(),triggeredBy:i||{},renderedComponents:[],totalRenderDuration:0,affectedBindingsCount:0};return this.stateRenderingLogs.push(u),this.stateRenderingLogs.length>this.maxStateRenderingLogs&&this.stateRenderingLogs.shift(),l}trackComponentRender(e,a,r,i=[],l=[],u){if(!this.config.enabled)return;const d=this.componentRenderCounts.get(a)||0;this.componentRenderCounts.set(a,d+1);for(const f of i)this.stateToComponentMap.has(f)||this.stateToComponentMap.set(f,new Set),this.stateToComponentMap.get(f).add(a);if(this.currentStateChangeContext){const f={componentId:e,componentName:a,renderDuration:r,accessedStatePaths:i,evaluatedBindings:l,renderOrder:this.currentStateChangeContext.renderedComponents.length,parentId:u};this.currentStateChangeContext.renderedComponents.push(f)}this.trackRender(a)}completeStateChange(e){if(!this.config.enabled||!this.currentStateChangeContext||this.currentStateChangeContext.setStateId!==e)return;const a=this.currentStateChangeContext,i=performance.now()-a.startTime,l=this.stateRenderingLogs.findIndex(u=>u.setStateId===e);if(l>=0){const u=this.stateRenderingLogs[l];u.renderedComponents=a.renderedComponents,u.totalRenderDuration=i,u.affectedBindingsCount=a.renderedComponents.reduce((d,f)=>d+f.evaluatedBindings.length,0)}this.currentStateChangeContext=null}getCurrentStateChangeContext(){return this.currentStateChangeContext}getStateRenderingLogs(){return[...this.stateRenderingLogs]}getStateRenderingInfo(){const e=this.stateRenderingLogs,a=Object.fromEntries(this.componentRenderCounts),r={};for(const[i,l]of this.stateToComponentMap)r[i]=Array.from(l);return{logs:e,componentRenderCounts:a,stateToComponentMap:r,stats:this.calculateStateRenderingStats()}}calculateStateRenderingStats(){const e=this.stateRenderingLogs;if(e.length===0)return{totalStateChanges:0,totalRenders:0,avgRenderDuration:0,avgComponentsPerChange:0,topRenderedComponents:[],topInfluentialPaths:[]};const a=e.reduce((d,f)=>d+f.renderedComponents.length,0),r=e.reduce((d,f)=>d+f.totalRenderDuration,0)/e.length,i=a/e.length,l=Array.from(this.componentRenderCounts.entries()).sort((d,f)=>f[1]-d[1]).slice(0,5).map(([d,f])=>({name:d,count:f})),u=Array.from(this.stateToComponentMap.entries()).sort((d,f)=>f[1].size-d[1].size).slice(0,5).map(([d,f])=>({path:d,affectedComponents:f.size}));return{totalStateChanges:e.length,totalRenders:a,avgRenderDuration:r,avgComponentsPerChange:i,topRenderedComponents:l,topInfluentialPaths:u}}getComponentsAffectedByState(e){const a=this.stateToComponentMap.get(e);return a?Array.from(a):[]}getComponentRenderHistory(e){return this.stateRenderingLogs.filter(a=>a.renderedComponents.some(r=>r.componentName===e))}clearStateRenderingLogs(){this.stateRenderingLogs=[],this.componentRenderCounts.clear(),this.stateToComponentMap.clear(),this.currentStateChangeContext=null,this.stateRenderingIdCounter=0}trackComponentStateSource(e,a,r,i){this.config.enabled&&this.componentStateSources.set(e,{componentId:e,componentName:a,stateSource:r,stateProvider:i})}trackDynamicState(e,a){this.config.enabled&&this.dynamicStates.set(e,a)}trackContextFlow(e,a,r,i,l,u){if(!this.config.enabled)return;const d={component:a,componentId:e,contextReceived:r,passedToChildren:i,usedInRender:l};if(this.contextFlowNodes.set(e,d),u){const f=this.contextFlowNodes.get(u);f&&(f.children||(f.children=[]),f.children.push(d))}}getStateHierarchyInfo(e=!0){const a=this.buildStateHierarchyLayers(),r=this.detectStateConflicts(a),i=Array.from(this.componentStateSources.values());return{layers:e?a.map(u=>({...u,values:this.summarizeValues(u.values,2)})):a,conflicts:r,componentStateSources:i,timestamp:Date.now()}}summarizeValues(e,a,r=0){if(e==null)return e;if(typeof e!="object")return typeof e=="string"&&e.length>100?e.substring(0,100)+`... (${e.length}자)`:e;if(r>=a){if(Array.isArray(e))return`[Array(${e.length})]`;const u=Object.keys(e);return`{${u.slice(0,5).join(", ")}${u.length>5?`, ... +${u.length-5}`:""}}`}if(Array.isArray(e))return e.length<=3?e.map(u=>this.summarizeValues(u,a,r+1)):{__type:"array",__length:e.length,__preview:e.slice(0,3).map(u=>this.summarizeValues(u,a,r+1))};const i={},l=Object.keys(e);for(const u of l)i[u]=this.summarizeValues(e[u],a,r+1);return i}buildStateHierarchyLayers(){const e=[];try{const r=window.G7Core?.state?.get?.();if(!r)return e;r._local&&e.push({name:"Global _local",type:"global",path:"_local",values:r._local,priority:1}),r._global&&e.push({name:"Global _global",type:"global",path:"_global",values:r._global,priority:1});for(const[l,u]of this.dynamicStates)u&&Object.keys(u).length>0&&e.push({name:`DynamicState (${l})`,type:"dynamicState",componentId:l,values:u,priority:2});const i=this.computeEffectiveState(r._local||{});e.push({name:"Effective _local (merged)",type:"effective",values:i,priority:3})}catch(a){console.warn("[G7DevTools] 상태 계층 빌드 실패:",a)}return e}computeEffectiveState(e){const a={...e};for(const[,r]of this.dynamicStates)r._local&&Object.assign(a,r._local);return a}detectStateConflicts(e){const a=[],r=e.find(l=>l.type==="global"&&l.path==="_local");if(!r)return a;const i=e.filter(l=>l.type==="dynamicState");for(const l of i){const u=l.values._local||l.values;for(const[d,f]of Object.entries(u)){const g=r.values[d];if(JSON.stringify(g)!==JSON.stringify(f)){const m=[],b=[];for(const v of this.componentStateSources.values())v.stateSource.local.some(E=>E===`_local.${d}`||E===d)&&(v.stateProvider.type==="dynamicState"?m.push(v.componentName):v.stateProvider.type==="globalState"&&b.push(v.componentName));a.push({path:`_local.${d}`,globalValue:g,dynamicStateValue:f,effectiveValue:f,usedBy:m,notUsedBy:b,severity:b.length>0?"warning":"info",description:b.length>0?`${b.join(", ")}은(는) globalState._local을 읽어 dynamicState 값을 사용하지 못함`:"dynamicState 값이 globalState와 다르지만 모든 컴포넌트가 dynamicState를 사용"})}}}return a}getContextFlowInfo(){const e=[],a=new Set(this.contextFlowNodes.keys());for(const r of this.contextFlowNodes.values())if(r.children)for(const i of r.children)a.delete(i.componentId);for(const r of a){const i=this.contextFlowNodes.get(r);i&&e.push(i)}return{rootComponent:e.length>0?e[0].component:"Unknown",contextFlow:e,timestamp:Date.now()}}clearStateHierarchyData(){this.componentStateSources.clear(),this.contextFlowNodes.clear(),this.dynamicStates.clear()}trackComponentStyle(e,a,r,i){if(!this.config.enabled)return;const l=this.analyzeTailwindClasses(r),u={componentId:e,componentName:a,classes:r,computedStyles:i,tailwindAnalysis:l};this.componentStyles.set(e,u),this.detectStyleIssues(u)}analyzeTailwindClasses(e){const a=[],r=[],i=[],l=[],u=["sm:","md:","lg:","xl:","2xl:"],d=[/bg-\w+-\d+/,/text-\w+-\d+/,/border-\w+-\d+/];for(const f of e){l.push(f),f.startsWith("dark:")&&a.push(f);for(const g of u)if(f.startsWith(g)){r.push(f);break}for(const g of d)if(g.test(f)){i.push(f);break}}return{usedClasses:l,darkClasses:a,responsiveClasses:r,dynamicClasses:i}}detectStyleIssues(e){const{componentId:a,componentName:r,computedStyles:i,tailwindAnalysis:l}=e;(i.opacity==="0"||i.visibility==="hidden"||i.display==="none"||i.width==="0px"&&i.height==="0px")&&this.addStyleIssue({id:this.generateId(),type:"invisible-element",componentId:a,componentName:r,property:i.opacity==="0"?"opacity":i.visibility==="hidden"?"visibility":i.display==="none"?"display":"width/height",currentValue:i.opacity==="0"?"0":i.visibility==="hidden"?"hidden":i.display==="none"?"none":"0px",severity:"warning",description:"요소가 화면에 보이지 않습니다.",suggestion:"opacity, visibility, display, width/height 값을 확인하세요."});const u=l.usedClasses.filter(f=>f.startsWith("bg-")&&!f.startsWith("dark:")),d=l.darkClasses.filter(f=>f.includes("bg-"));u.length>0&&d.length===0&&this.addStyleIssue({id:this.generateId(),type:"dark-mode-missing",componentId:a,componentName:r,property:"background-color",currentValue:u.join(", "),expectedValue:"dark:bg-* 클래스 필요",severity:"info",description:"다크 모드 배경색 클래스가 누락되었습니다.",suggestion:`${u[0]}에 대응하는 dark: 클래스를 추가하세요.`}),l.dynamicClasses.length>0&&this.addStyleIssue({id:this.generateId(),type:"tailwind-purging",componentId:a,componentName:r,property:"class",currentValue:l.dynamicClasses.join(", "),severity:"info",description:"동적으로 생성된 Tailwind 클래스가 있습니다.",suggestion:"tailwind.config.js의 safelist에 추가하거나 전체 클래스명을 상수로 정의하세요."})}addStyleIssue(e){this.styleIssues.some(r=>r.componentId===e.componentId&&r.type===e.type&&r.property===e.property)||this.styleIssues.push(e)}getStyleValidationInfo(){const e=Array.from(this.componentStyles.values());return{issues:[...this.styleIssues],componentStyles:e,stats:{totalComponents:e.length,invisibleCount:this.styleIssues.filter(a=>a.type==="invisible-element").length,tailwindIssueCount:this.styleIssues.filter(a=>a.type==="tailwind-purging").length,darkModeIssueCount:this.styleIssues.filter(a=>a.type==="dark-mode-missing").length},timestamp:Date.now()}}clearStyleValidationData(){this.styleIssues=[],this.componentStyles.clear()}trackAuthEvent(e,a,r,i){if(!this.config.enabled)return;const l={id:`auth_${++this.authEventIdCounter}`,type:e,timestamp:Date.now(),success:a,error:r,details:i};this.authEvents.push(l),this.authEvents.length>this.maxAuthEventHistory&&(this.authEvents=this.authEvents.slice(-this.maxAuthEventHistory))}trackAuthHeader(e,a,r,i,l){if(!this.config.enabled)return;const u={url:e,hasAuthHeader:a,headerType:r,tokenValid:i??!1,responseStatus:l,timestamp:Date.now()};this.authHeaderHistory.push(u),this.authHeaderHistory.length>this.maxAuthEventHistory&&(this.authHeaderHistory=this.authHeaderHistory.slice(-this.maxAuthEventHistory)),l===401&&this.trackAuthEvent("api-unauthorized",!1,`401 Unauthorized: ${e}`,{url:e})}getAuthState(){try{const e=window.G7Core,a=e?.api;let r=e?.auth;!r&&e?.AuthManager?.getInstance&&(r=e.AuthManager.getInstance());const i=a?.getToken?.()||r?.getAccessToken?.()||localStorage.getItem("auth_token")||localStorage.getItem("access_token"),l=r?.getRefreshToken?.()||localStorage.getItem("refresh_token");let u="unknown",d=r?.getUser?.();if(d&&(u="AuthManager"),!d){const S=e?.state?.get?.();d=S?._global?.user||S?._global?.currentUser||S?.user||S?.currentUser,d&&(u="G7Core.state")}const f=r?.isAuthenticated?.()??!1;let g="memory";localStorage.getItem("auth_token")||localStorage.getItem("access_token")?g="localStorage":(sessionStorage.getItem("auth_token")||sessionStorage.getItem("access_token"))&&(g="sessionStorage");const m=!!i&&(!!d||f);let b;i&&typeof i=="string"&&i.length>20?b=`${i.substring(0,10)}...${i.substring(i.length-6)}`:i&&(b="***");const v=d?{id:d.id,email:d.email,name:d.name,roles:d.roles?.map(S=>typeof S=="string"?{name:S}:{id:S.id,name:S.name,guard_name:S.guard_name}),permissions:d.permissions?.map(S=>typeof S=="string"?{name:S}:{id:S.id,name:S.name,guard_name:S.guard_name}),avatar:d.avatar||d.profile_photo_url||d.profile_image,created_at:d.created_at,updated_at:d.updated_at,last_login_at:d.last_login_at,email_verified_at:d.email_verified_at,...d.phone&&{phone:d.phone},...d.nickname&&{nickname:d.nickname},...d.status&&{status:d.status},...d.locale&&{locale:d.locale}}:void 0;return{isAuthenticated:m,user:v,tokens:{hasAccessToken:!!i,hasRefreshToken:!!l,storage:g,accessTokenPreview:b},lastActivity:Date.now(),source:u}}catch{return{isAuthenticated:!1,tokens:{hasAccessToken:!1,hasRefreshToken:!1,storage:"memory"},source:"unknown"}}}getAuthDebugInfo(){const e=[...this.authEvents];return{state:this.getAuthState(),events:e,headerAnalysis:[...this.authHeaderHistory],stats:{loginAttempts:e.filter(a=>a.type==="login").length,successfulLogins:e.filter(a=>a.type==="login"&&a.success).length,failedLogins:e.filter(a=>a.type==="login"&&!a.success).length,tokenRefreshes:e.filter(a=>a.type==="token-refresh").length,unauthorizedResponses:e.filter(a=>a.type==="api-unauthorized").length},timestamp:Date.now()}}clearAuthData(){this.authEvents=[],this.authHeaderHistory=[],this.authEventIdCounter=0}trackLayoutLoad(e,a,r,i="api"){if(!this.config.enabled)return;const l=Date.now();this.currentLayout={layoutPath:e,templateId:a,layoutJson:this.sanitizeObject(r),loadedAt:l,version:r.version,layoutName:r.layout_name,source:i};const u={id:`layout-${++this.layoutIdCounter}`,layoutPath:e,templateId:a,loadedAt:l,source:i,version:r.version};this.layoutHistory.push(u),this.layoutHistory.length>this.maxLayoutHistory&&this.layoutHistory.shift(),this.layoutStats.totalLoads++,i==="cache"?this.layoutStats.cacheHits++:this.layoutStats.apiLoads++}getCurrentLayout(){return this.currentLayout}getLayoutHistory(){return[...this.layoutHistory]}getLayoutDebugInfo(){return{current:this.currentLayout,history:[...this.layoutHistory],stats:{...this.layoutStats}}}clearLayoutData(){this.currentLayout=null,this.layoutHistory=[],this.layoutIdCounter=0,this.layoutStats={totalLoads:0,cacheHits:0,apiLoads:0}}startHandlerExecution(e){if(!this.config.enabled)return"";const a=`exec_${++this.executionIdCounter}_${Date.now()}`,r={handlerName:e,executionId:a,startTime:Date.now(),stateChanges:[],dataSourceChanges:[],alerts:[]};if(this.executionDetails.set(a,r),this.executionDetails.size>this.maxExecutionDetails){const i=Array.from(this.executionDetails.keys());i.slice(0,i.length-this.maxExecutionDetails).forEach(u=>this.executionDetails.delete(u))}return a}endHandlerExecution(e,a,r,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);if(!l)return;l.endTime=Date.now(),l.duration=l.endTime-l.startTime,l.exitReason=a,l.exitLocation=r,l.exitDescription=i;const u=this.changeExpectations.get(e);u&&(l.expectedChanges=u,this.checkExpectations(e,l,u),this.changeExpectations.delete(e)),a==="normal"&&l.stateChanges.length===0&&l.dataSourceChanges.length===0&&this.addChangeAlert(e,{type:"no-state-change",severity:"warning",message:`핸들러 "${l.handlerName}"이(가) 상태 변경 없이 완료됨`,description:"핸들러가 성공적으로 완료되었지만 상태나 데이터소스 변경이 없습니다. 의도한 동작인지 확인하세요.",handlerName:l.handlerName,suggestion:'핸들러 내부 조건문을 확인하거나 DevTools의 "변경감지" 탭에서 exitLocation을 확인하세요.',docLink:"troubleshooting-state.md"}),(a==="early-return-condition"||a==="early-return-validation")&&this.addChangeAlert(e,{type:"early-return-detected",severity:"info",message:`핸들러 "${l.handlerName}"이(가) early return으로 종료됨`,description:i||`종료 위치: ${r||"알 수 없음"}`,handlerName:l.handlerName,suggestion:a==="early-return-validation"?"검증 실패로 인한 early return입니다. 입력 데이터를 확인하세요.":"조건부 early return입니다. 조건문의 평가 결과를 확인하세요."})}recordExitReason(e,a,r,i){if(!this.config.enabled||!e)return;const l=this.executionDetails.get(e);l&&(l.exitReason=a,l.exitLocation=r,l.exitDescription=i)}recordStateChange(e,a,r,i,l){if(!this.config.enabled)return;const u={id:`sc_${++this.stateChangeIdCounter}`,path:a,changeType:r,oldValue:this.safeClone(i),newValue:this.safeClone(l),timestamp:Date.now(),comparison:this.compareValues(i,l),executionId:e};if(this.stateChangeHistory.push(u),e){const d=this.executionDetails.get(e);d&&d.stateChanges.push(u)}this.stateChangeHistory.length>this.maxStateChangeHistory&&this.stateChangeHistory.splice(0,this.stateChangeHistory.length-this.maxStateChangeHistory),u.comparison.isDeepEqual&&typeof i=="object"&&i!==null&&this.addChangeAlert(e||"",{type:"object-reference-same",severity:"warning",message:`상태 "${a}"가 변경되었으나 값이 동일함`,description:"객체 참조가 동일하거나 깊은 비교 결과 변경이 없습니다. 변경 감지가 실패할 수 있습니다.",handlerName:e&&this.executionDetails.get(e)?.handlerName||"unknown",statePath:a,suggestion:"불변성을 유지하여 새 객체를 생성하세요. 예: { ...oldObject, newField: value }",docLink:"troubleshooting-state.md#object-mutation"})}recordDataSourceChange(e,a,r,i,l){if(!this.config.enabled)return;const u={dataSourceId:a,changeType:r,timestamp:Date.now(),previousStatus:i,newStatus:l,executionId:e};if(this.dataSourceChangeHistory.push(u),e){const d=this.executionDetails.get(e);d&&d.dataSourceChanges.push(u)}this.dataSourceChangeHistory.length>this.maxStateChangeHistory&&this.dataSourceChangeHistory.splice(0,this.dataSourceChangeHistory.length-this.maxStateChangeHistory)}expectChange(e,a,r){if(!this.config.enabled||!e)return;const i=this.executionDetails.get(e);if(!i)return;const l={expectedStatePaths:a,expectedDataSources:r,setAt:Date.now(),source:i.handlerName};this.changeExpectations.set(e,l)}checkExpectations(e,a,r){const i=new Set(a.stateChanges.map(u=>u.path)),l=new Set(a.dataSourceChanges.map(u=>u.dataSourceId));for(const u of r.expectedStatePaths)i.has(u)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 상태 변경 "${u}"가 발생하지 않음`,description:`핸들러 "${a.handlerName}"이(가) "${u}" 변경을 예고했지만 실제로 변경되지 않았습니다.`,handlerName:a.handlerName,statePath:u,suggestion:"핸들러 내부 로직을 확인하세요. early return이나 조건부 분기로 인해 변경이 생략되었을 수 있습니다.",docLink:"troubleshooting-state.md"});for(const u of r.expectedDataSources)l.has(u)||this.addChangeAlert(e,{type:"expected-not-fulfilled",severity:"error",message:`기대한 데이터소스 갱신 "${u}"가 발생하지 않음`,description:`핸들러 "${a.handlerName}"이(가) "${u}" 갱신을 예고했지만 실제로 갱신되지 않았습니다.`,handlerName:a.handlerName,dataSourceId:u,suggestion:"refetchDataSource 호출이 실행되었는지 확인하세요.",docLink:"data-sources.md"})}addChangeAlert(e,a){const r={...a,id:`alert_${++this.alertIdCounter}`,timestamp:Date.now()};if(this.changeAlerts.push(r),e){const i=this.executionDetails.get(e);i&&i.alerts.push(r)}this.changeAlerts.length>this.maxChangeAlerts&&this.changeAlerts.splice(0,this.changeAlerts.length-this.maxChangeAlerts)}compareValues(e,a){const r=this.getValueType(e),i=this.getValueType(a),l=r!==i;let u=!1,d;return!l&&(r==="object"||r==="array")?(u=this.deepEqual(e,a),!u&&r==="object"&&(d=this.getChangedKeys(e,a))):u=e===a,{typeChanged:l,oldType:r,newType:i,isDeepEqual:u,changedKeys:d}}getValueType(e){return e===null?"null":e===void 0?"undefined":Array.isArray(e)?"array":typeof e}deepEqual(e,a){if(e===a)return!0;if(typeof e!=typeof a)return!1;if(e===null||a===null||typeof e!="object")return e===a;if(Array.isArray(e)!==Array.isArray(a))return!1;const r=Object.keys(e),i=Object.keys(a);if(r.length!==i.length)return!1;for(const l of r)if(!i.includes(l)||!this.deepEqual(e[l],a[l]))return!1;return!0}getChangedKeys(e,a){const r=[],i=new Set([...Object.keys(e),...Object.keys(a)]);for(const l of i)this.deepEqual(e[l],a[l])||r.push(l);return r}getChangeDetectionInfo(){const e=Array.from(this.executionDetails.values()),a={totalExecutions:e.length,executionsWithStateChange:e.filter(r=>r.stateChanges.length>0).length,executionsWithoutStateChange:e.filter(r=>r.stateChanges.length===0&&r.exitReason==="normal").length,earlyReturnCount:e.filter(r=>r.exitReason==="early-return-condition"||r.exitReason==="early-return-validation").length,alertCount:this.changeAlerts.length,alertsByType:this.getAlertsByType()};return{executionDetails:e,stateChangeHistory:[...this.stateChangeHistory],dataSourceChangeHistory:[...this.dataSourceChangeHistory],alerts:[...this.changeAlerts],stats:a,timestamp:Date.now()}}getAlertsByType(){const e={"no-state-change":0,"no-datasource-change":0,"expected-not-fulfilled":0,"object-reference-same":0,"early-return-detected":0,"async-timing-issue":0};for(const a of this.changeAlerts)e[a.type]++;return e}clearChangeDetectionData(){this.executionDetails.clear(),this.stateChangeHistory=[],this.dataSourceChangeHistory=[],this.changeAlerts=[],this.changeExpectations.clear(),this.executionIdCounter=0,this.stateChangeIdCounter=0,this.alertIdCounter=0}getRecentAlerts(e=10,a){let r=[...this.changeAlerts];return a&&(r=r.filter(i=>i.severity===a)),r.slice(-e)}getHandlerExecutions(e,a=10){return Array.from(this.executionDetails.values()).filter(i=>i.handlerName===e).slice(-a)}findExecutionInStack(e){return this.sequenceExecutionStack.find(a=>a.sequenceId===e)}startSequenceExecution(e){if(!this.config.enabled)return"";const a=`seq_${++this.sequenceIdCounter}_${Date.now()}`,r={sequenceId:a,startTime:Date.now(),totalDuration:0,trigger:e,actions:[],status:"running"};return this.sequenceExecutionStack.push(r),a}captureSequenceActionBefore(e,a,r,i,l){if(!this.config.enabled)return;const u=this.findExecutionInStack(e);if(!u)return;const d={index:a,handler:r,params:this.safeClone(i),stateBeforeAction:{_global:this.safeClone(l._global),_local:this.safeClone(l._local),_isolated:l._isolated?this.safeClone(l._isolated):void 0},stateAfterAction:{_global:{},_local:{}},duration:0,status:"success"};u.actions.push(d)}captureSequenceActionAfter(e,a,r,i,l,u){if(!this.config.enabled)return;const d=this.findExecutionInStack(e);if(!d)return;const f=d.actions.find(m=>m.index===a);if(!f)return;f.stateAfterAction={_global:this.safeClone(r._global),_local:this.safeClone(r._local),_isolated:r._isolated?this.safeClone(r._isolated):void 0},f.duration=i,f.result=this.safeClone(l),f.stateDiff={global:this.computeStateDiff(f.stateBeforeAction._global,f.stateAfterAction._global),local:this.computeStateDiff(f.stateBeforeAction._local,f.stateAfterAction._local)},f.stateBeforeAction._isolated&&f.stateAfterAction._isolated&&(f.stateDiff.isolated=this.computeStateDiff(f.stateBeforeAction._isolated,f.stateAfterAction._isolated));const g=window.__g7PendingLocalState;g&&(f.pendingState=this.safeClone(g)),u&&(f.status="error",f.error={name:u.name,message:u.message,stack:u.stack})}endSequenceExecution(e,a){if(!this.config.enabled)return;const r=this.sequenceExecutionStack.findIndex(l=>l.sequenceId===e);if(r===-1)return;const i=this.sequenceExecutionStack[r];if(i.endTime=Date.now(),i.totalDuration=i.endTime-i.startTime,a){i.status="error",i.error={name:a.name,message:a.message,stack:a.stack};const l=i.actions.find(u=>u.status==="error");l&&(i.failedAtIndex=l.index)}else i.status="success";this.sequenceExecutions.push(i),this.sequenceExecutions.length>this.maxSequenceExecutions&&this.sequenceExecutions.shift(),this.sequenceExecutionStack.splice(r,1)}computeStateDiff(e,a){const r=[],i=[],l=[],u=new Set(Object.keys(e)),d=new Set(Object.keys(a));for(const f of d)u.has(f)||r.push(f);for(const f of u)d.has(f)||i.push(f);for(const f of u)if(d.has(f)){const g=e[f],m=a[f];JSON.stringify(g)!==JSON.stringify(m)&&l.push({path:f,oldValue:g,newValue:m})}return{added:r,removed:i,changed:l}}getSequenceTrackingInfo(){const e=this.getSequenceStats(),a=this.sequenceExecutionStack.length>0?this.sequenceExecutionStack[this.sequenceExecutionStack.length-1]:void 0;return{executions:[...this.sequenceExecutions],currentExecution:a,stats:e}}getSequenceStats(){const e=this.sequenceExecutions,a=e.length,r=e.filter(g=>g.status==="success").length,i=e.filter(g=>g.status==="error").length,l=e.reduce((g,m)=>g+m.actions.length,0),u=a>0?e.reduce((g,m)=>g+m.totalDuration,0)/a:0,d={};for(const g of e)for(const m of g.actions)d[m.handler]=(d[m.handler]||0)+1;const f=Object.entries(d).sort(([,g],[,m])=>m-g).slice(0,5).map(([g,m])=>({handler:g,count:m}));return{totalExecutions:a,successCount:r,errorCount:i,totalActions:l,avgDuration:u,topHandlers:f}}clearSequenceData(){this.sequenceExecutions=[],this.sequenceExecutionStack=[],this.sequenceIdCounter=0}getSequenceExecution(e){const a=this.findExecutionInStack(e);return a||this.sequenceExecutions.find(r=>r.sequenceId===e)}getRecentSequences(e=10){return this.sequenceExecutions.slice(-e)}registerStateCaptureForHandler(e,a,r){if(!this.config.enabled)return;const i=new Map,l=Date.now();for(const u of a)i.set(u,{value:this.safeClone(r[u]),capturedAt:l});this.stateCaptureRegistry.set(e,i)}detectStaleClosure(e,a,r,i,l){if(!this.config.enabled)return[];const u=this.stateCaptureRegistry.get(e);if(!u)return[];const d=Date.now(),f=[];for(const[g,m]of u){const b=this.getValueByPath(r,g),v=d-m.capturedAt;if(!this.isDeepEqual(m.value,b)&&v>0){const S=this.createStaleClosureWarning(i,a,g,m.value,m.capturedAt,b,d,v,l);f.push(S),this.addStaleClosureWarning(S)}}return this.stateCaptureRegistry.delete(e),f}trackStaleClosureWarning(e){if(!this.config.enabled)return;const a=Date.now(),r=this.createStaleClosureWarning(e.type,e.location,e.capturedPath,e.capturedValue,e.capturedAt,e.currentValue,a,a-e.capturedAt,e.actionId,e.stackTrace);this.addStaleClosureWarning(r)}createStaleClosureWarning(e,a,r,i,l,u,d,f,g,m){const b=this.getStaleClosureSeverity(e,f),{description:v,suggestion:S,docLink:E}=this.getStaleClosureMessages(e,r,f);return{id:`stale_${++this.staleClosureIdCounter}_${Date.now()}`,timestamp:Date.now(),type:e,location:a,capturedState:{path:r,capturedValue:this.safeClone(i),capturedAt:l},currentState:{path:r,currentValue:this.safeClone(u),retrievedAt:d},timeDiff:f,severity:b,description:v,suggestion:S,docLink:E,actionId:g,stackTrace:m}}getStaleClosureSeverity(e,a){if(a>5e3)return"error";if(a>1e3)return"warning";switch(e){case"async-state-capture":case"sequence-state-mismatch":return"warning";case"callback-state-capture":case"timeout-state-capture":return"info";case"event-handler-stale":return"warning";default:return"info"}}getStaleClosureMessages(e,a,r){const i=r<1e3?`${r}ms`:`${(r/1e3).toFixed(1)}s`;switch(e){case"async-state-capture":return{description:`await 후 ${i} 경과 시점에 '${a}' 상태가 변경됨. 캡처된 상태 대신 최신 상태를 사용해야 함`,suggestion:"await 이후에는 G7Core.state.get()으로 최신 상태를 다시 조회하거나, useRef + getter 패턴을 사용하세요",docLink:"troubleshooting-state-closure.md"};case"callback-state-capture":return{description:`콜백에서 ${i} 전에 캡처된 '${a}' 상태를 사용 중. 최신 상태와 다름`,suggestion:"콜백에서 상태를 참조할 때는 stateRef.current 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};case"timeout-state-capture":return{description:`setTimeout/setInterval 콜백에서 '${a}' 상태가 ${i} 전 값 사용 중`,suggestion:"타이머 콜백 내에서는 G7Core.state.get()으로 최신 상태를 조회하세요",docLink:"troubleshooting-state-closure.md"};case"sequence-state-mismatch":return{description:`sequence 내 '${a}' 상태가 이전 액션 결과와 불일치. context.state 대신 캡처된 값 사용 의심`,suggestion:"sequence 내에서는 context.state 또는 $prev를 사용하여 최신 상태를 참조하세요",docLink:"troubleshooting-state-setstate.md"};case"event-handler-stale":return{description:`이벤트 핸들러에서 ${i} 전에 바인딩된 '${a}' 상태 사용 중`,suggestion:"이벤트 핸들러에서 상태를 참조할 때는 useRef 패턴 또는 G7Core.state.get()를 사용하세요",docLink:"troubleshooting-state-closure.md"};default:return{description:`'${a}' 상태에서 stale closure 감지됨 (${i} 경과)`,suggestion:"상태 참조 시 최신 값을 사용하는지 확인하세요"}}}addStaleClosureWarning(e){this.staleClosureWarnings.push(e),this.staleClosureWarnings.length>this.maxStaleClosureWarnings&&this.staleClosureWarnings.shift()}getValueByPath(e,a){const r=a.split(".");let i=e;for(const l of r){if(i==null)return;i=i[l]}return i}isDeepEqual(e,a){if(e===a)return!0;if(e===null||a===null)return e===a;if(typeof e!=typeof a)return!1;if(typeof e!="object")return e===a;try{return JSON.stringify(e)===JSON.stringify(a)}catch{return!1}}getStaleClosureTrackingInfo(){const e=this.getStaleClosureStats();return{warnings:[...this.staleClosureWarnings],stats:e,timestamp:Date.now()}}getStaleClosureStats(){const e=this.staleClosureWarnings,a={"async-state-capture":0,"callback-state-capture":0,"timeout-state-capture":0,"sequence-state-mismatch":0,"event-handler-stale":0};for(const f of e)a[f.type]++;const r={info:0,warning:0,error:0};for(const f of e)r[f.severity]++;const i={};for(const f of e)i[f.location]=(i[f.location]||0)+1;const l=Object.entries(i).sort(([,f],[,g])=>g-f).slice(0,5).map(([f,g])=>({location:f,count:g})),u={};for(const f of e)u[f.capturedState.path]=(u[f.capturedState.path]||0)+1;const d=Object.entries(u).sort(([,f],[,g])=>g-f).slice(0,5).map(([f,g])=>({path:f,count:g}));return{totalWarnings:e.length,warningsByType:a,warningsBySeverity:r,topLocations:l,topAffectedPaths:d}}clearStaleClosureData(){this.staleClosureWarnings=[],this.staleClosureIdCounter=0,this.stateCaptureRegistry.clear()}getRecentStaleClosureWarnings(e=10,a){let r=[...this.staleClosureWarnings];return a&&(r=r.filter(i=>i.severity===a)),r.slice(-e)}safeClone(e){if(e==null||typeof e!="object")return e;try{return JSON.parse(JSON.stringify(e))}catch{if(Array.isArray(e))return e.map(r=>{try{return JSON.parse(JSON.stringify(r))}catch{return"[Unserializable]"}});const a={};for(const r of Object.keys(e))try{a[r]=JSON.parse(JSON.stringify(e[r]))}catch{a[r]="[Unserializable]"}return a}}};$(yi,"instance");let Tr=yi;const Bn=ct("LayoutLoader");class ba extends Error{constructor(a,r,i){super(a);$(this,"code");$(this,"details");this.code=r,this.details=i,this.name="LayoutLoaderError"}}class nf{constructor(e){$(this,"componentRegistry");$(this,"currentLayout",null);$(this,"layoutCache",new Map);$(this,"cacheVersion",0);this.componentRegistry=e}setCacheVersion(e){this.cacheVersion!==e&&(Bn.log("Cache version updated:",this.cacheVersion,"->",e),this.cacheVersion=e,this.layoutCache.clear())}getCacheVersion(){return this.cacheVersion}async loadLayout(e,a){const r=`${e}:${a}`;if(this.layoutCache.has(r)){Bn.log("Loading layout from cache:",a);const l=await this.layoutCache.get(r),u=JSON.parse(JSON.stringify(l));return this.currentLayout=u,Tr.getInstance().trackLayoutLoad(a,e,u,"cache"),u}const i=this.fetchLayout(e,a);return this.layoutCache.set(r,i),i.catch(()=>{this.layoutCache.get(r)===i&&this.layoutCache.delete(r)}),i}async fetchLayout(e,a,r=!1){try{const i=this.cacheVersion>0?this.cacheVersion:null;let l;if(a.startsWith("__preview__/")){const v=a.replace("__preview__/","");l=La(`/api/layouts/preview/${v}`,"json",i)}else l=La(`/api/layouts/${e}/${a}`,"json",i);Bn.log("Fetching layout from API:",l);const u=Na(),d=r?null:u.getToken(),f={Accept:"application/json"};d&&(f.Authorization=`Bearer ${d}`);const g=await xl(l,{init:{headers:f},label:`layout: ${a}`});let m;try{m=await g.json()}catch{m=null}if(!g.ok){if(g.status===401&&!r&&d){Bn.log("Token invalid, removing and retrying without token"),u.removeToken();try{return await this.fetchLayout(e,a,!0)}catch(S){throw S instanceof ba&&S.details?.status===401?new ba(S.message,S.code,{...S.details,hadToken:!0}):S}}const v=m?.message||m?.error;throw new ba(`Failed to fetch layout: ${g.status} ${g.statusText}`,"FETCH_FAILED",{status:g.status,statusText:g.statusText,url:l,apiMessage:v})}const b=m.data||m;return this.validateLayoutData(b),this.currentLayout=b,Tr.getInstance().trackLayoutLoad(a,e,b,"api"),Bn.log("Layout fetched and cached successfully:",b.layout_name),b}catch(i){const l=`${e}:${a}`;throw this.layoutCache.delete(l),i instanceof ba?i:new ba("Failed to load layout","LOAD_FAILED",{originalError:i})}}prefetchLayout(e,a){return this.loadLayout(e,a)}validateLayoutData(e){if(!e.version)throw new ba("Layout data missing required field: version","VALIDATION_FAILED",{field:"version"});if(!e.layout_name)throw new ba("Layout data missing required field: layout_name","VALIDATION_FAILED",{field:"layout_name"});if(!Array.isArray(e.components))throw new ba('Layout data field "components" must be an array',"VALIDATION_FAILED",{field:"components",value:e.components});Bn.log("Layout data validation passed")}renderLayout(e,a){try{const r=a||this.currentLayout;if(!r)throw new ba("No layout data to render","NO_LAYOUT_DATA");Bn.log("Rendering layout:",r.layout_name),e.innerHTML="",this.renderComponents(e,r.components),Bn.log("Layout rendered successfully")}catch(r){Bn.error("Render error:",r),this.renderErrorState(e,r)}}renderComponents(e,a){for(const r of a){const i=this.renderComponent(r);i&&e.appendChild(i)}}renderComponent(e){try{if(!this.componentRegistry.getComponent(e.type))return Bn.warn(`Component not found: ${e.type}`),this.createPlaceholderElement(e.type);const r=document.createElement("div");return r.setAttribute("data-component",e.type),e.props&&Object.entries(e.props).forEach(([i,l])=>{r.setAttribute(`data-prop-${i}`,JSON.stringify(l))}),e.children&&e.children.length>0?this.renderComponents(r,e.children):e.text&&(r.textContent=e.text),r}catch(a){return Bn.error(`Error rendering component ${e.type}:`,a),this.createErrorElement(e.type,a)}}createPlaceholderElement(e){const a=document.createElement("div");return a.className="component-placeholder",a.setAttribute("data-component-type",e),a.innerHTML=`

Component not found: ${e}

- `,n}createErrorElement(e,n){const r=document.createElement("div");return r.className="component-error",r.setAttribute("data-component-type",e),r.innerHTML=` + `,a}createErrorElement(e,a){const r=document.createElement("div");return r.className="component-error",r.setAttribute("data-component-type",e),r.innerHTML=`

Component Error: ${e}

-

${n?.message||"Unknown error"}

+

${a?.message||"Unknown error"}

- `,r}renderErrorState(e,n){const r=n instanceof Error?n.message:String(n),i=n instanceof va?n.code:"UNKNOWN_ERROR";e.innerHTML=` + `,r}renderErrorState(e,a){const r=a instanceof Error?a.message:String(a),i=a instanceof ba?a.code:"UNKNOWN_ERROR";e.innerHTML=`
페이지 새로고침
- `,Gn.error("Error state rendered:",i,r)}getCurrentLayout(){return this.currentLayout}clear(){this.currentLayout=null,this.layoutCache.clear(),Gn.log("Layout data and cache cleared")}}const I_=dt("TranslationContext"),of=I.createContext(null),hc=({children:o,translationEngine:e,translationContext:n})=>{const r=I.useMemo(()=>(l,u)=>{if(u){const d="|"+Object.entries(u).map(([f,g])=>`${f}=${g}`).join("|");return e.translate(l,n,d)}return e.translate(l,n)},[e,n]),i=I.useMemo(()=>({translationEngine:e,translationContext:n,t:r}),[e,n,r]);return ft.jsx(of.Provider,{value:i,children:o})},ty=()=>{const o=I.useContext(of);return o||(I_.warn("TranslationProvider 외부에서 호출되었습니다. 키를 그대로 반환합니다."),{t:e=>e,translationEngine:null,translationContext:null})},Rr=dt("SlotContext"),H_={registerToSlot:()=>{Rr.warn("SlotContext not available: registerToSlot called outside SlotProvider")},unregisterFromSlot:()=>{Rr.warn("SlotContext not available: unregisterFromSlot called outside SlotProvider")},getSlotComponents:()=>[],subscribeToSlot:()=>()=>{},clearAllSlots:()=>{},isEnabled:!1},z_=I.createContext(H_),lf=({children:o})=>{const e=I.useRef(new Map),n=I.useRef(new Map),r=I.useCallback(m=>{const b=n.current.get(m);b&&b.forEach(v=>{try{v()}catch(S){Rr.error(`Slot subscriber error (slotId: ${m}):`,S)}})},[]),i=I.useCallback((m,b,v)=>{if(!m||!b){Rr.warn("registerToSlot: slotId and componentId are required");return}e.current.has(m)||e.current.set(m,new Map);const S=e.current.get(m),_=S.get(b);_&&_.registrationKey===v.registrationKey||(S.set(b,v),Rr.log(`Component registered to slot: ${b} -> ${m} (order: ${v.order})`),r(m))},[r]),l=I.useCallback((m,b)=>{const v=e.current.get(m);v&&v.has(b)&&(v.delete(b),Rr.log(`Component unregistered from slot: ${b} <- ${m}`),v.size===0&&e.current.delete(m),r(m))},[r]),u=I.useCallback(m=>{const b=e.current.get(m);return b?Array.from(b.values()).sort((v,S)=>v.order-S.order):[]},[]),d=I.useCallback((m,b)=>{n.current.has(m)||n.current.set(m,new Set);const v=n.current.get(m);return v.add(b),()=>{v.delete(b),v.size===0&&n.current.delete(m)}},[]),f=I.useCallback(()=>{e.current.forEach((m,b)=>{r(b)}),e.current.clear(),n.current.clear(),Rr.log("All slots cleared")},[r]),g=I.useMemo(()=>({registerToSlot:i,unregisterFromSlot:l,getSlotComponents:u,subscribeToSlot:d,clearAllSlots:f,isEnabled:!0}),[i,l,u,d,f]);return I.useLayoutEffect(()=>{typeof window<"u"&&(window.__slotContextValue=g,Rr.log("SlotContext exposed to window.__slotContextValue"))},[g]),ft.jsx(z_.Provider,{value:g,children:o})};class cf{constructor(){this.notificationCreatedEvent=".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated"}listenForWhisper(e,n){return this.listen(".client-"+e,n)}notification(e){return this.listen(this.notificationCreatedEvent,e)}stopListeningForNotification(e){return this.stopListening(this.notificationCreatedEvent,e)}stopListeningForWhisper(e,n){return this.stopListening(".client-"+e,n)}}class ny{constructor(e){this.namespace=e}format(e){return[".","\\"].includes(e.charAt(0))?e.substring(1):(this.namespace&&(e=this.namespace+"."+e),e.replace(/\./g,"\\"))}setNamespace(e){this.namespace=e}}function j_(o){try{return Reflect.construct(String,[],o),!0}catch{return!1}}class uf extends cf{constructor(e,n,r){super(),this.name=n,this.pusher=e,this.options=r,this.eventFormatter=new ny(this.options.namespace),this.subscribe()}subscribe(){this.subscription=this.pusher.subscribe(this.name)}unsubscribe(){this.pusher.unsubscribe(this.name)}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}listenToAll(e){return this.subscription.bind_global((n,r)=>{if(n.startsWith("pusher:"))return;let i=String(this.options.namespace??"").replace(/\./g,"\\"),l=n.startsWith(i)?n.substring(i.length+1):"."+n;e(l,r)}),this}stopListening(e,n){return n?this.subscription.unbind(this.eventFormatter.format(e),n):this.subscription.unbind(this.eventFormatter.format(e)),this}stopListeningToAll(e){return e?this.subscription.unbind_global(e):this.subscription.unbind_global(),this}subscribed(e){return this.on("pusher:subscription_succeeded",()=>{e()}),this}error(e){return this.on("pusher:subscription_error",n=>{e(n)}),this}on(e,n){return this.subscription.bind(e,n),this}}class ay extends uf{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class U_ extends uf{whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}}class B_ extends ay{here(e){return this.on("pusher:subscription_succeeded",n=>{e(Object.keys(n.members).map(r=>n.members[r]))}),this}joining(e){return this.on("pusher:member_added",n=>{e(n.info)}),this}whisper(e,n){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,n),this}leaving(e){return this.on("pusher:member_removed",n=>{e(n.info)}),this}}class ry extends cf{constructor(e,n,r){super(),this.events={},this.listeners={},this.name=n,this.socket=e,this.options=r,this.eventFormatter=new ny(this.options.namespace),this.subscribe()}subscribe(){this.socket.emit("subscribe",{channel:this.name,auth:this.options.auth||{}})}unsubscribe(){this.unbind(),this.socket.emit("unsubscribe",{channel:this.name,auth:this.options.auth||{}})}listen(e,n){return this.on(this.eventFormatter.format(e),n),this}stopListening(e,n){return this.unbindEvent(this.eventFormatter.format(e),n),this}subscribed(e){return this.on("connect",n=>{e(n)}),this}error(e){return this}on(e,n){return this.listeners[e]=this.listeners[e]||[],this.events[e]||(this.events[e]=(r,i)=>{this.name===r&&this.listeners[e]&&this.listeners[e].forEach(l=>l(i))},this.socket.on(e,this.events[e])),this.listeners[e].push(n),this}unbind(){Object.keys(this.events).forEach(e=>{this.unbindEvent(e)})}unbindEvent(e,n){this.listeners[e]=this.listeners[e]||[],n&&(this.listeners[e]=this.listeners[e].filter(r=>r!==n)),(!n||this.listeners[e].length===0)&&(this.events[e]&&(this.socket.removeListener(e,this.events[e]),delete this.events[e]),delete this.listeners[e])}}class iy extends ry{whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}}class P_ extends iy{here(e){return this.on("presence:subscribed",n=>{e(n.map(r=>r.user_info))}),this}joining(e){return this.on("presence:joining",n=>e(n.user_info)),this}whisper(e,n){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:n}),this}leaving(e){return this.on("presence:leaving",n=>e(n.user_info)),this}}class gc extends cf{subscribe(){}unsubscribe(){}listen(e,n){return this}listenToAll(e){return this}stopListening(e,n){return this}subscribed(e){return this}error(e){return this}on(e,n){return this}}class sy extends gc{whisper(e,n){return this}}class q_ extends gc{whisper(e,n){return this}}class G_ extends sy{here(e){return this}joining(e){return this}whisper(e,n){return this}leaving(e){return this}}const wc=class wc{constructor(e){this.setOptions(e),this.connect()}setOptions(e){this.options={...wc._defaultOptions,...e,broadcaster:e.broadcaster};let n=this.csrfToken();n&&(this.options.auth.headers["X-CSRF-TOKEN"]=n,this.options.userAuthentication.headers["X-CSRF-TOKEN"]=n),n=this.options.bearerToken,n&&(this.options.auth.headers.Authorization="Bearer "+n,this.options.userAuthentication.headers.Authorization="Bearer "+n)}csrfToken(){return typeof window<"u"&&window.Laravel?.csrfToken?window.Laravel.csrfToken:this.options.csrfToken?this.options.csrfToken:typeof document<"u"&&typeof document.querySelector=="function"?document.querySelector('meta[name="csrf-token"]')?.getAttribute("content")??null:null}};wc._defaultOptions={auth:{headers:{}},authEndpoint:"/broadcasting/auth",userAuthentication:{endpoint:"/broadcasting/user-auth",headers:{}},csrfToken:null,bearerToken:null,host:null,key:null,namespace:"App.Events"};let Eo=wc;class pc extends Eo{constructor(){super(...arguments),this.channels={}}connect(){if(typeof this.options.client<"u")this.pusher=this.options.client;else if(this.options.Pusher)this.pusher=new this.options.Pusher(this.options.key,this.options);else if(typeof window<"u"&&typeof window.Pusher<"u")this.pusher=new window.Pusher(this.options.key,this.options);else throw new Error("Pusher client not found. Should be globally available or passed via options.client")}signin(){this.pusher.signin()}listen(e,n,r){return this.channel(e).listen(n,r)}channel(e){return this.channels[e]||(this.channels[e]=new uf(this.pusher,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new ay(this.pusher,"private-"+e,this.options)),this.channels["private-"+e]}encryptedPrivateChannel(e){return this.channels["private-encrypted-"+e]||(this.channels["private-encrypted-"+e]=new U_(this.pusher,"private-encrypted-"+e,this.options)),this.channels["private-encrypted-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new B_(this.pusher,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"private-encrypted-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.pusher.connection.socket_id}connectionStatus(){const e=this.pusher.connection.state;switch(e){case"connected":case"connecting":return e;case"failed":case"unavailable":return"failed";default:return"disconnected"}}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},r=["state_change","connected","disconnected"];return r.forEach(i=>{this.pusher.connection.bind(i,n)}),()=>{r.forEach(i=>{this.pusher.connection.unbind(i,n)})}}disconnect(){this.pusher.disconnect()}}class V_ extends Eo{constructor(){super(...arguments),this.channels={}}connect(){const e=this.getSocketIO();this.socket=e(this.options.host??void 0,this.options),this.socket.io.on("reconnect",()=>{Object.values(this.channels).forEach(n=>{n.subscribe()})})}getSocketIO(){if(typeof this.options.client<"u")return this.options.client;if(typeof window<"u"&&typeof window.io<"u")return window.io;throw new Error("Socket.io client not found. Should be globally available or passed via options.client")}listen(e,n,r){return this.channel(e).listen(n,r)}channel(e){return this.channels[e]||(this.channels[e]=new ry(this.socket,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new iy(this.socket,"private-"+e,this.options)),this.channels["private-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new P_(this.socket,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"presence-"+e].forEach(n=>{this.leaveChannel(n)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.socket.id}connectionStatus(){return this.socket.connected?"connected":this.socket.io._reconnecting?"reconnecting":this.socket.id!==void 0?"disconnected":"connecting"}onConnectionChange(e){const n=()=>{e(this.connectionStatus())},r=["connect","disconnect","connect_error","reconnect_attempt","reconnect","reconnect_error","reconnect_failed"];return r.forEach(i=>{this.socket.on(i,n)}),()=>{r.forEach(i=>{this.socket.off(i,n)})}}disconnect(){this.socket.disconnect()}}class oy extends Eo{constructor(){super(...arguments),this.channels={}}connect(){}listen(e,n,r){return new gc}channel(e){return new gc}privateChannel(e){return new sy}encryptedPrivateChannel(e){return new q_}presenceChannel(e){return new G_}leave(e){}leaveChannel(e){}socketId(){return"fake-socket-id"}connectionStatus(){return"connected"}onConnectionChange(e){return()=>{}}disconnect(){}}class F_{constructor(e){this.options=e,this.connect(),this.options.withoutInterceptors||this.registerInterceptors()}channel(e){return this.connector.channel(e)}connect(){if(this.options.broadcaster==="reverb")this.connector=new pc({...this.options,cluster:""});else if(this.options.broadcaster==="pusher")this.connector=new pc(this.options);else if(this.options.broadcaster==="ably")this.connector=new pc({...this.options,cluster:"",broadcaster:"pusher"});else if(this.options.broadcaster==="socket.io")this.connector=new V_(this.options);else if(this.options.broadcaster==="null")this.connector=new oy(this.options);else if(typeof this.options.broadcaster=="function"&&j_(this.options.broadcaster))this.connector=new this.options.broadcaster(this.options);else throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} is not supported.`)}disconnect(){this.connector.disconnect()}join(e){return this.connector.presenceChannel(e)}leave(e){this.connector.leave(e)}leaveChannel(e){this.connector.leaveChannel(e)}leaveAllChannels(){for(const e in this.connector.channels)this.leaveChannel(e)}listen(e,n,r){return this.connector.listen(e,n,r)}private(e){return this.connector.privateChannel(e)}encryptedPrivate(e){if(this.connectorSupportsEncryptedPrivateChannels(this.connector))return this.connector.encryptedPrivateChannel(e);throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} does not support encrypted private channels.`)}connectorSupportsEncryptedPrivateChannels(e){return e instanceof pc||e instanceof oy}socketId(){return this.connector.socketId()}connectionStatus(){return this.connector.connectionStatus()}registerInterceptors(){typeof Vue<"u"&&Vue?.http&&this.registerVueRequestInterceptor(),typeof axios=="function"&&this.registerAxiosRequestInterceptor(),typeof jQuery=="function"&&this.registerjQueryAjaxSetup(),typeof Turbo=="object"&&this.registerTurboRequestInterceptor()}registerVueRequestInterceptor(){Vue.http.interceptors.push((e,n)=>{this.socketId()&&e.headers.set("X-Socket-ID",this.socketId()),n()})}registerAxiosRequestInterceptor(){axios.interceptors.request.use(e=>(this.socketId()&&(e.headers["X-Socket-Id"]=this.socketId()),e))}registerjQueryAjaxSetup(){typeof jQuery.ajax<"u"&&jQuery.ajaxPrefilter((e,n,r)=>{this.socketId()&&r.setRequestHeader("X-Socket-Id",this.socketId())})}registerTurboRequestInterceptor(){document.addEventListener("turbo:before-fetch-request",e=>{e.detail.fetchOptions.headers["X-Socket-Id"]=this.socketId()})}}var df={exports:{}};var ly;function K_(){return ly||(ly=1,(function(o,e){(function(r,i){o.exports=i()})(self,()=>(()=>{var n={594(u,d){var f=this&&this.__extends||(function(){var M=function(T,D){return M=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(j,H){j.__proto__=H}||function(j,H){for(var P in H)H.hasOwnProperty(P)&&(j[P]=H[P])},M(T,D)};return function(T,D){M(T,D);function j(){this.constructor=T}T.prototype=D===null?Object.create(D):(j.prototype=D.prototype,new j)}})();Object.defineProperty(d,"__esModule",{value:!0});var g=256,m=(function(){function M(T){T===void 0&&(T="="),this._paddingCharacter=T}return M.prototype.encodedLength=function(T){return this._paddingCharacter?(T+2)/3*4|0:(T*8+5)/6|0},M.prototype.encode=function(T){for(var D="",j=0;j>>18&63),D+=this._encodeByte(H>>>12&63),D+=this._encodeByte(H>>>6&63),D+=this._encodeByte(H>>>0&63)}var P=T.length-j;if(P>0){var H=T[j]<<16|(P===2?T[j+1]<<8:0);D+=this._encodeByte(H>>>18&63),D+=this._encodeByte(H>>>12&63),P===2?D+=this._encodeByte(H>>>6&63):D+=this._paddingCharacter||"",D+=this._paddingCharacter||""}return D},M.prototype.maxDecodedLength=function(T){return this._paddingCharacter?T/4*3|0:(T*6+7)/8|0},M.prototype.decodedLength=function(T){return this.maxDecodedLength(T.length-this._getPaddingLength(T))},M.prototype.decode=function(T){if(T.length===0)return new Uint8Array(0);for(var D=this._getPaddingLength(T),j=T.length-D,H=new Uint8Array(this.maxDecodedLength(j)),P=0,X=0,pe=0,_e=0,we=0,Me=0,qe=0;X>>4,H[P++]=we<<4|Me>>>2,H[P++]=Me<<6|qe,pe|=_e&g,pe|=we&g,pe|=Me&g,pe|=qe&g;if(X>>4,pe|=_e&g,pe|=we&g),X>>2,pe|=Me&g),X>>8&6,D+=51-T>>>8&-75,D+=61-T>>>8&-15,D+=62-T>>>8&3,String.fromCharCode(D)},M.prototype._decodeChar=function(T){var D=g;return D+=(42-T&T-44)>>>8&-g+T-43+62,D+=(46-T&T-48)>>>8&-g+T-47+63,D+=(47-T&T-58)>>>8&-g+T-48+52,D+=(64-T&T-91)>>>8&-g+T-65+0,D+=(96-T&T-123)>>>8&-g+T-97+26,D},M.prototype._getPaddingLength=function(T){var D=0;if(this._paddingCharacter){for(var j=T.length-1;j>=0&&T[j]===this._paddingCharacter;j--)D++;if(T.length<4||D>2)throw new Error("Base64Coder: incorrect padding")}return D},M})();d.Coder=m;var b=new m;function v(M){return b.encode(M)}d.encode=v;function S(M){return b.decode(M)}d.decode=S;var _=(function(M){f(T,M);function T(){return M!==null&&M.apply(this,arguments)||this}return T.prototype._encodeByte=function(D){var j=D;return j+=65,j+=25-D>>>8&6,j+=51-D>>>8&-75,j+=61-D>>>8&-13,j+=62-D>>>8&49,String.fromCharCode(j)},T.prototype._decodeChar=function(D){var j=g;return j+=(44-D&D-46)>>>8&-g+D-45+62,j+=(94-D&D-96)>>>8&-g+D-95+63,j+=(47-D&D-58)>>>8&-g+D-48+52,j+=(64-D&D-91)>>>8&-g+D-65+0,j+=(96-D&D-123)>>>8&-g+D-97+26,j},T})(m);d.URLSafeCoder=_;var x=new _;function k(M){return x.encode(M)}d.encodeURLSafe=k;function O(M){return x.decode(M)}d.decodeURLSafe=O,d.encodedLength=function(M){return b.encodedLength(M)},d.maxDecodedLength=function(M){return b.maxDecodedLength(M)},d.decodedLength=function(M){return b.decodedLength(M)}},978(u,d){var f="utf8: invalid source encoding";function g(m){for(var b=[],v=0;v=m.length)throw new Error(f);var x=m[++v];if((x&192)!==128)throw new Error(f);S=(S&31)<<6|x&63,_=128}else if(S<240){if(v>=m.length-1)throw new Error(f);var x=m[++v],k=m[++v];if((x&192)!==128||(k&192)!==128)throw new Error(f);S=(S&15)<<12|(x&63)<<6|k&63,_=2048}else if(S<248){if(v>=m.length-2)throw new Error(f);var x=m[++v],k=m[++v],O=m[++v];if((x&192)!==128||(k&192)!==128||(O&192)!==128)throw new Error(f);S=(S&15)<<18|(x&63)<<12|(k&63)<<6|O&63,_=65536}else throw new Error(f);if(S<_||S>=55296&&S<=57343)throw new Error(f);if(S>=65536){if(S>1114111)throw new Error(f);S-=65536,b.push(String.fromCharCode(55296|S>>10)),S=56320|S&1023}}b.push(String.fromCharCode(S))}return b.join("")}d.D4=g},721(u,d,f){u.exports=f(207).default},207(u,d,f){f.d(d,{default:()=>Ss});class g{constructor(y,w){this.lastId=0,this.prefix=y,this.name=w}create(y){this.lastId++;var w=this.lastId,L=this.prefix+w,N=this.name+"["+w+"]",te=!1,ye=function(){te||(y.apply(null,arguments),te=!0)};return this[w]=ye,{number:w,id:L,name:N,callback:ye}}remove(y){delete this[y.number]}}var m=new g("_pusher_script_","Pusher.ScriptReceivers"),b={VERSION:"8.6.0",PROTOCOL:7,wsPort:80,wssPort:443,wsPath:"",httpHost:"sockjs.pusher.com",httpPort:80,httpsPort:443,httpPath:"/pusher",stats_host:"stats.pusher.com",authEndpoint:"/pusher/auth",authTransport:"ajax",activityTimeout:12e4,pongTimeout:3e4,unavailableTimeout:1e4,userAuthentication:{endpoint:"/pusher/user-auth",transport:"ajax"},channelAuthorization:{endpoint:"/pusher/auth",transport:"ajax"},cdn_http:"http://js.pusher.com",cdn_https:"https://js.pusher.com",dependency_suffix:""};const v=b;class S{constructor(y){this.options=y,this.receivers=y.receivers||m,this.loading={}}load(y,w,L){var N=this;if(N.loading[y]&&N.loading[y].length>0)N.loading[y].push(L);else{N.loading[y]=[L];var te=Be.createScriptRequest(N.getPath(y,w)),ye=N.receivers.create(function(Ne){if(N.receivers.remove(ye),N.loading[y]){var Fe=N.loading[y];delete N.loading[y];for(var lt=function(jt){jt||te.cleanup()},pt=0;pt>>6)+Q(128|y&63):Q(224|y>>>12&15)+Q(128|y>>>6&63)+Q(128|y&63)},ae=function(E){return E.replace(/[^\x00-\x7F]/g,ve)},ge=function(E){var y=[0,2,1][E.length%3],w=E.charCodeAt(0)<<16|(E.length>1?E.charCodeAt(1):0)<<8|(E.length>2?E.charCodeAt(2):0),L=[fe.charAt(w>>>18),fe.charAt(w>>>12&63),y>=2?"=":fe.charAt(w>>>6&63),y>=1?"=":fe.charAt(w&63)];return L.join("")},A=typeof window<"u"&&window.btoa||function(E){return E.replace(/[\s\S]{1,3}/g,ge)};class K{constructor(y,w,L,N){this.clear=w,this.timer=y(()=>{this.timer&&(this.timer=N(this.timer))},L)}isRunning(){return this.timer!==null}ensureAborted(){this.timer&&(this.clear(this.timer),this.timer=null)}}const ue=K;function he(E){window.clearTimeout(E)}function le(E){window.clearInterval(E)}class de extends ue{constructor(y,w){super(setTimeout,he,y,function(L){return w(),null})}}class Oe extends ue{constructor(y,w){super(setInterval,le,y,function(L){return w(),L})}}var He={now(){return Date.now?Date.now():new Date().valueOf()},defer(E){return new de(0,E)},method(E,...y){var w=Array.prototype.slice.call(arguments,1);return function(L){return L[E].apply(L,w.concat(arguments))}}};const Pe=He;function ke(E,...y){for(var w=0;w{window.console&&window.console.log&&window.console.log(y)}}debug(...y){this.log(this.globalLog,y)}warn(...y){this.log(this.globalLogWarn,y)}error(...y){this.log(this.globalLogError,y)}globalLogWarn(y){window.console&&window.console.warn?window.console.warn(y):this.globalLog(y)}globalLogError(y){window.console&&window.console.error?window.console.error(y):this.globalLogWarn(y)}log(y,...w){var L=Ot.apply(this,arguments);Ss.log?Ss.log(L):Ss.logToConsole&&y.bind(this)(L)}}const tt=new sn;var na=function(E,y,w,L,N){(w.headers!==void 0||w.headersProvider!=null)&&tt.warn(`To send headers with the ${L.toString()} request, you must use AJAX, rather than JSONP.`);var te=E.nextAuthCallbackID.toString();E.nextAuthCallbackID++;var ye=E.getDocument(),Ne=ye.createElement("script");E.auth_callbacks[te]=function(pt){N(null,pt)};var Fe="Pusher.auth_callbacks['"+te+"']";Ne.src=w.endpoint+"?callback="+encodeURIComponent(Fe)+"&"+y;var lt=ye.getElementsByTagName("head")[0]||ye.documentElement;lt.insertBefore(Ne,lt.firstChild)};const kn=na;class nr{constructor(y){this.src=y}send(y){var w=this,L="Error loading "+w.src;w.script=document.createElement("script"),w.script.id=y.id,w.script.src=w.src,w.script.type="text/javascript",w.script.charset="UTF-8",w.script.addEventListener?(w.script.onerror=function(){y.callback(L)},w.script.onload=function(){y.callback(null)}):w.script.onreadystatechange=function(){(w.script.readyState==="loaded"||w.script.readyState==="complete")&&y.callback(null)},w.script.async===void 0&&document.attachEvent&&/opera/i.test(navigator.userAgent)?(w.errorScript=document.createElement("script"),w.errorScript.id=y.id+"_error",w.errorScript.text=y.name+"('"+L+"');",w.script.async=w.errorScript.async=!1):w.script.async=!0;var N=document.getElementsByTagName("head")[0];N.insertBefore(w.script,N.firstChild),w.errorScript&&N.insertBefore(w.errorScript,w.script.nextSibling)}cleanup(){this.script&&(this.script.onload=this.script.onerror=null,this.script.onreadystatechange=null),this.script&&this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.errorScript&&this.errorScript.parentNode&&this.errorScript.parentNode.removeChild(this.errorScript),this.script=null,this.errorScript=null}}class _a{constructor(y,w){this.url=y,this.data=w}send(y){if(!this.request){var w=Dr(this.data),L=this.url+"/"+y.number+"?"+w;this.request=Be.createScriptRequest(L),this.request.send(y)}}cleanup(){this.request&&this.request.cleanup()}}var on=function(E,y){return function(w,L){var N="http"+(y?"s":"")+"://",te=N+(E.host||E.options.host)+E.options.path,ye=Be.createJSONPRequest(te,w),Ne=Be.ScriptReceivers.create(function(Fe,lt){m.remove(Ne),ye.cleanup(),lt&<.host&&(E.host=lt.host),L&&L(Fe,lt)});ye.send(Ne)}},Dn={name:"jsonp",getAgent:on};const q=Dn;function ie(E,y,w){var L=E+(y.useTLS?"s":""),N=y.useTLS?y.hostTLS:y.hostNonTLS;return L+"://"+N+w}function oe(E,y){var w="/app/"+E,L="?protocol="+v.PROTOCOL+"&client=js&version="+v.VERSION+(y?"&"+y:"");return w+L}var Z={getInitial:function(E,y){var w=(y.httpPath||"")+oe(E,"flash=false");return ie("ws",y,w)}},be={getInitial:function(E,y){var w=(y.httpPath||"/pusher")+oe(E);return ie("http",y,w)}},me={getInitial:function(E,y){return ie("http",y,y.httpPath||"/pusher")},getPath:function(E,y){return oe(E)}};class Te{constructor(){this._callbacks={}}get(y){return this._callbacks[xe(y)]}add(y,w,L){var N=xe(y);this._callbacks[N]=this._callbacks[N]||[],this._callbacks[N].push({fn:w,context:L})}remove(y,w,L){if(!y&&!w&&!L){this._callbacks={};return}var N=y?[xe(y)]:vt(this._callbacks);w||L?this.removeCallback(N,w,L):this.removeAllCallbacks(N)}removeCallback(y,w,L){Ee(y,function(N){this._callbacks[N]=qt(this._callbacks[N]||[],function(te){return w&&w!==te.fn||L&&L!==te.context}),this._callbacks[N].length===0&&delete this._callbacks[N]},this)}removeAllCallbacks(y){Ee(y,function(w){delete this._callbacks[w]},this)}}function xe(E){return"_"+E}class je{constructor(y){this.callbacks=new Te,this.global_callbacks=[],this.failThrough=y}bind(y,w,L){return this.callbacks.add(y,w,L),this}bind_global(y){return this.global_callbacks.push(y),this}unbind(y,w,L){return this.callbacks.remove(y,w,L),this}unbind_global(y){return y?(this.global_callbacks=qt(this.global_callbacks||[],w=>w!==y),this):(this.global_callbacks=[],this)}unbind_all(){return this.unbind(),this.unbind_global(),this}emit(y,w,L){for(var N=0;N0)for(var N=0;N{this.onError(w),this.changeState("closed")}),!1}return this.bindListeners(),tt.debug("Connecting",{transport:this.name,url:y}),this.changeState("connecting"),!0}close(){return this.socket?(this.socket.close(),!0):!1}send(y){return this.state==="open"?(Pe.defer(()=>{this.socket&&this.socket.send(y)}),!0):!1}ping(){this.state==="open"&&this.supportsPing()&&this.socket.ping()}onOpen(){this.hooks.beforeOpen&&this.hooks.beforeOpen(this.socket,this.hooks.urls.getPath(this.key,this.options)),this.changeState("open"),this.socket.onopen=void 0}onError(y){this.emit("error",{type:"WebSocketError",error:y}),this.timeline.error(this.buildTimelineMessage({error:y.toString()}))}onClose(y){y?this.changeState("closed",{code:y.code,reason:y.reason,wasClean:y.wasClean}):this.changeState("closed"),this.unbindListeners(),this.socket=void 0}onMessage(y){this.emit("message",y)}onActivity(){this.emit("activity")}bindListeners(){this.socket.onopen=()=>{this.onOpen()},this.socket.onerror=y=>{this.onError(y)},this.socket.onclose=y=>{this.onClose(y)},this.socket.onmessage=y=>{this.onMessage(y)},this.supportsPing()&&(this.socket.onactivity=()=>{this.onActivity()})}unbindListeners(){this.socket&&(this.socket.onopen=void 0,this.socket.onerror=void 0,this.socket.onclose=void 0,this.socket.onmessage=void 0,this.supportsPing()&&(this.socket.onactivity=void 0))}changeState(y,w){this.state=y,this.timeline.info(this.buildTimelineMessage({state:y,params:w})),this.emit(y,w)}buildTimelineMessage(y){return ke({cid:this.id},y)}}class $e{constructor(y){this.hooks=y}isSupported(y){return this.hooks.isSupported(y)}createConnection(y,w,L,N){return new ze(this.hooks,y,w,L,N)}}var De=new $e({urls:Z,handlesActivityChecks:!1,supportsPing:!1,isInitialized:function(){return!!Be.getWebSocketAPI()},isSupported:function(){return!!Be.getWebSocketAPI()},getSocket:function(E){return Be.createWebSocket(E)}}),Ye={urls:be,handlesActivityChecks:!1,supportsPing:!0,isInitialized:function(){return!0}},Ke=ke({getSocket:function(E){return Be.HTTPFactory.createStreamingSocket(E)}},Ye),Ae=ke({getSocket:function(E){return Be.HTTPFactory.createPollingSocket(E)}},Ye),Re={isSupported:function(){return Be.isXHRSupported()}},Ue=new $e(ke({},Ke,Re)),ot=new $e(ke({},Ae,Re)),At={ws:De,xhr_streaming:Ue,xhr_polling:ot};const Qt=At;var Kt=new $e({file:"sockjs",urls:me,handlesActivityChecks:!0,supportsPing:!1,isSupported:function(){return!0},isInitialized:function(){return window.SockJS!==void 0},getSocket:function(E,y){return new window.SockJS(E,null,{js_path:x.getPath("sockjs",{useTLS:y.useTLS}),ignore_null_origin:y.ignoreNullOrigin})},beforeOpen:function(E,y){E.send(JSON.stringify({path:y}))}}),Fn={isSupported:function(E){var y=Be.isXDRSupported(E.useTLS);return y}},Nn=new $e(ke({},Ke,Fn)),ln=new $e(ke({},Ae,Fn));Qt.xdr_streaming=Nn,Qt.xdr_polling=ln,Qt.sockjs=Kt;const xn=Qt;class Or extends je{constructor(){super();var y=this;typeof window<"u"&&window.addEventListener!==void 0&&(window.addEventListener("online",function(){y.emit("online")},!1),window.addEventListener("offline",function(){y.emit("offline")},!1))}isOnline(){return window.navigator.onLine===void 0?!0:window.navigator.onLine}}var Lo=new Or;class bf{constructor(y,w,L){this.manager=y,this.transport=w,this.minPingDelay=L.minPingDelay,this.maxPingDelay=L.maxPingDelay,this.pingDelay=void 0}createConnection(y,w,L,N){N=ke({},N,{activityTimeout:this.pingDelay});var te=this.transport.createConnection(y,w,L,N),ye=null,Ne=function(){te.unbind("open",Ne),te.bind("closed",Fe),ye=Pe.now()},Fe=lt=>{if(te.unbind("closed",Fe),lt.code===1002||lt.code===1003)this.manager.reportDeath();else if(!lt.wasClean&&ye){var pt=Pe.now()-ye;pt<2*this.maxPingDelay&&(this.manager.reportDeath(),this.pingDelay=Math.max(pt/2,this.minPingDelay))}};return te.bind("open",Ne),te}isSupported(y){return this.manager.isAlive()&&this.transport.isSupported(y)}}const Ec={decodeMessage:function(E){try{var y=JSON.parse(E.data),w=y.data;if(typeof w=="string")try{w=JSON.parse(y.data)}catch{}var L={event:y.event,channel:y.channel,data:w};return y.user_id&&(L.user_id=y.user_id),L}catch(N){throw{type:"MessageParseError",error:N,data:E.data}}},encodeMessage:function(E){return JSON.stringify(E)},processHandshake:function(E){var y=Ec.decodeMessage(E);if(y.event==="pusher:connection_established"){if(!y.data.activity_timeout)throw"No activity timeout specified in handshake";return{action:"connected",id:y.data.socket_id,activityTimeout:y.data.activity_timeout*1e3}}else{if(y.event==="pusher:error")return{action:this.getCloseAction(y.data),error:this.getCloseError(y.data)};throw"Invalid handshake"}},getCloseAction:function(E){return E.code<4e3?E.code>=1002&&E.code<=1004?"backoff":null:E.code===4e3?"tls_only":E.code<4100?"refused":E.code<4200?"backoff":E.code<4300?"retry":"refused"},getCloseError:function(E){return E.code!==1e3&&E.code!==1001?{type:"PusherError",data:{code:E.code,message:E.reason||E.message}}:null}},Pa=Ec;class vi extends je{constructor(y,w){super(),this.id=y,this.transport=w,this.activityTimeout=w.activityTimeout,this.bindListeners()}handlesActivityChecks(){return this.transport.handlesActivityChecks()}send(y){return this.transport.send(y)}send_event(y,w,L){var N={event:y,data:w};return L&&(N.channel=L),tt.debug("Event sent",N),this.send(Pa.encodeMessage(N))}ping(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})}close(){this.transport.close()}bindListeners(){var y={message:L=>{var N;try{N=Pa.decodeMessage(L)}catch(te){this.emit("error",{type:"MessageParseError",error:te,data:L.data})}if(N!==void 0){switch(tt.debug("Event recd",N),N.event){case"pusher:error":this.emit("error",{type:"PusherError",data:N.data});break;case"pusher:ping":this.emit("ping");break;case"pusher:pong":this.emit("pong");break}this.emit("message",N)}},activity:()=>{this.emit("activity")},error:L=>{this.emit("error",L)},closed:L=>{w(),L&&L.code&&this.handleCloseEvent(L),this.transport=null,this.emit("closed")}},w=()=>{Ct(y,(L,N)=>{this.transport.unbind(N,L)})};Ct(y,(L,N)=>{this.transport.bind(N,L)})}handleCloseEvent(y){var w=Pa.getCloseAction(y),L=Pa.getCloseError(y);L&&this.emit("error",L),w&&this.emit(w,{action:w,error:L})}}class Mo{constructor(y,w){this.transport=y,this.callback=w,this.bindListeners()}close(){this.unbindListeners(),this.transport.close()}bindListeners(){this.onMessage=y=>{this.unbindListeners();var w;try{w=Pa.processHandshake(y)}catch(L){this.finish("error",{error:L}),this.transport.close();return}w.action==="connected"?this.finish("connected",{connection:new vi(w.id,this.transport),activityTimeout:w.activityTimeout}):(this.finish(w.action,{error:w.error}),this.transport.close())},this.onClosed=y=>{this.unbindListeners();var w=Pa.getCloseAction(y)||"backoff",L=Pa.getCloseError(y);this.finish(w,{error:L})},this.transport.bind("message",this.onMessage),this.transport.bind("closed",this.onClosed)}unbindListeners(){this.transport.unbind("message",this.onMessage),this.transport.unbind("closed",this.onClosed)}finish(y,w){this.callback(ke({transport:this.transport,action:y},w))}}class Lr{constructor(y,w){this.timeline=y,this.options=w||{}}send(y,w){this.timeline.isEmpty()||this.timeline.send(Be.TimelineTransport.getAgent(this,y),w)}}class qa extends je{constructor(y,w){super(function(L,N){tt.debug("No callbacks on "+y+" for "+L)}),this.name=y,this.pusher=w,this.subscribed=!1,this.subscriptionPending=!1,this.subscriptionCancelled=!1}authorize(y,w){return w(null,{auth:""})}trigger(y,w){if(y.indexOf("client-")!==0)throw new D("Event '"+y+"' does not start with 'client-'");if(!this.subscribed){var L=M.buildLogSuffix("triggeringClientEvents");tt.warn(`Client event triggered before channel 'subscription_succeeded' event . ${L}`)}return this.pusher.send_event(y,w,this.name)}disconnect(){this.subscribed=!1,this.subscriptionPending=!1}handleEvent(y){var w=y.event,L=y.data;if(w==="pusher_internal:subscription_succeeded")this.handleSubscriptionSucceededEvent(y);else if(w==="pusher_internal:subscription_count")this.handleSubscriptionCountEvent(y);else if(w.indexOf("pusher_internal:")!==0){var N={};this.emit(w,L,N)}}handleSubscriptionSucceededEvent(y){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit("pusher:subscription_succeeded",y.data)}handleSubscriptionCountEvent(y){y.data.subscription_count&&(this.subscriptionCount=y.data.subscription_count),this.emit("pusher:subscription_count",y.data)}subscribe(){this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,(y,w)=>{y?(this.subscriptionPending=!1,tt.error(y.toString()),this.emit("pusher:subscription_error",Object.assign({},{type:"AuthError",error:y.message},y instanceof Me?{status:y.status}:{}))):this.pusher.send_event("pusher:subscribe",{auth:w.auth,channel_data:w.channel_data,channel:this.name})}))}unsubscribe(){this.subscribed=!1,this.pusher.send_event("pusher:unsubscribe",{channel:this.name})}cancelSubscription(){this.subscriptionCancelled=!0}reinstateSubscription(){this.subscriptionCancelled=!1}}class ar extends qa{authorize(y,w){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:y},w)}}class Mr{constructor(){this.reset()}get(y){return Object.prototype.hasOwnProperty.call(this.members,y)?{id:y,info:this.members[y]}:null}each(y){Ct(this.members,(w,L)=>{y(this.get(L))})}setMyID(y){this.myID=y}onSubscription(y){this.members=y.presence.hash,this.count=y.presence.count,this.me=this.get(this.myID)}addMember(y){return this.get(y.user_id)===null&&this.count++,this.members[y.user_id]=y.user_info,this.get(y.user_id)}removeMember(y){var w=this.get(y.user_id);return w&&(delete this.members[y.user_id],this.count--),w}reset(){this.members={},this.count=0,this.myID=null,this.me=null}}var Zt=function(E,y,w,L){function N(te){return te instanceof w?te:new w(function(ye){ye(te)})}return new(w||(w=Promise))(function(te,ye){function Ne(pt){try{lt(L.next(pt))}catch(jt){ye(jt)}}function Fe(pt){try{lt(L.throw(pt))}catch(jt){ye(jt)}}function lt(pt){pt.done?te(pt.value):N(pt.value).then(Ne,Fe)}lt((L=L.apply(E,y||[])).next())})};class _c extends ar{constructor(y,w){super(y,w),this.members=new Mr}authorize(y,w){super.authorize(y,(L,N)=>Zt(this,void 0,void 0,function*(){if(!L)if(N=N,N.channel_data!=null){var te=JSON.parse(N.channel_data);this.members.setMyID(te.user_id)}else if(yield this.pusher.user.signinDonePromise,this.pusher.user.user_data!=null)this.members.setMyID(this.pusher.user.user_data.id);else{let ye=M.buildLogSuffix("authorizationEndpoint");tt.error(`Invalid auth response for channel '${this.name}', expected 'channel_data' field. ${ye}, or the user should be signed in.`),w("Invalid auth response");return}w(L,N)}))}handleEvent(y){var w=y.event;if(w.indexOf("pusher_internal:")===0)this.handleInternalEvent(y);else{var L=y.data,N={};y.user_id&&(N.user_id=y.user_id),this.emit(w,L,N)}}handleInternalEvent(y){var w=y.event,L=y.data;switch(w){case"pusher_internal:subscription_succeeded":this.handleSubscriptionSucceededEvent(y);break;case"pusher_internal:subscription_count":this.handleSubscriptionCountEvent(y);break;case"pusher_internal:member_added":var N=this.members.addMember(L);this.emit("pusher:member_added",N);break;case"pusher_internal:member_removed":var te=this.members.removeMember(L);te&&this.emit("pusher:member_removed",te);break}}handleSubscriptionSucceededEvent(y){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):(this.members.onSubscription(y.data),this.emit("pusher:subscription_succeeded",this.members))}disconnect(){this.members.reset(),super.disconnect()}}var Ac=f(978),Aa=f(594);class $r extends ar{constructor(y,w,L){super(y,w),this.key=null,this.nacl=L}authorize(y,w){super.authorize(y,(L,N)=>{if(L){w(L,N);return}let te=N.shared_secret;if(!te){w(new Error(`No shared_secret key in auth payload for encrypted channel: ${this.name}`),null);return}this.key=(0,Aa.decode)(te),delete N.shared_secret,w(null,N)})}trigger(y,w){throw new pe("Client events are not currently supported for encrypted channels")}handleEvent(y){var w=y.event,L=y.data;if(w.indexOf("pusher_internal:")===0||w.indexOf("pusher:")===0){super.handleEvent(y);return}this.handleEncryptedEvent(w,L)}handleEncryptedEvent(y,w){if(!this.key){tt.debug("Received encrypted event before key has been retrieved from the authEndpoint");return}if(!w.ciphertext||!w.nonce){tt.error("Unexpected format for encrypted event, expected object with `ciphertext` and `nonce` fields, got: "+w);return}let L=(0,Aa.decode)(w.ciphertext);if(L.length{if(ye){tt.error(`Failed to make a request to the authEndpoint: ${Ne}. Unable to fetch new key, so dropping encrypted event`);return}if(te=this.nacl.secretbox.open(L,N,this.key),te===null){tt.error("Failed to decrypt event with new key. Dropping encrypted event");return}this.emit(y,this.getDataToEmit(te))});return}this.emit(y,this.getDataToEmit(te))}getDataToEmit(y){let w=(0,Ac.D4)(y);try{return JSON.parse(w)}catch{return w}}}class vf extends je{constructor(y,w){super(),this.state="initialized",this.connection=null,this.key=y,this.options=w,this.timeline=this.options.timeline,this.usingTLS=this.options.useTLS,this.errorCallbacks=this.buildErrorCallbacks(),this.connectionCallbacks=this.buildConnectionCallbacks(this.errorCallbacks),this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var L=Be.getNetwork();L.bind("online",()=>{this.timeline.info({netinfo:"online"}),(this.state==="connecting"||this.state==="unavailable")&&this.retryIn(0)}),L.bind("offline",()=>{this.timeline.info({netinfo:"offline"}),this.connection&&this.sendActivityCheck()}),this.updateStrategy()}switchCluster(y){this.key=y,this.updateStrategy(),this.retryIn(0)}connect(){if(!(this.connection||this.runner)){if(!this.strategy.isSupported()){this.updateState("failed");return}this.updateState("connecting"),this.startConnecting(),this.setUnavailableTimer()}}send(y){return this.connection?this.connection.send(y):!1}send_event(y,w,L){return this.connection?this.connection.send_event(y,w,L):!1}disconnect(){this.disconnectInternally(),this.updateState("disconnected")}isUsingTLS(){return this.usingTLS}startConnecting(){var y=(w,L)=>{w?this.runner=this.strategy.connect(0,y):L.action==="error"?(this.emit("error",{type:"HandshakeError",error:L.error}),this.timeline.error({handshakeError:L.error})):(this.abortConnecting(),this.handshakeCallbacks[L.action](L))};this.runner=this.strategy.connect(0,y)}abortConnecting(){this.runner&&(this.runner.abort(),this.runner=null)}disconnectInternally(){if(this.abortConnecting(),this.clearRetryTimer(),this.clearUnavailableTimer(),this.connection){var y=this.abandonConnection();y.close()}}updateStrategy(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,useTLS:this.usingTLS})}retryIn(y){this.timeline.info({action:"retry",delay:y}),y>0&&this.emit("connecting_in",Math.round(y/1e3)),this.retryTimer=new de(y||0,()=>{this.disconnectInternally(),this.connect()})}clearRetryTimer(){this.retryTimer&&(this.retryTimer.ensureAborted(),this.retryTimer=null)}setUnavailableTimer(){this.unavailableTimer=new de(this.options.unavailableTimeout,()=>{this.updateState("unavailable")})}clearUnavailableTimer(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()}sendActivityCheck(){this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new de(this.options.pongTimeout,()=>{this.timeline.error({pong_timed_out:this.options.pongTimeout}),this.retryIn(0)})}resetActivityCheck(){this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new de(this.activityTimeout,()=>{this.sendActivityCheck()}))}stopActivityCheck(){this.activityTimer&&this.activityTimer.ensureAborted()}buildConnectionCallbacks(y){return ke({},y,{message:w=>{this.resetActivityCheck(),this.emit("message",w)},ping:()=>{this.send_event("pusher:pong",{})},activity:()=>{this.resetActivityCheck()},error:w=>{this.emit("error",w)},closed:()=>{this.abandonConnection(),this.shouldRetry()&&this.retryIn(1e3)}})}buildHandshakeCallbacks(y){return ke({},y,{connected:w=>{this.activityTimeout=Math.min(this.options.activityTimeout,w.activityTimeout,w.connection.activityTimeout||1/0),this.clearUnavailableTimer(),this.setConnection(w.connection),this.socket_id=this.connection.id,this.updateState("connected",{socket_id:this.socket_id})}})}buildErrorCallbacks(){let y=w=>L=>{L.error&&this.emit("error",{type:"WebSocketError",error:L.error}),w(L)};return{tls_only:y(()=>{this.usingTLS=!0,this.updateStrategy(),this.retryIn(0)}),refused:y(()=>{this.disconnect()}),backoff:y(()=>{this.retryIn(1e3)}),retry:y(()=>{this.retryIn(0)})}}setConnection(y){this.connection=y;for(var w in this.connectionCallbacks)this.connection.bind(w,this.connectionCallbacks[w]);this.resetActivityCheck()}abandonConnection(){if(this.connection){this.stopActivityCheck();for(var y in this.connectionCallbacks)this.connection.unbind(y,this.connectionCallbacks[y]);var w=this.connection;return this.connection=null,w}}updateState(y,w){var L=this.state;if(this.state=y,L!==y){var N=y;N==="connected"&&(N+=" with new socket ID "+w.socket_id),tt.debug("State changed",L+" -> "+N),this.timeline.info({state:y,params:w}),this.emit("state_change",{previous:L,current:y}),this.emit(y,w)}}shouldRetry(){return this.state==="connecting"||this.state==="connected"}}class xc{constructor(){this.channels={}}add(y,w){return this.channels[y]||(this.channels[y]=Tc(y,w)),this.channels[y]}all(){return kt(this.channels)}find(y){return this.channels[y]}remove(y){var w=this.channels[y];return delete this.channels[y],w}disconnect(){Ct(this.channels,function(y){y.disconnect()})}}function Tc(E,y){if(E.indexOf("private-encrypted-")===0){if(y.config.nacl)return In.createEncryptedChannel(E,y,y.config.nacl);let w="Tried to subscribe to a private-encrypted- channel but no nacl implementation available",L=M.buildLogSuffix("encryptedChannelSupport");throw new pe(`${w}. ${L}`)}else{if(E.indexOf("private-")===0)return In.createPrivateChannel(E,y);if(E.indexOf("presence-")===0)return In.createPresenceChannel(E,y);if(E.indexOf("#")===0)throw new j('Cannot create a channel with name "'+E+'".');return In.createChannel(E,y)}}var Sf={createChannels(){return new xc},createConnectionManager(E,y){return new vf(E,y)},createChannel(E,y){return new qa(E,y)},createPrivateChannel(E,y){return new ar(E,y)},createPresenceChannel(E,y){return new _c(E,y)},createEncryptedChannel(E,y,w){return new $r(E,y,w)},createTimelineSender(E,y){return new Lr(E,y)},createHandshake(E,y){return new Mo(E,y)},createAssistantToTheTransportManager(E,y,w){return new bf(E,y,w)}};const In=Sf;class Si{constructor(y){this.options=y||{},this.livesLeft=this.options.lives||1/0}getAssistant(y){return In.createAssistantToTheTransportManager(this,y,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})}isAlive(){return this.livesLeft>0}reportDeath(){this.livesLeft-=1}}class mn{constructor(y,w){this.strategies=y,this.loop=!!w.loop,this.failFast=!!w.failFast,this.timeout=w.timeout,this.timeoutLimit=w.timeoutLimit}isSupported(){return hn(this.strategies,Pe.method("isSupported"))}connect(y,w){var L=this.strategies,N=0,te=this.timeout,ye=null,Ne=(Fe,lt)=>{lt?w(null,lt):(N=N+1,this.loop&&(N=N%L.length),N0&&(te=new de(L.timeout,function(){ye.abort(),N(!0)})),ye=y.connect(w,function(Ne,Fe){Ne&&te&&te.isRunning()&&!L.failFast||(te&&te.ensureAborted(),N(Ne,Fe))}),{abort:function(){te&&te.ensureAborted(),ye.abort()},forceMinPriority:function(Ne){ye.forceMinPriority(Ne)}}}}class Tn{constructor(y){this.strategies=y}isSupported(){return hn(this.strategies,Pe.method("isSupported"))}connect(y,w){return Rc(this.strategies,y,function(L,N){return function(te,ye){if(N[L].error=te,te){wf(N)&&w(!0);return}Ee(N,function(Ne){Ne.forceMinPriority(ye.transport.priority)}),w(null,ye)}})}}function Rc(E,y,w){var L=Nt(E,function(N,te,ye,Ne){return N.connect(y,w(te,Ne))});return{abort:function(){Ee(L,$o)},forceMinPriority:function(N){Ee(L,function(te){te.forceMinPriority(N)})}}}function wf(E){return Ba(E,function(y){return!!y.error})}function $o(E){!E.error&&!E.aborted&&(E.abort(),E.aborted=!0)}class kc{constructor(y,w,L){this.strategy=y,this.transports=w,this.ttl=L.ttl||18e5,this.usingTLS=L.useTLS,this.timeline=L.timeline}isSupported(){return this.strategy.isSupported()}connect(y,w){var L=this.usingTLS,N=Cf(L),te=N&&N.cacheSkipCount?N.cacheSkipCount:0,ye=[this.strategy];if(N&&N.timestamp+this.ttl>=Pe.now()){var Ne=this.transports[N.transport];Ne&&(["ws","wss"].includes(N.transport)||te>3?(this.timeline.info({cached:!0,transport:N.transport,latency:N.latency}),ye.push(new mn([Ne],{timeout:N.latency*2+1e3,failFast:!0}))):te++)}var Fe=Pe.now(),lt=ye.pop().connect(y,function pt(jt,ka){jt?(ps(L),ye.length>0?(Fe=Pe.now(),lt=ye.pop().connect(y,pt)):w(jt)):(Hn(L,ka.transport.name,Pe.now()-Fe,te),w(null,ka))});return{abort:function(){lt.abort()},forceMinPriority:function(pt){y=pt,lt&<.forceMinPriority(pt)}}}}function Nr(E){return"pusherTransport"+(E?"TLS":"NonTLS")}function Cf(E){var y=Be.getLocalStorage();if(y)try{var w=y[Nr(E)];if(w)return JSON.parse(w)}catch{ps(E)}return null}function Hn(E,y,w,L){var N=Be.getLocalStorage();if(N)try{N[Nr(E)]=An({timestamp:Pe.now(),transport:y,latency:w,cacheSkipCount:L})}catch{}}function ps(E){var y=Be.getLocalStorage();if(y)try{delete y[Nr(E)]}catch{}}class wi{constructor(y,{delay:w}){this.strategy=y,this.options={delay:w}}isSupported(){return this.strategy.isSupported()}connect(y,w){var L=this.strategy,N,te=new de(this.options.delay,function(){N=L.connect(y,w)});return{abort:function(){te.ensureAborted(),N&&N.abort()},forceMinPriority:function(ye){y=ye,N&&N.forceMinPriority(ye)}}}}class rr{constructor(y,w,L){this.test=y,this.trueBranch=w,this.falseBranch=L}isSupported(){var y=this.test()?this.trueBranch:this.falseBranch;return y.isSupported()}connect(y,w){var L=this.test()?this.trueBranch:this.falseBranch;return L.connect(y,w)}}class Ir{constructor(y){this.strategy=y}isSupported(){return this.strategy.isSupported()}connect(y,w){var L=this.strategy.connect(y,function(N,te){te&&L.abort(),w(N,te)});return L}}function Hr(E){return function(){return E.isSupported()}}var Dc=function(E,y,w){var L={};function N(Wo,Gc,Vc,qf,Gf){var Fc=w(E,Wo,Gc,Vc,qf,Gf);return L[Wo]=Fc,Fc}var te=Object.assign({},y,{hostNonTLS:E.wsHost+":"+E.wsPort,hostTLS:E.wsHost+":"+E.wssPort,httpPath:E.wsPath}),ye=Object.assign({},te,{useTLS:!0}),Ne=Object.assign({},y,{hostNonTLS:E.httpHost+":"+E.httpPort,hostTLS:E.httpHost+":"+E.httpsPort,httpPath:E.httpPath}),Fe={loop:!0,timeout:15e3,timeoutLimit:6e4},lt=new Si({minPingDelay:1e4,maxPingDelay:E.activityTimeout}),pt=new Si({lives:2,minPingDelay:1e4,maxPingDelay:E.activityTimeout}),jt=N("ws","ws",3,te,lt),ka=N("wss","ws",3,ye,lt),Uf=N("sockjs","sockjs",1,Ne),Pc=N("xhr_streaming","xhr_streaming",1,Ne,pt),Bf=N("xdr_streaming","xdr_streaming",1,Ne,pt),Vo=N("xhr_polling","xhr_polling",1,Ne),qc=N("xdr_polling","xdr_polling",1,Ne),Ur=new mn([jt],Fe),Ti=new mn([ka],Fe),Pf=new mn([Uf],Fe),Ri=new mn([new rr(Hr(Pc),Pc,Bf)],Fe),Fo=new mn([new rr(Hr(Vo),Vo,qc)],Fe),Ko=new mn([new rr(Hr(Ri),new Tn([Ri,new wi(Fo,{delay:4e3})]),Fo)],Fe),ki=new rr(Hr(Ko),Ko,Pf),Di;return y.useTLS?Di=new Tn([Ur,new wi(ki,{delay:2e3})]):Di=new Tn([Ur,new wi(Ti,{delay:2e3}),new wi(ki,{delay:5e3})]),new kc(new Ir(new rr(Hr(jt),Di,ki)),L,{ttl:18e5,timeline:y.timeline,useTLS:y.useTLS})};const zr=Dc;function Ef(){var E=this;E.timeline.info(E.buildTimelineMessage({transport:E.name+(E.options.useTLS?"s":"")})),E.hooks.isInitialized()?E.changeState("initialized"):E.hooks.file?(E.changeState("initializing"),x.load(E.hooks.file,{useTLS:E.options.useTLS},function(y,w){E.hooks.isInitialized()?(E.changeState("initialized"),w(!0)):(y&&E.onError(y),E.onClose(),w(!1))})):E.onClose()}var Oc={getRequest:function(E){var y=new window.XDomainRequest;return y.ontimeout=function(){E.emit("error",new H),E.close()},y.onerror=function(w){E.emit("error",w),E.close()},y.onprogress=function(){y.responseText&&y.responseText.length>0&&E.onChunk(200,y.responseText)},y.onload=function(){y.responseText&&y.responseText.length>0&&E.onChunk(200,y.responseText),E.emit("finished",200),E.close()},y},abortRequest:function(E){E.ontimeout=E.onerror=E.onprogress=E.onload=null,E.abort()}};const Lc=Oc,No=256*1024;class _f extends je{constructor(y,w,L){super(),this.hooks=y,this.method=w,this.url=L}start(y){this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=()=>{this.close()},Be.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.send(y)}close(){this.unloader&&(Be.removeUnloadListener(this.unloader),this.unloader=null),this.xhr&&(this.hooks.abortRequest(this.xhr),this.xhr=null)}onChunk(y,w){for(;;){var L=this.advanceBuffer(w);if(L)this.emit("chunk",{status:y,data:L});else break}this.isBufferTooLong(w)&&this.emit("buffer_too_long")}advanceBuffer(y){var w=y.slice(this.position),L=w.indexOf(` -`);return L!==-1?(this.position+=L+1,w.slice(0,L)):null}isBufferTooLong(y){return this.position===y.length&&y.length>No}}var Io;(function(E){E[E.CONNECTING=0]="CONNECTING",E[E.OPEN=1]="OPEN",E[E.CLOSED=3]="CLOSED"})(Io||(Io={}));const aa=Io;var xa=1;class Ho{constructor(y,w){this.hooks=y,this.session=ms(1e3)+"/"+$c(8),this.location=zo(w),this.readyState=aa.CONNECTING,this.openStream()}send(y){return this.sendRaw(JSON.stringify([y]))}ping(){this.hooks.sendHeartbeat(this)}close(y,w){this.onClose(y,w,!0)}sendRaw(y){if(this.readyState===aa.OPEN)try{return Be.createSocketRequest("POST",ir(jr(this.location,this.session))).start(y),!0}catch{return!1}else return!1}reconnect(){this.closeStream(),this.openStream()}onClose(y,w,L){this.closeStream(),this.readyState=aa.CLOSED,this.onclose&&this.onclose({code:y,reason:w,wasClean:L})}onChunk(y){if(y.status===200){this.readyState===aa.OPEN&&this.onActivity();var w,L=y.data.slice(0,1);switch(L){case"o":w=JSON.parse(y.data.slice(1)||"{}"),this.onOpen(w);break;case"a":w=JSON.parse(y.data.slice(1)||"[]");for(var N=0;N{this.onChunk(y)}),this.stream.bind("finished",y=>{this.hooks.onFinished(this,y)}),this.stream.bind("buffer_too_long",()=>{this.reconnect()});try{this.stream.start()}catch(y){Pe.defer(()=>{this.onError(y),this.onClose(1006,"Could not start streaming",!1)})}}closeStream(){this.stream&&(this.stream.unbind_all(),this.stream.close(),this.stream=null)}}function zo(E){var y=/([^\?]*)\/*(\??.*)/.exec(E);return{base:y[1],queryString:y[2]}}function jr(E,y){return E.base+"/"+y+"/xhr_send"}function ir(E){var y=E.indexOf("?")===-1?"?":"&";return E+y+"t="+ +new Date+"&n="+xa++}function Mc(E,y){var w=/(https?:\/\/)([^\/:]+)((\/|:)?.*)/.exec(E);return w[1]+y+w[3]}function ms(E){return Be.randomInt(E)}function $c(E){for(var y=[],w=0;w0&&E.onChunk(w.status,w.responseText);break;case 4:w.responseText&&w.responseText.length>0&&E.onChunk(w.status,w.responseText),E.emit("finished",w.status),E.close();break}},w},abortRequest:function(E){E.onreadystatechange=null,E.abort()}};const ys=Uo;var Nc={createStreamingSocket(E){return this.createSocket(jo,E)},createPollingSocket(E){return this.createSocket(Ga,E)},createSocket(E,y){return new Ci(E,y)},createXHR(E,y){return this.createRequest(ys,E,y)},createRequest(E,y,w){return new _f(E,y,w)}};const _i=Nc;_i.createXDR=function(E,y){return this.createRequest(Lc,E,y)};var Ic={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:m,DependenciesReceivers:_,getDefaultStrategy:zr,Transports:xn,transportConnectionInitializer:Ef,HTTPFactory:_i,TimelineTransport:q,getXHRAPI(){return window.XMLHttpRequest},getWebSocketAPI(){return window.WebSocket||window.MozWebSocket},setup(E){if(typeof window<"u"){window.Pusher=E;var y=()=>{this.onDocumentBody(E.ready)};window.JSON?y():x.load("json2",{},y)}},getDocument(){return document},getProtocol(){return this.getDocument().location.protocol},getAuthorizers(){return{ajax:Je,jsonp:kn}},onDocumentBody(E){document.body?E():setTimeout(()=>{this.onDocumentBody(E)},0)},createJSONPRequest(E,y){return new _a(E,y)},createScriptRequest(E){return new nr(E)},getLocalStorage(){try{return window.localStorage}catch{return}},createXHR(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest(){var E=this.getXHRAPI();return new E},createMicrosoftXHR(){return new ActiveXObject("Microsoft.XMLHTTP")},getNetwork(){return Lo},createWebSocket(E){var y=this.getWebSocketAPI();return new y(E)},createSocketRequest(E,y){if(this.isXHRSupported())return this.HTTPFactory.createXHR(E,y);if(this.isXDRSupported(y.indexOf("https:")===0))return this.HTTPFactory.createXDR(E,y);throw"Cross-origin HTTP requests are not supported"},isXHRSupported(){var E=this.getXHRAPI();return!!E&&new E().withCredentials!==void 0},isXDRSupported(E){var y=E?"https:":"http:",w=this.getProtocol();return!!window.XDomainRequest&&w===y},addUnloadListener(E){window.addEventListener!==void 0?window.addEventListener("pagehide",E,!1):window.attachEvent!==void 0&&window.attachEvent("onunload",E)},removeUnloadListener(E){window.addEventListener!==void 0?window.removeEventListener("pagehide",E,!1):window.detachEvent!==void 0&&window.detachEvent("onunload",E)},randomInt(E){const y=window.crypto||window.msCrypto,w=Math.floor(Math.pow(2,32)/E)*E;let L;do L=y.getRandomValues(new Uint32Array(1))[0];while(L>=w);return L%E}};const Be=Ic;var Ra;(function(E){E[E.ERROR=3]="ERROR",E[E.INFO=6]="INFO",E[E.DEBUG=7]="DEBUG"})(Ra||(Ra={}));const sr=Ra;class Ai{constructor(y,w,L){this.key=y,this.session=w,this.events=[],this.options=L||{},this.sent=0,this.uniqueID=0}log(y,w){y<=this.options.level&&(this.events.push(ke({},w,{timestamp:Pe.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())}error(y){this.log(sr.ERROR,y)}info(y){this.log(sr.INFO,y)}debug(y){this.log(sr.DEBUG,y)}isEmpty(){return this.events.length===0}send(y,w){var L=ke({session:this.session,bundle:this.sent+1,key:this.key,lib:"js",version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],y(L,(N,te)=>{N||this.sent++,w&&w(N,te)}),!0}generateUniqueID(){return this.uniqueID++,this.uniqueID}}class Af{constructor(y,w,L,N){this.name=y,this.priority=w,this.transport=L,this.options=N||{}}isSupported(){return this.transport.isSupported({useTLS:this.options.useTLS})}connect(y,w){if(this.isSupported()){if(this.priority{L||(pt(),te?te.close():N.close())},forceMinPriority:jt=>{L||this.priority{var w="socket_id="+encodeURIComponent(E.socketId);for(var L in y.params)w+="&"+encodeURIComponent(L)+"="+encodeURIComponent(y.params[L]);if(y.paramsProvider!=null){let N=y.paramsProvider();for(var L in N)w+="&"+encodeURIComponent(L)+"="+encodeURIComponent(N[L])}return w},Tf=E=>{if(typeof Be.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(y,w)=>{const L=xf(y,E);Be.getAuthorizers()[E.transport](Be,L,E,T.UserAuthentication,w)}},qo=(E,y)=>{var w="socket_id="+encodeURIComponent(E.socketId);w+="&channel_name="+encodeURIComponent(E.channelName);for(var L in y.params)w+="&"+encodeURIComponent(L)+"="+encodeURIComponent(y.params[L]);if(y.paramsProvider!=null){let N=y.paramsProvider();for(var L in N)w+="&"+encodeURIComponent(L)+"="+encodeURIComponent(N[L])}return w},Rf=E=>{if(typeof Be.getAuthorizers()[E.transport]>"u")throw`'${E.transport}' is not a recognized auth transport`;return(y,w)=>{const L=qo(y,E);Be.getAuthorizers()[E.transport](Be,L,E,T.ChannelAuthorization,w)}},kf=(E,y,w)=>{const L={authTransport:y.transport,authEndpoint:y.endpoint,auth:{params:y.params,headers:y.headers}};return(N,te)=>{const ye=E.channel(N.channelName);w(ye,L).authorize(N.socketId,te)}};function zc(E,y){let w={activityTimeout:E.activityTimeout||v.activityTimeout,cluster:E.cluster,httpPath:E.httpPath||v.httpPath,httpPort:E.httpPort||v.httpPort,httpsPort:E.httpsPort||v.httpsPort,pongTimeout:E.pongTimeout||v.pongTimeout,statsHost:E.statsHost||v.stats_host,unavailableTimeout:E.unavailableTimeout||v.unavailableTimeout,wsPath:E.wsPath||v.wsPath,wsPort:E.wsPort||v.wsPort,wssPort:E.wssPort||v.wssPort,enableStats:Mf(E),httpHost:Df(E),useTLS:Lf(E),wsHost:jc(E),userAuthenticator:Go(E),channelAuthorizer:Nf(E,y)};return"disabledTransports"in E&&(w.disabledTransports=E.disabledTransports),"enabledTransports"in E&&(w.enabledTransports=E.enabledTransports),"ignoreNullOrigin"in E&&(w.ignoreNullOrigin=E.ignoreNullOrigin),"timelineParams"in E&&(w.timelineParams=E.timelineParams),"nacl"in E&&(w.nacl=E.nacl),w}function Df(E){return E.httpHost?E.httpHost:E.cluster?`sockjs-${E.cluster}.pusher.com`:v.httpHost}function jc(E){return E.wsHost?E.wsHost:Of(E.cluster)}function Of(E){return`ws-${E}.pusher.com`}function Lf(E){return Be.getProtocol()==="https:"?!0:E.forceTLS!==!1}function Mf(E){return"enableStats"in E?E.enableStats:"disableStats"in E?!E.disableStats:!1}const Uc=E=>"customHandler"in E&&E.customHandler!=null;function Go(E){const y=Object.assign(Object.assign({},v.userAuthentication),E.userAuthentication);return Uc(y)?y.customHandler:Tf(y)}function $f(E,y){let w;return"channelAuthorization"in E?w=Object.assign(Object.assign({},v.channelAuthorization),E.channelAuthorization):(w={transport:E.authTransport||v.authTransport,endpoint:E.authEndpoint||v.authEndpoint},"auth"in E&&("params"in E.auth&&(w.params=E.auth.params),"headers"in E.auth&&(w.headers=E.auth.headers)),"authorizer"in E&&(w.customHandler=kf(y,w,E.authorizer))),w}function Nf(E,y){const w=$f(E,y);return Uc(w)?w.customHandler:Rf(w)}class If extends je{constructor(y){super(function(w,L){tt.debug(`No callbacks on watchlist events for ${w}`)}),this.pusher=y,this.bindWatchlistInternalEvent()}handleEvent(y){y.data.events.forEach(w=>{this.emit(w.name,w)})}bindWatchlistInternalEvent(){this.pusher.connection.bind("message",y=>{var w=y.event;w==="pusher_internal:watchlist_events"&&this.handleEvent(y)})}}function Bc(){let E,y;return{promise:new Promise((L,N)=>{E=L,y=N}),resolve:E,reject:y}}const Hf=Bc;class zf extends je{constructor(y){super(function(w,L){tt.debug("No callbacks on user for "+w)}),this.signin_requested=!1,this.user_data=null,this.serverToUserChannel=null,this.signinDonePromise=null,this._signinDoneResolve=null,this._onAuthorize=(w,L)=>{if(w){tt.warn(`Error during signin: ${w}`),this.emit("pusher:signin_error",Object.assign({},{type:"AuthError",error:w.message},w instanceof Me?{status:w.status}:{})),this._cleanup();return}this.pusher.send_event("pusher:signin",{auth:L.auth,user_data:L.user_data})},this.pusher=y,this.pusher.connection.bind("state_change",({previous:w,current:L})=>{w!=="connected"&&L==="connected"&&this._signin(),w==="connected"&&L!=="connected"&&(this._cleanup(),this._newSigninPromiseIfNeeded())}),this.watchlist=new If(y),this.pusher.connection.bind("message",w=>{var L=w.event;L==="pusher:signin_success"&&this._onSigninSuccess(w.data),this.serverToUserChannel&&this.serverToUserChannel.name===w.channel&&this.serverToUserChannel.handleEvent(w)})}signin(){this.signin_requested||(this.signin_requested=!0,this._signin())}_signin(){this.signin_requested&&(this._newSigninPromiseIfNeeded(),this.pusher.connection.state==="connected"&&this.pusher.config.userAuthenticator({socketId:this.pusher.connection.socket_id},this._onAuthorize))}_onSigninSuccess(y){try{this.user_data=JSON.parse(y.user_data)}catch{tt.error(`Failed parsing user data after signin: ${y.user_data}`),this._cleanup();return}if(typeof this.user_data.id!="string"||this.user_data.id===""){tt.error(`user_data doesn't contain an id. user_data: ${this.user_data}`),this._cleanup();return}this._signinDoneResolve(),this._subscribeChannels()}_subscribeChannels(){const y=w=>{w.subscriptionPending&&w.subscriptionCancelled?w.reinstateSubscription():!w.subscriptionPending&&this.pusher.connection.state==="connected"&&w.subscribe()};this.serverToUserChannel=new qa(`#server-to-user-${this.user_data.id}`,this.pusher),this.serverToUserChannel.bind_global((w,L)=>{w.indexOf("pusher_internal:")===0||w.indexOf("pusher:")===0||this.emit(w,L)}),y(this.serverToUserChannel)}_cleanup(){this.user_data=null,this.serverToUserChannel&&(this.serverToUserChannel.unbind_all(),this.serverToUserChannel.disconnect(),this.serverToUserChannel=null),this.signin_requested&&this._signinDoneResolve()}_newSigninPromiseIfNeeded(){if(!this.signin_requested||this.signinDonePromise&&!this.signinDonePromise.done)return;const{promise:y,resolve:w}=Hf();y.done=!1;const L=()=>{y.done=!0};y.then(L).catch(L),this.signinDonePromise=y,this._signinDoneResolve=w}}class cn{static ready(){cn.isReady=!0;for(var y=0,w=cn.instances.length;yBe.getDefaultStrategy(this.config,N,xi);this.connection=In.createConnectionManager(this.key,{getStrategy:L,timeline:this.timeline,activityTimeout:this.config.activityTimeout,pongTimeout:this.config.pongTimeout,unavailableTimeout:this.config.unavailableTimeout,useTLS:!!this.config.useTLS}),this.connection.bind("connected",()=>{this.subscribeAll(),this.timelineSender&&this.timelineSender.send(this.connection.isUsingTLS())}),this.connection.bind("message",N=>{var te=N.event,ye=te.indexOf("pusher_internal:")===0;if(N.channel){var Ne=this.channel(N.channel);Ne&&Ne.handleEvent(N)}ye||this.global_emitter.emit(N.event,N.data)}),this.connection.bind("connecting",()=>{this.channels.disconnect()}),this.connection.bind("disconnected",()=>{this.channels.disconnect()}),this.connection.bind("error",N=>{tt.warn(N)}),cn.instances.push(this),this.timeline.info({instances:cn.instances.length}),this.user=new zf(this),cn.isReady&&this.connect()}switchCluster(y){const{appKey:w,cluster:L}=y;this.key=w,this.options=Object.assign(Object.assign({},this.options),{cluster:L}),this.config=zc(this.options,this),this.connection.switchCluster(this.key)}channel(y){return this.channels.find(y)}allChannels(){return this.channels.all()}connect(){if(this.connection.connect(),this.timelineSender&&!this.timelineSenderTimer){var y=this.connection.isUsingTLS(),w=this.timelineSender;this.timelineSenderTimer=new Oe(6e4,function(){w.send(y)})}}disconnect(){this.connection.disconnect(),this.timelineSenderTimer&&(this.timelineSenderTimer.ensureAborted(),this.timelineSenderTimer=null)}bind(y,w,L){return this.global_emitter.bind(y,w,L),this}unbind(y,w,L){return this.global_emitter.unbind(y,w,L),this}bind_global(y){return this.global_emitter.bind_global(y),this}unbind_global(y){return this.global_emitter.unbind_global(y),this}unbind_all(y){return this.global_emitter.unbind_all(),this}subscribeAll(){var y;for(y in this.channels.channels)this.channels.channels.hasOwnProperty(y)&&this.subscribe(y)}subscribe(y){var w=this.channels.add(y,this);return w.subscriptionPending&&w.subscriptionCancelled?w.reinstateSubscription():!w.subscriptionPending&&this.connection.state==="connected"&&w.subscribe(),w}unsubscribe(y){var w=this.channels.find(y);w&&w.subscriptionPending?w.cancelSubscription():(w=this.channels.remove(y),w&&w.subscribed&&w.unsubscribe())}send_event(y,w,L){return this.connection.send_event(y,w,L)}shouldUseTLS(){return this.config.useTLS}signin(){this.user.signin()}}cn.instances=[],cn.isReady=!1,cn.logToConsole=!1,cn.Runtime=Be,cn.ScriptReceivers=Be.ScriptReceivers,cn.DependenciesReceivers=Be.DependenciesReceivers,cn.auth_callbacks=Be.auth_callbacks;const Ss=cn;function jf(E){if(E==null)throw"You must pass your app key when you instantiate Pusher."}Be.setup(cn)}},r={};function i(u){var d=r[u];if(d!==void 0)return d.exports;var f=r[u]={exports:{}};return n[u].call(f.exports,f,f.exports,i),f.exports}i.d=(u,d)=>{for(var f in d)i.o(d,f)&&!i.o(u,f)&&Object.defineProperty(u,f,{enumerable:!0,get:d[f]})},i.o=(u,d)=>Object.prototype.hasOwnProperty.call(u,d);var l=i(721);return l})())})(df)),df.exports}var W_=K_();const cy=Xa(W_),Bt=dt("WebSocketManager");class Y_{constructor(){$(this,"echo",null);$(this,"subscriptions",new Map);$(this,"initialized",!1);$(this,"config",null)}configure(e){if(this.initialized){Bt.warn("[WebSocketManager] 이미 초기화되어 설정을 변경할 수 없습니다.");return}this.config=e,Bt.log("[WebSocketManager] 설정 완료:",{appKey:e.appKey?"***":"(없음)",host:e.host,port:e.port,scheme:e.scheme})}initialize(){if(this.initialized)return;if(!this.config||!this.config.appKey){Bt.warn("[WebSocketManager] WebSocket 설정이 없습니다. initTemplateApp에서 websocket 옵션을 전달해주세요.");return}const{appKey:e,host:n="localhost",port:r=80,scheme:i="https",authEndpoint:l="/api/broadcasting/auth"}=this.config,u=Number(r)||80,d=i==="https";Bt.log("[WebSocketManager] 연결 설정:",{host:n,port:u,scheme:i,useTLS:d,authEndpoint:l}),window.Pusher=cy;const f=localStorage.getItem("auth_token"),g={wsHost:n,wsPort:u,wssPort:u,forceTLS:d,disableStats:!0,enabledTransports:["ws","wss"],cluster:"mt1",authEndpoint:l,auth:{headers:{Authorization:f?`Bearer ${f}`:"",Accept:"application/json"}}};Bt.log("[WebSocketManager] Pusher 옵션:",g);const m=new cy(e,g);m.connection.bind("connecting",()=>{Bt.log("[WebSocketManager] 연결 시도 중...")}),m.connection.bind("connected",()=>{Bt.log("[WebSocketManager] 연결 성공! Socket ID:",m.connection.socket_id)}),m.connection.bind("failed",()=>{Bt.error("[WebSocketManager] 연결 실패 - WebSocket을 사용할 수 없습니다.")}),m.connection.bind("error",b=>{Bt.error("[WebSocketManager] 연결 오류:",b)}),m.connection.bind("state_change",b=>{Bt.log(`[WebSocketManager] 상태 변경: ${b.previous} → ${b.current}`)}),m.connection.bind("unavailable",()=>{Bt.error("[WebSocketManager] WebSocket 사용 불가 - 연결할 수 없습니다.")}),m.connection.bind("disconnected",()=>{Bt.warn("[WebSocketManager] 연결이 끊어졌습니다.")}),Bt.log("[WebSocketManager] 초기 연결 상태:",m.connection.state),this.echo=new F_({broadcaster:"reverb",client:m}),Bt.log("[WebSocketManager] 연결 시작 시도..."),m.connect(),window.Echo=this.echo,this.initialized=!0,Bt.log("[WebSocketManager] Echo 초기화 완료")}subscribe(e,n,r,i={}){if(this.initialize(),!this.echo)return Bt.warn("[WebSocketManager] Echo가 초기화되지 않았습니다."),"";const{channelType:l="private"}=i,u=`${e}:${n}`;if(this.subscriptions.has(u))return Bt.log(`[WebSocketManager] 이미 구독 중: ${u}`),u;let d;switch(l){case"public":d=this.echo.channel(e);break;case"presence":d=this.echo.join(e);break;default:d=this.echo.private(e)}return d.listen(`.${n}`,r),this.subscriptions.set(u,d),Bt.log(`[WebSocketManager] 구독 완료: ${u} (${l})`),u}unsubscribe(e){const n=this.subscriptions.get(e);if(!n)return;const r=e.lastIndexOf(":"),i=r>=0?e.substring(r+1):"";if(i)try{n.stopListening(`.${i}`)}catch(l){Bt.warn(`[WebSocketManager] stopListening 실패: ${e}`,l)}this.subscriptions.delete(e),Bt.log(`[WebSocketManager] 구독 해제: ${e}`)}leaveChannel(e){if(!this.echo)return;this.echo.leave(e);const n=[];this.subscriptions.forEach((r,i)=>{i.startsWith(`${e}:`)&&n.push(i)}),n.forEach(r=>this.subscriptions.delete(r)),Bt.log(`[WebSocketManager] 채널 구독 해제: ${e}`)}disconnect(){this.echo&&(this.echo.disconnect(),this.subscriptions.clear(),this.initialized=!1,Bt.log("[WebSocketManager] 연결 종료"))}getEcho(){return this.echo}isInitialized(){return this.initialized}isConfigured(){return this.config!==null&&!!this.config.appKey}getSubscriptionCount(){return this.subscriptions.size}}const mc=new Y_,bt=dt("DataSourceManager"),X_=new Rn;function yc(o,e){const n=o.match(/^\{\{(.+)\}\}$/);if(n){const r=n[1].trim();try{return X_.evaluateExpression(r,e)}catch(i){return bt.error("Failed to evaluate param expression:",r,i),o}}return Zi(o,e,{skipCache:!0})}function uy(o){const e={};for(const n of o.keys()){if(n in e)continue;const r=o.getAll(n);r.length>1?e[n]=r:r.length===1&&(n.endsWith("[]")?e[n]=r:e[n]=r[0])}return e}function J_(){try{return window.G7Core?.devTools}catch{return}}const Cc=class Cc{constructor(e={}){$(this,"options");$(this,"dataCache",new Map);$(this,"bindingEngine");$(this,"globalHeaders",[]);this.options=e,this.bindingEngine=new Rn}setGlobalHeaders(e){this.globalHeaders=e||[]}matchesPattern(e,n){if(n==="*")return!0;const r=n.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}toFormData(e){const n=new FormData;for(const[r,i]of Object.entries(e))i instanceof File||i instanceof Blob?n.append(r,i):i!=null&&n.append(r,typeof i=="object"?JSON.stringify(i):String(i));return n}getMatchingGlobalHeaders(e,n){const r={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,u])=>{const d=yc(u,n);d!=null&&d!==""&&(r[l]=String(d))});return r}filterByCondition(e,n={}){const r=new Set,i=[];for(const l of e){if(r.has(l.id))continue;if(!l.if&&!l.conditions){r.add(l.id),i.push(l);continue}Qi({if:l.if,conditions:l.conditions},n,this.bindingEngine,`data_source:${l.id}`)&&(r.add(l.id),i.push(l))}return i}checkErrorCondition(e,n){if(!e.errorCondition?.if||e.errorCondition?.errorCode===void 0)return null;try{const r={response:n};if(Qi({if:e.errorCondition.if},r,this.bindingEngine,`errorCondition:${e.id}`))return bt.log(`errorCondition matched for ${e.id}, triggering error ${e.errorCondition.errorCode}`),e.errorCondition.errorCode}catch(r){bt.error(`Failed to evaluate errorCondition for ${e.id}:`,r)}return null}async fetchDataSources(e,n={},r=new URLSearchParams,i,l){const u={},d=!!this.options.sampleProvider,f=e.filter(g=>(d||g.auto_fetch!==!1)&&g.type!=="websocket");return await Promise.all(f.map(async g=>{try{const m=await this.fetchDataSource(g,n,r,i,l);u[g.id]=m,this.dataCache.set(g.id,{data:m,timestamp:Date.now()});const b=this.checkErrorCondition(g,m);if(b!==null){const v={status:b,message:`Error condition matched for ${g.id}`,data:m},S=ma(),_=S.resolve(b,{errorHandling:g.errorHandling,onError:g.onError});if(_.handler)try{await S.execute(_.handler,v)}catch(x){bt.error(`Error executing errorCondition handler for ${g.id}:`,x)}g.fallback!==void 0&&(u[g.id]=g.fallback,this.dataCache.set(g.id,{data:g.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${g.id} (errorCondition)`));return}if(g.onSuccess)try{const v={data:m,sourceId:g.id};await this.executeOnSuccessHandler(g.onSuccess,v)}catch(v){bt.error(`Error executing onSuccess handler for ${g.id}:`,v)}}catch(m){bt.error(`Failed to fetch data source: ${g.id}`,m);const b=m?.response?.status||m?.status||500,v=m?.response?.data,S=v?.message||m.message||"Unknown error",_={status:b,message:S,errors:v?.errors,data:v,statusText:m?.response?.statusText},x=ma(),k=x.resolve(b,{errorHandling:g.errorHandling,onError:g.onError});if(k.handler)try{await x.execute(k.handler,_)}catch(O){bt.error(`Error executing error handler for ${g.id}:`,O)}g.fallback!==void 0&&(u[g.id]=g.fallback,this.dataCache.set(g.id,{data:g.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${g.id}`)),this.options.onError&&this.options.onError(m,g)}})),u}async fetchDataSourcesWithResults(e,n={},r=new URLSearchParams,i,l,u){if(e.some(m=>m.type==="websocket")){const m=e.filter(b=>b.type==="websocket").map(b=>b.id).join(", ");bt.warn(`fetchDataSourcesWithResults received WebSocket sources (caller should filter them out before calling): ${m}. WebSocket sources are event listeners, not data providers — they should not be passed to fetch.`)}const f=u?.ignoreAutoFetch?e.filter(m=>m.type!=="websocket"):e.filter(m=>m.auto_fetch!==!1&&m.type!=="websocket");return await Promise.all(f.map(async m=>{try{const b=await this.fetchDataSource(m,n,r,i,l);this.dataCache.set(m.id,{data:b,timestamp:Date.now()});const v=this.checkErrorCondition(m,b);if(v!==null){const S={status:v,message:`Error condition matched for ${m.id}`,data:b},_=ma(),x=_.resolve(v,{errorHandling:m.errorHandling,onError:m.onError});let k=!1;if(x.handler)if(m.loading_strategy==="blocking")_.execute(x.handler,S).catch(O=>{bt.error(`Error executing errorCondition handler for ${m.id}:`,O)}),k=!0;else try{await _.execute(x.handler,S),k=!0}catch(O){bt.error(`Error executing errorCondition handler for ${m.id}:`,O)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${m.id} (errorCondition)`),{id:m.id,state:"success",data:m.fallback}):{id:m.id,state:"error",error:new Error(`Error condition matched: ${v}`),errorCode:v,errorHandled:k}}if(m.onSuccess)try{const S={data:b,sourceId:m.id};await this.executeOnSuccessHandler(m.onSuccess,S)}catch(S){bt.error(`Error executing onSuccess handler for ${m.id}:`,S)}return{id:m.id,state:"success",data:b}}catch(b){bt.error(`Failed to fetch data source: ${m.id}`,b);const v=b?.response?.status||b?.status||500,S=b?.response?.data,_=S?.message||b.message||"Unknown error",x={status:v,message:_,errors:S?.errors,data:S,statusText:b?.response?.statusText},k=ma(),O=k.resolve(v,{errorHandling:m.errorHandling,onError:m.onError});let M=!1;if(O.handler)if(m.loading_strategy==="blocking")k.execute(O.handler,x).catch(T=>{bt.error(`Error executing error handler for ${m.id}:`,T)}),M=!0;else try{await k.execute(O.handler,x),M=!0}catch(T){bt.error(`Error executing error handler for ${m.id}:`,T)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${m.id}`),{id:m.id,state:"success",data:m.fallback}):(this.options.onError&&this.options.onError(b,m),{id:m.id,state:"error",error:b,errorCode:v,errorHandled:M})}}))}fetchDataSourcesInBackground(e,n={},r=new URLSearchParams,i){e.filter(u=>u.auto_fetch!==!1&&u.loading_strategy==="background"&&u.type!=="websocket").forEach(u=>{this.fetchDataSource(u,n,r).then(async d=>{this.dataCache.set(u.id,{data:d,timestamp:Date.now()});const f=this.checkErrorCondition(u,d);if(f!==null){const g={status:f,message:`Error condition matched for ${u.id}`,data:d},m=ma(),b=m.resolve(f,{errorHandling:u.errorHandling,onError:u.onError});if(b.handler)try{await m.execute(b.handler,g)}catch(v){bt.error(`Error executing errorCondition handler for ${u.id}:`,v)}u.fallback!==void 0&&(this.dataCache.set(u.id,{data:u.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${u.id} (errorCondition)`),i&&i(u.id,u.fallback));return}if(u.onSuccess)try{const g={data:d,sourceId:u.id};await this.executeOnSuccessHandler(u.onSuccess,g)}catch(g){bt.error(`Error executing onSuccess handler for ${u.id}:`,g)}i&&i(u.id,d)}).catch(async d=>{bt.error(`Background fetch failed: ${u.id}`,d);const f=d?.response?.status||d?.status||500,g=d?.response?.data,m=g?.message||d.message||"Unknown error",b={status:f,message:m,errors:g?.errors,data:g,statusText:d?.response?.statusText},v=ma(),S=v.resolve(f,{errorHandling:u.errorHandling,onError:u.onError});if(S.handler)try{await v.execute(S.handler,b)}catch(_){bt.error(`Error executing error handler for ${u.id}:`,_)}if(u.fallback!==void 0){this.dataCache.set(u.id,{data:u.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${u.id}`),i&&i(u.id,u.fallback);return}this.options.onError&&this.options.onError(d,u)})})}async fetchDataSource(e,n,r,i,l){switch(e.type){case"api":return this.fetchApiDataSource(e,n,r,i,l);case"static":return e.data;case"route_params":return n;case"query_params":return uy(r);default:throw new Error(`Unknown data source type: ${e.type}`)}}async fetchApiDataSource(e,n,r,i,l){if(this.options.sampleProvider?.has(e.id))return await Promise.resolve(this.options.sampleProvider.resolve(e));if(!e.endpoint)throw new Error(`API data source ${e.id} has no endpoint`);const u={route:n,query:uy(r)};i&&(u._global=i),l&&(u._local=l);let d=Zi(e.endpoint,u,{skipCache:!0});const f=e.method||"GET",m=(e.contentType?typeof e.contentType=="string"&&e.contentType.includes("{{")?Zi(e.contentType,u,{skipCache:!0}):e.contentType:"application/json")==="multipart/form-data";let b=m?{...e.params||{}}:JSON.parse(JSON.stringify(e.params||{}));Object.entries(b).forEach(([_,x])=>{typeof x=="string"&&(b[_]=yc(x,u))}),Object.keys(b).forEach(_=>{b[_]===""&&delete b[_]});const v=J_();let S=null;if(v?.isEnabled()){v.trackDataSourceDefinition({id:e.id,type:e.type,endpoint:d,method:f,autoFetch:e.auto_fetch,initLocal:e.initLocal,initGlobal:e.initGlobal});let _=d;if(f==="GET"&&Object.keys(b).length>0){const x=new URLSearchParams;Object.entries(b).forEach(([O,M])=>{Array.isArray(M)?M.forEach(T=>x.append(O,String(T))):M!=null&&x.append(O,String(M))});const k=x.toString();_=k?`${d}?${k}`:d}S=v.trackRequest(_,f,{requestBody:f!=="GET"?b:void 0,dataSourceId:e.id}),v.trackDataSourceLoading(e.id)}try{let _;const x=e.auth_mode??(e.auth_required===!0?"required":"none"),k=ga.getInstance().isAuthenticated();if(x==="required"&&!k){bt.log(`[DataSourceManager] Skipping ${e.id}: auth_mode is 'required' but no token available`),v?.isEnabled()&&(v.trackDataSourceError(e.id,"Skipped: auth_mode required but not authenticated"),S&&v.failRequest(S,"Skipped: not authenticated"));const M=new Error("Auth required but not authenticated");throw M.response={status:401,statusText:"Unauthorized",data:null},M.status=401,M._authSkipped=!0,M}const O=x==="required"||x==="optional"&&k;if(O){const M=Ia(),T=d.startsWith("/api/")?d.substring(4):d.startsWith("/api")?d.substring(4)||"/":d;this.options.onError&&bt.log(`[DataSourceManager] Normalized endpoint: ${d} -> ${T}`);const D=this.getMatchingGlobalHeaders(d,u),j={};e.headers&&Object.entries(e.headers).forEach(([pe,_e])=>{const we=yc(_e,u);we!=null&&we!==""&&(j[pe]=String(we))});const H={...D,...j},P=Object.keys(H).length>0,X=m?this.toFormData(b):b;switch(f){case"GET":_=await M.get(T,{params:b,...P&&{headers:H}});break;case"POST":P?_=await M.post(T,X,{headers:H}):_=await M.post(T,X);break;case"PUT":P?_=await M.put(T,X,{headers:H}):_=await M.put(T,X);break;case"PATCH":P?_=await M.patch(T,X,{headers:H}):_=await M.patch(T,X);break;case"DELETE":_=await M.delete(T,{params:b,...P&&{headers:H}});break;default:throw new Error(`Unsupported HTTP method: ${f}`)}}else{let M=d;if(f==="GET"){const X=new URLSearchParams(b).toString();M=X?`${d}?${X}`:d}const T={...m?{}:{"Content-Type":"application/json"},Accept:"application/json"};if(typeof window<"u"){const X=localStorage.getItem("g7_locale");X&&(T["Accept-Language"]=X)}const D=this.getMatchingGlobalHeaders(d,u);Object.assign(T,D),e.headers&&Object.entries(e.headers).forEach(([X,pe])=>{const _e=yc(pe,u);_e!=null&&_e!==""&&(T[X]=String(_e))});let j;f!=="GET"&&(m?(j=this.toFormData(b),delete T["Content-Type"]):j=JSON.stringify(b));const H=await fetch(M,{method:f,headers:T,body:j});let P;try{P=await H.json()}catch{P=null}if(S&&v?.isEnabled()&&(v.completeRequest(S,H.status,P),S=null),!H.ok){v?.isEnabled()&&v.trackDataSourceError(e.id,`HTTP ${H.status}: ${H.statusText}`);const X=new Error(`HTTP error! status: ${H.status}`);throw X.response={status:H.status,statusText:H.statusText,data:P},X.status=H.status,X}_=P,v?.isEnabled()&&v.trackDataSourceLoaded(e.id,_)}return O&&S&&v?.isEnabled()&&(v.completeRequest(S,200,_),v.trackDataSourceLoaded(e.id,_)),_}catch(_){if(S&&v?.isEnabled()){const x=_;x?.response?v.completeRequest(S,x.response.status,x.response.data):v.failRequest(S,_ instanceof Error?_.message:String(_)),v.trackDataSourceError(e.id,_ instanceof Error?_.message:String(_))}throw _}}getCachedData(e){const n=this.dataCache.get(e);if(!n)return;if(Date.now()-n.timestamp>Cc.DATA_CACHE_TTL){this.dataCache.delete(e);return}return n.data}clearCache(){this.dataCache.clear()}subscribeWebSockets(e,n,r={}){const i=[],l=e.filter(u=>u.type==="websocket");return l.length===0||l.forEach(u=>{if(!u.channel||!u.event){bt.warn(`WebSocket source ${u.id} missing channel or event`);return}let d,f;try{d=Zi(u.channel,r,{skipCache:!0}),f=Zi(u.event,r,{skipCache:!0})}catch(b){bt.error(`Failed to resolve WebSocket channel/event for source ${u.id}:`,b);return}const g=b=>!!(!b||b.trim()===""||b.includes("{{")||b.endsWith(".")||b.endsWith(":")||b.startsWith(".")||b.startsWith(":")||b.includes("..")||b.includes("::"));if(g(d)||g(f)){bt.warn(`WebSocket source ${u.id} has invalid channel/event after expression resolution, skipping. Original channel="${u.channel}", resolved="${d}". Original event="${u.event}", resolved="${f}". Available context keys: [${Object.keys(r).join(", ")}]`);return}const m=mc.subscribe(d,f,b=>{const v=u.target_source||u.id;this.dataCache.set(v,{data:b,timestamp:Date.now()}),n(v,b)},{channelType:u.channel_type||"private"});m&&i.push(m)}),i}unsubscribeWebSockets(e){e.length!==0&&e.forEach(n=>{mc.unsubscribe(n)})}async executeOnSuccessHandler(e,n){const r=this.options.actionDispatcher??lm();if(!r){bt.warn("ActionDispatcher not available, skipping onSuccess handler");return}const i=Array.isArray(e)?e:[e];for(const l of i)try{await r.dispatchAction({type:"click",...l},{data:{response:n}})}catch(u){throw bt.error("Failed to execute onSuccess handler:",u),u}}};$(Cc,"DATA_CACHE_TTL",3e5);let Sa=Cc;const Q_=new Sa,pi=dt("ModalDataSourceWrapper");function Z_(o,e){if(!o||!e)return o;const r=e.replace(/\?\./g,".").split(/\.(?![^\[]*\])/).flatMap(l=>{const u=l.match(/^([^\[]*)((?:\[\d+\])*)$/);if(u){const[,d,f]=u,g=[];d&&g.push(d);const m=f.match(/\[\d+\]/g);return m&&g.push(...m),g}return[l]}).filter(l=>l!=="");let i=o;for(const l of r){if(i==null)return;if(l.startsWith("[")&&l.endsWith("]")){const u=parseInt(l.slice(1,-1),10);i=i[u]}else i=i[l]}return i}const dy=({isOpen:o,modalId:e,dataSources:n,dataContext:r,globalStateUpdater:i,bindingEngine:l,debug:u=!1,children:d})=>{const[f,g]=I.useState(!1),[m,b]=I.useState(!1),v=I.useRef(!1),S=I.useRef(null);S.current||(S.current=new Sa);const _=I.useCallback(async()=>{if(!(!n||n.length===0)&&S.current){g(!0);try{const x=r.route||{},k=new URLSearchParams(window.location.search),O=n.map(T=>{let D={...T};if(T.endpoint&&l){const j=l.resolveBindings(T.endpoint,r,{skipCache:!0});D.endpoint=j}if(T.params&&l){const j={};Object.entries(T.params).forEach(([H,P])=>{typeof P=="string"?j[H]=l.resolveBindings(P,r,{skipCache:!0}):j[H]=P}),D.params=j}return D});u&&pi.log(`[ModalDataSourceWrapper] Modal "${e}" fetching data sources:`,O.map(T=>({id:T.id,endpoint:T.endpoint})));const M=await S.current.fetchDataSourcesWithResults(O,x,k);i&&M.forEach(T=>{if(T.state==="success"&&T.data!==void 0){const D=O.find(j=>j.id===T.id);if(D?.initGlobal){const j=T.data?.data??T.data,H=Array.isArray(D.initGlobal)?D.initGlobal:[D.initGlobal];for(const P of H)if(typeof P=="string")i({[P]:j}),u&&pi.log(`[ModalDataSourceWrapper] Modal "${e}" initGlobal: ${T.id}.data -> _global.${P}`);else if(typeof P=="object"&&P.key){const{key:X,path:pe}=P,_e=pe?Z_(j,pe):j;i({[X]:_e}),u&&pi.log(`Modal "${e}" initGlobal: ${T.id}.data${pe?"."+pe:""} -> _global.${X}`)}}}else T.state==="error"&&pi.error(`Modal "${e}" data source error:`,T.id,T.error)}),u&&pi.log(`Modal "${e}" data sources fetched successfully`)}catch(x){pi.error(`Modal "${e}" data source fetch error:`,x)}finally{g(!1),b(!0)}}},[n,r,i,l,e,u]);return I.useEffect(()=>{if(!n||n.length===0)return;const x=window.__templateApp;return x?.registerModalDataSources&&x.registerModalDataSources(e,n),()=>{x?.unregisterModalDataSources&&x.unregisterModalDataSources(e)}},[e,n]),I.useEffect(()=>{!o&&v.current&&(b(!1),u&&pi.log(`[ModalDataSourceWrapper] Modal "${e}" closed, reset fetch state`)),o&&!v.current&&n&&n.length>0&&!m&&_(),v.current=o},[o,n,m,_,e,u]),ft.jsx(ft.Fragment,{children:d})};class bc extends Error{constructor(n,r,i={}){super(r);$(this,"code");$(this,"userMessageKey");$(this,"showStack");$(this,"icon");$(this,"recoverable");this.name="TemplateEngineError",this.code=n,this.userMessageKey=r,this.showStack=i.showStack??!0,this.icon=i.icon??"fa-circle-exclamation",this.recoverable=i.recoverable??!0,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}getUserMessage(n,r){if(!r||!n)return this.userMessageKey;try{return r.translate(this.userMessageKey,n)}catch{return this.userMessageKey}}}class eA extends bc{constructor(){super("TEMPLATE_NOT_FOUND","$t:core.errors.template_not_found",{showStack:!1,icon:"fa-paint-brush",recoverable:!1}),this.name="TemplateNotFoundError"}}function fy(o){if(o instanceof bc)return o;if(o instanceof Error){const e=new bc("TEMPLATE_INIT_ERROR",o.message||"$t:core.errors.template_init_error",{showStack:!0,icon:"fa-circle-exclamation",recoverable:!0});return e.stack=o.stack,e}return new bc("TEMPLATE_INIT_ERROR","$t:core.errors.template_init_error",{showStack:!1,icon:"fa-circle-exclamation",recoverable:!0})}const tA=dt("ErrorDisplay");class ff{static render(e,n){const r=document.getElementById(e);if(!r){tA.error(`Container #${e} not found`);return}const i=n.debug&&n.showStack&&n.stackTrace,l=window.matchMedia("(prefers-color-scheme: dark)").matches,u={container:` + `,Bn.error("Error state rendered:",i,r)}getCurrentLayout(){return this.currentLayout}clear(){this.currentLayout=null,this.layoutCache.clear(),Bn.log("Layout data and cache cleared")}}const w_=ct("TranslationContext"),af=N.createContext(null),uc=({children:o,translationEngine:e,translationContext:a})=>{const r=N.useMemo(()=>(l,u)=>{if(u){const d="|"+Object.entries(u).map(([f,g])=>`${f}=${g}`).join("|");return e.translate(l,a,d)}return e.translate(l,a)},[e,a]),i=N.useMemo(()=>({translationEngine:e,translationContext:a,t:r}),[e,a,r]);return ut.jsx(af.Provider,{value:i,children:o})},Km=()=>{const o=N.useContext(af);return o||(w_.warn("TranslationProvider 외부에서 호출되었습니다. 키를 그대로 반환합니다."),{t:e=>e,translationEngine:null,translationContext:null})},Rr=ct("SlotContext"),C_={registerToSlot:()=>{Rr.warn("SlotContext not available: registerToSlot called outside SlotProvider")},unregisterFromSlot:()=>{Rr.warn("SlotContext not available: unregisterFromSlot called outside SlotProvider")},getSlotComponents:()=>[],subscribeToSlot:()=>()=>{},clearAllSlots:()=>{},isEnabled:!1},E_=N.createContext(C_),rf=({children:o})=>{const e=N.useRef(new Map),a=N.useRef(new Map),r=N.useCallback(m=>{const b=a.current.get(m);b&&b.forEach(v=>{try{v()}catch(S){Rr.error(`Slot subscriber error (slotId: ${m}):`,S)}})},[]),i=N.useCallback((m,b,v)=>{if(!m||!b){Rr.warn("registerToSlot: slotId and componentId are required");return}e.current.has(m)||e.current.set(m,new Map);const S=e.current.get(m),E=S.get(b);E&&E.registrationKey===v.registrationKey||(S.set(b,v),Rr.log(`Component registered to slot: ${b} -> ${m} (order: ${v.order})`),r(m))},[r]),l=N.useCallback((m,b)=>{const v=e.current.get(m);v&&v.has(b)&&(v.delete(b),Rr.log(`Component unregistered from slot: ${b} <- ${m}`),v.size===0&&e.current.delete(m),r(m))},[r]),u=N.useCallback(m=>{const b=e.current.get(m);return b?Array.from(b.values()).sort((v,S)=>v.order-S.order):[]},[]),d=N.useCallback((m,b)=>{a.current.has(m)||a.current.set(m,new Set);const v=a.current.get(m);return v.add(b),()=>{v.delete(b),v.size===0&&a.current.delete(m)}},[]),f=N.useCallback(()=>{e.current.forEach((m,b)=>{r(b)}),e.current.clear(),a.current.clear(),Rr.log("All slots cleared")},[r]),g=N.useMemo(()=>({registerToSlot:i,unregisterFromSlot:l,getSlotComponents:u,subscribeToSlot:d,clearAllSlots:f,isEnabled:!0}),[i,l,u,d,f]);return N.useLayoutEffect(()=>{typeof window<"u"&&(window.__slotContextValue=g,Rr.log("SlotContext exposed to window.__slotContextValue"))},[g]),ut.jsx(E_.Provider,{value:g,children:o})};class sf{constructor(){this.notificationCreatedEvent=".Illuminate\\Notifications\\Events\\BroadcastNotificationCreated"}listenForWhisper(e,a){return this.listen(".client-"+e,a)}notification(e){return this.listen(this.notificationCreatedEvent,e)}stopListeningForNotification(e){return this.stopListening(this.notificationCreatedEvent,e)}stopListeningForWhisper(e,a){return this.stopListening(".client-"+e,a)}}class Wm{constructor(e){this.namespace=e}format(e){return[".","\\"].includes(e.charAt(0))?e.substring(1):(this.namespace&&(e=this.namespace+"."+e),e.replace(/\./g,"\\"))}setNamespace(e){this.namespace=e}}function __(o){try{return Reflect.construct(String,[],o),!0}catch{return!1}}class of extends sf{constructor(e,a,r){super(),this.name=a,this.pusher=e,this.options=r,this.eventFormatter=new Wm(this.options.namespace),this.subscribe()}subscribe(){this.subscription=this.pusher.subscribe(this.name)}unsubscribe(){this.pusher.unsubscribe(this.name)}listen(e,a){return this.on(this.eventFormatter.format(e),a),this}listenToAll(e){return this.subscription.bind_global((a,r)=>{if(a.startsWith("pusher:"))return;let i=String(this.options.namespace??"").replace(/\./g,"\\"),l=a.startsWith(i)?a.substring(i.length+1):"."+a;e(l,r)}),this}stopListening(e,a){return a?this.subscription.unbind(this.eventFormatter.format(e),a):this.subscription.unbind(this.eventFormatter.format(e)),this}stopListeningToAll(e){return e?this.subscription.unbind_global(e):this.subscription.unbind_global(),this}subscribed(e){return this.on("pusher:subscription_succeeded",()=>{e()}),this}error(e){return this.on("pusher:subscription_error",a=>{e(a)}),this}on(e,a){return this.subscription.bind(e,a),this}}class Ym extends of{whisper(e,a){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,a),this}}class A_ extends of{whisper(e,a){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,a),this}}class x_ extends Ym{here(e){return this.on("pusher:subscription_succeeded",a=>{e(Object.keys(a.members).map(r=>a.members[r]))}),this}joining(e){return this.on("pusher:member_added",a=>{e(a.info)}),this}whisper(e,a){return this.pusher.channels.channels[this.name].trigger(`client-${e}`,a),this}leaving(e){return this.on("pusher:member_removed",a=>{e(a.info)}),this}}class Xm extends sf{constructor(e,a,r){super(),this.events={},this.listeners={},this.name=a,this.socket=e,this.options=r,this.eventFormatter=new Wm(this.options.namespace),this.subscribe()}subscribe(){this.socket.emit("subscribe",{channel:this.name,auth:this.options.auth||{}})}unsubscribe(){this.unbind(),this.socket.emit("unsubscribe",{channel:this.name,auth:this.options.auth||{}})}listen(e,a){return this.on(this.eventFormatter.format(e),a),this}stopListening(e,a){return this.unbindEvent(this.eventFormatter.format(e),a),this}subscribed(e){return this.on("connect",a=>{e(a)}),this}error(e){return this}on(e,a){return this.listeners[e]=this.listeners[e]||[],this.events[e]||(this.events[e]=(r,i)=>{this.name===r&&this.listeners[e]&&this.listeners[e].forEach(l=>l(i))},this.socket.on(e,this.events[e])),this.listeners[e].push(a),this}unbind(){Object.keys(this.events).forEach(e=>{this.unbindEvent(e)})}unbindEvent(e,a){this.listeners[e]=this.listeners[e]||[],a&&(this.listeners[e]=this.listeners[e].filter(r=>r!==a)),(!a||this.listeners[e].length===0)&&(this.events[e]&&(this.socket.removeListener(e,this.events[e]),delete this.events[e]),delete this.listeners[e])}}class Jm extends Xm{whisper(e,a){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:a}),this}}class T_ extends Jm{here(e){return this.on("presence:subscribed",a=>{e(a.map(r=>r.user_info))}),this}joining(e){return this.on("presence:joining",a=>e(a.user_info)),this}whisper(e,a){return this.socket.emit("client event",{channel:this.name,event:`client-${e}`,data:a}),this}leaving(e){return this.on("presence:leaving",a=>e(a.user_info)),this}}class dc extends sf{subscribe(){}unsubscribe(){}listen(e,a){return this}listenToAll(e){return this}stopListening(e,a){return this}subscribed(e){return this}error(e){return this}on(e,a){return this}}class Qm extends dc{whisper(e,a){return this}}class R_ extends dc{whisper(e,a){return this}}class k_ extends Qm{here(e){return this}joining(e){return this}whisper(e,a){return this}leaving(e){return this}}const Zm=class OS{constructor(e){this.setOptions(e),this.connect()}setOptions(e){this.options={...OS._defaultOptions,...e,broadcaster:e.broadcaster};let a=this.csrfToken();a&&(this.options.auth.headers["X-CSRF-TOKEN"]=a,this.options.userAuthentication.headers["X-CSRF-TOKEN"]=a),a=this.options.bearerToken,a&&(this.options.auth.headers.Authorization="Bearer "+a,this.options.userAuthentication.headers.Authorization="Bearer "+a)}csrfToken(){var e,a;return typeof window<"u"&&(e=window.Laravel)!=null&&e.csrfToken?window.Laravel.csrfToken:this.options.csrfToken?this.options.csrfToken:typeof document<"u"&&typeof document.querySelector=="function"?((a=document.querySelector('meta[name="csrf-token"]'))==null?void 0:a.getAttribute("content"))??null:null}};Zm._defaultOptions={auth:{headers:{}},authEndpoint:"/broadcasting/auth",userAuthentication:{endpoint:"/broadcasting/user-auth",headers:{}},csrfToken:null,bearerToken:null,host:null,key:null,namespace:"App.Events"};let lf=Zm;class fc extends lf{constructor(){super(...arguments),this.channels={}}connect(){if(typeof this.options.client<"u")this.pusher=this.options.client;else if(this.options.Pusher)this.pusher=new this.options.Pusher(this.options.key,this.options);else if(typeof window<"u"&&typeof window.Pusher<"u")this.pusher=new window.Pusher(this.options.key,this.options);else throw new Error("Pusher client not found. Should be globally available or passed via options.client")}signin(){this.pusher.signin()}listen(e,a,r){return this.channel(e).listen(a,r)}channel(e){return this.channels[e]||(this.channels[e]=new of(this.pusher,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new Ym(this.pusher,"private-"+e,this.options)),this.channels["private-"+e]}encryptedPrivateChannel(e){return this.channels["private-encrypted-"+e]||(this.channels["private-encrypted-"+e]=new A_(this.pusher,"private-encrypted-"+e,this.options)),this.channels["private-encrypted-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new x_(this.pusher,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"private-encrypted-"+e,"presence-"+e].forEach(a=>{this.leaveChannel(a)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.pusher.connection.socket_id}connectionStatus(){const e=this.pusher.connection.state;switch(e){case"connected":case"connecting":return e;case"failed":case"unavailable":return"failed";default:return"disconnected"}}onConnectionChange(e){const a=()=>{e(this.connectionStatus())},r=["state_change","connected","disconnected"];return r.forEach(i=>{this.pusher.connection.bind(i,a)}),()=>{r.forEach(i=>{this.pusher.connection.unbind(i,a)})}}disconnect(){this.pusher.disconnect()}}class D_ extends lf{constructor(){super(...arguments),this.channels={}}connect(){let e=this.getSocketIO();this.socket=e(this.options.host??void 0,this.options),this.socket.io.on("reconnect",()=>{Object.values(this.channels).forEach(a=>{a.subscribe()})})}getSocketIO(){if(typeof this.options.client<"u")return this.options.client;if(typeof window<"u"&&typeof window.io<"u")return window.io;throw new Error("Socket.io client not found. Should be globally available or passed via options.client")}listen(e,a,r){return this.channel(e).listen(a,r)}channel(e){return this.channels[e]||(this.channels[e]=new Xm(this.socket,e,this.options)),this.channels[e]}privateChannel(e){return this.channels["private-"+e]||(this.channels["private-"+e]=new Jm(this.socket,"private-"+e,this.options)),this.channels["private-"+e]}presenceChannel(e){return this.channels["presence-"+e]||(this.channels["presence-"+e]=new T_(this.socket,"presence-"+e,this.options)),this.channels["presence-"+e]}leave(e){[e,"private-"+e,"presence-"+e].forEach(a=>{this.leaveChannel(a)})}leaveChannel(e){this.channels[e]&&(this.channels[e].unsubscribe(),delete this.channels[e])}socketId(){return this.socket.id}connectionStatus(){return this.socket.connected?"connected":this.socket.io._reconnecting?"reconnecting":this.socket.id!==void 0?"disconnected":"connecting"}onConnectionChange(e){const a=()=>{e(this.connectionStatus())},r=["connect","disconnect","connect_error","reconnect_attempt","reconnect","reconnect_error","reconnect_failed"];return r.forEach(i=>{this.socket.on(i,a)}),()=>{r.forEach(i=>{this.socket.off(i,a)})}}disconnect(){this.socket.disconnect()}}class ey extends lf{constructor(){super(...arguments),this.channels={}}connect(){}listen(e,a,r){return new dc}channel(e){return new dc}privateChannel(e){return new Qm}encryptedPrivateChannel(e){return new R_}presenceChannel(e){return new k_}leave(e){}leaveChannel(e){}socketId(){return"fake-socket-id"}connectionStatus(){return"connected"}onConnectionChange(e){return()=>{}}disconnect(){}}class O_{constructor(e){this.options=e,this.connect(),this.options.withoutInterceptors||this.registerInterceptors()}channel(e){return this.connector.channel(e)}connect(){if(this.options.broadcaster==="reverb")this.connector=new fc({...this.options,cluster:""});else if(this.options.broadcaster==="pusher")this.connector=new fc(this.options);else if(this.options.broadcaster==="ably")this.connector=new fc({...this.options,cluster:"",broadcaster:"pusher"});else if(this.options.broadcaster==="socket.io")this.connector=new D_(this.options);else if(this.options.broadcaster==="null")this.connector=new ey(this.options);else if(typeof this.options.broadcaster=="function"&&__(this.options.broadcaster))this.connector=new this.options.broadcaster(this.options);else throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} is not supported.`)}disconnect(){this.connector.disconnect()}join(e){return this.connector.presenceChannel(e)}leave(e){this.connector.leave(e)}leaveChannel(e){this.connector.leaveChannel(e)}leaveAllChannels(){for(const e in this.connector.channels)this.leaveChannel(e)}listen(e,a,r){return this.connector.listen(e,a,r)}private(e){return this.connector.privateChannel(e)}encryptedPrivate(e){if(this.connectorSupportsEncryptedPrivateChannels(this.connector))return this.connector.encryptedPrivateChannel(e);throw new Error(`Broadcaster ${typeof this.options.broadcaster} ${String(this.options.broadcaster)} does not support encrypted private channels.`)}connectorSupportsEncryptedPrivateChannels(e){return e instanceof fc||e instanceof ey}socketId(){return this.connector.socketId()}connectionStatus(){return this.connector.connectionStatus()}registerInterceptors(){typeof Vue<"u"&&Vue!=null&&Vue.http&&this.registerVueRequestInterceptor(),typeof axios=="function"&&this.registerAxiosRequestInterceptor(),typeof jQuery=="function"&&this.registerjQueryAjaxSetup(),typeof Turbo=="object"&&this.registerTurboRequestInterceptor()}registerVueRequestInterceptor(){Vue.http.interceptors.push((e,a)=>{this.socketId()&&e.headers.set("X-Socket-ID",this.socketId()),a()})}registerAxiosRequestInterceptor(){axios.interceptors.request.use(e=>(this.socketId()&&(e.headers["X-Socket-Id"]=this.socketId()),e))}registerjQueryAjaxSetup(){typeof jQuery.ajax<"u"&&jQuery.ajaxPrefilter((e,a,r)=>{this.socketId()&&r.setRequestHeader("X-Socket-Id",this.socketId())})}registerTurboRequestInterceptor(){document.addEventListener("turbo:before-fetch-request",e=>{e.detail.fetchOptions.headers["X-Socket-Id"]=this.socketId()})}}var cf={exports:{}};var ty;function L_(){return ty||(ty=1,(function(o,e){(function(r,i){o.exports=i()})(self,()=>(()=>{var a={594(u,d){var f=this&&this.__extends||(function(){var M=function(R,D){return M=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(U,j){U.__proto__=j}||function(U,j){for(var P in j)j.hasOwnProperty(P)&&(U[P]=j[P])},M(R,D)};return function(R,D){M(R,D);function U(){this.constructor=R}R.prototype=D===null?Object.create(D):(U.prototype=D.prototype,new U)}})();Object.defineProperty(d,"__esModule",{value:!0});var g=256,m=(function(){function M(R){R===void 0&&(R="="),this._paddingCharacter=R}return M.prototype.encodedLength=function(R){return this._paddingCharacter?(R+2)/3*4|0:(R*8+5)/6|0},M.prototype.encode=function(R){for(var D="",U=0;U>>18&63),D+=this._encodeByte(j>>>12&63),D+=this._encodeByte(j>>>6&63),D+=this._encodeByte(j>>>0&63)}var P=R.length-U;if(P>0){var j=R[U]<<16|(P===2?R[U+1]<<8:0);D+=this._encodeByte(j>>>18&63),D+=this._encodeByte(j>>>12&63),P===2?D+=this._encodeByte(j>>>6&63):D+=this._paddingCharacter||"",D+=this._paddingCharacter||""}return D},M.prototype.maxDecodedLength=function(R){return this._paddingCharacter?R/4*3|0:(R*6+7)/8|0},M.prototype.decodedLength=function(R){return this.maxDecodedLength(R.length-this._getPaddingLength(R))},M.prototype.decode=function(R){if(R.length===0)return new Uint8Array(0);for(var D=this._getPaddingLength(R),U=R.length-D,j=new Uint8Array(this.maxDecodedLength(U)),P=0,X=0,ge=0,Ee=0,Ce=0,$e=0,Pe=0;X>>4,j[P++]=Ce<<4|$e>>>2,j[P++]=$e<<6|Pe,ge|=Ee&g,ge|=Ce&g,ge|=$e&g,ge|=Pe&g;if(X>>4,ge|=Ee&g,ge|=Ce&g),X>>2,ge|=$e&g),X>>8&6,D+=51-R>>>8&-75,D+=61-R>>>8&-15,D+=62-R>>>8&3,String.fromCharCode(D)},M.prototype._decodeChar=function(R){var D=g;return D+=(42-R&R-44)>>>8&-g+R-43+62,D+=(46-R&R-48)>>>8&-g+R-47+63,D+=(47-R&R-58)>>>8&-g+R-48+52,D+=(64-R&R-91)>>>8&-g+R-65+0,D+=(96-R&R-123)>>>8&-g+R-97+26,D},M.prototype._getPaddingLength=function(R){var D=0;if(this._paddingCharacter){for(var U=R.length-1;U>=0&&R[U]===this._paddingCharacter;U--)D++;if(R.length<4||D>2)throw new Error("Base64Coder: incorrect padding")}return D},M})();d.Coder=m;var b=new m;function v(M){return b.encode(M)}d.encode=v;function S(M){return b.decode(M)}d.decode=S;var E=(function(M){f(R,M);function R(){return M!==null&&M.apply(this,arguments)||this}return R.prototype._encodeByte=function(D){var U=D;return U+=65,U+=25-D>>>8&6,U+=51-D>>>8&-75,U+=61-D>>>8&-13,U+=62-D>>>8&49,String.fromCharCode(U)},R.prototype._decodeChar=function(D){var U=g;return U+=(44-D&D-46)>>>8&-g+D-45+62,U+=(94-D&D-96)>>>8&-g+D-95+63,U+=(47-D&D-58)>>>8&-g+D-48+52,U+=(64-D&D-91)>>>8&-g+D-65+0,U+=(96-D&D-123)>>>8&-g+D-97+26,U},R})(m);d.URLSafeCoder=E;var A=new E;function T(M){return A.encode(M)}d.encodeURLSafe=T;function L(M){return A.decode(M)}d.decodeURLSafe=L,d.encodedLength=function(M){return b.encodedLength(M)},d.maxDecodedLength=function(M){return b.maxDecodedLength(M)},d.decodedLength=function(M){return b.decodedLength(M)}},978(u,d){var f="utf8: invalid source encoding";function g(m){for(var b=[],v=0;v=m.length)throw new Error(f);var A=m[++v];if((A&192)!==128)throw new Error(f);S=(S&31)<<6|A&63,E=128}else if(S<240){if(v>=m.length-1)throw new Error(f);var A=m[++v],T=m[++v];if((A&192)!==128||(T&192)!==128)throw new Error(f);S=(S&15)<<12|(A&63)<<6|T&63,E=2048}else if(S<248){if(v>=m.length-2)throw new Error(f);var A=m[++v],T=m[++v],L=m[++v];if((A&192)!==128||(T&192)!==128||(L&192)!==128)throw new Error(f);S=(S&15)<<18|(A&63)<<12|(T&63)<<6|L&63,E=65536}else throw new Error(f);if(S=55296&&S<=57343)throw new Error(f);if(S>=65536){if(S>1114111)throw new Error(f);S-=65536,b.push(String.fromCharCode(55296|S>>10)),S=56320|S&1023}}b.push(String.fromCharCode(S))}return b.join("")}d.D4=g},721(u,d,f){u.exports=f(207).default},207(u,d,f){f.d(d,{default:()=>ys});class g{constructor(y,C){this.lastId=0,this.prefix=y,this.name=C}create(y){this.lastId++;var C=this.lastId,O=this.prefix+C,I=this.name+"["+C+"]",ne=!1,me=function(){ne||(y.apply(null,arguments),ne=!0)};return this[C]=me,{number:C,id:O,name:I,callback:me}}remove(y){delete this[y.number]}}var m=new g("_pusher_script_","Pusher.ScriptReceivers"),b={VERSION:"8.5.0",PROTOCOL:7,wsPort:80,wssPort:443,wsPath:"",httpHost:"sockjs.pusher.com",httpPort:80,httpsPort:443,httpPath:"/pusher",stats_host:"stats.pusher.com",authEndpoint:"/pusher/auth",authTransport:"ajax",activityTimeout:12e4,pongTimeout:3e4,unavailableTimeout:1e4,userAuthentication:{endpoint:"/pusher/user-auth",transport:"ajax"},channelAuthorization:{endpoint:"/pusher/auth",transport:"ajax"},cdn_http:"http://js.pusher.com",cdn_https:"https://js.pusher.com",dependency_suffix:""};const v=b;class S{constructor(y){this.options=y,this.receivers=y.receivers||m,this.loading={}}load(y,C,O){var I=this;if(I.loading[y]&&I.loading[y].length>0)I.loading[y].push(O);else{I.loading[y]=[O];var ne=Be.createScriptRequest(I.getPath(y,C)),me=I.receivers.create(function(Me){if(I.receivers.remove(me),I.loading[y]){var Fe=I.loading[y];delete I.loading[y];for(var ot=function(Nt){Nt||ne.cleanup()},pt=0;pt>>6)+Q(128|y&63):Q(224|y>>>12&15)+Q(128|y>>>6&63)+Q(128|y&63)},te=function(_){return _.replace(/[^\x00-\x7F]/g,ve)},he=function(_){var y=[0,2,1][_.length%3],C=_.charCodeAt(0)<<16|(_.length>1?_.charCodeAt(1):0)<<8|(_.length>2?_.charCodeAt(2):0),O=[fe.charAt(C>>>18),fe.charAt(C>>>12&63),y>=2?"=":fe.charAt(C>>>6&63),y>=1?"=":fe.charAt(C&63)];return O.join("")},x=window.btoa||function(_){return _.replace(/[\s\S]{1,3}/g,he)};class K{constructor(y,C,O,I){this.clear=C,this.timer=y(()=>{this.timer&&(this.timer=I(this.timer))},O)}isRunning(){return this.timer!==null}ensureAborted(){this.timer&&(this.clear(this.timer),this.timer=null)}}const re=K;function de(_){window.clearTimeout(_)}function ie(_){window.clearInterval(_)}class ye extends re{constructor(y,C){super(setTimeout,de,y,function(O){return C(),null})}}class Ne extends re{constructor(y,C){super(setInterval,ie,y,function(O){return C(),O})}}var Se={now(){return Date.now?Date.now():new Date().valueOf()},defer(_){return new ye(0,_)},method(_,...y){var C=Array.prototype.slice.call(arguments,1);return function(O){return O[_].apply(O,C.concat(arguments))}}};const He=Se;function Ae(_,...y){for(var C=0;C{window.console&&window.console.log&&window.console.log(y)}}debug(...y){this.log(this.globalLog,y)}warn(...y){this.log(this.globalLogWarn,y)}error(...y){this.log(this.globalLogError,y)}globalLogWarn(y){window.console&&window.console.warn?window.console.warn(y):this.globalLog(y)}globalLogError(y){window.console&&window.console.error?window.console.error(y):this.globalLogWarn(y)}log(y,...C){var O=Et.apply(this,arguments);ys.log?ys.log(O):ys.logToConsole&&y.bind(this)(O)}}const et=new tn;var ta=function(_,y,C,O,I){(C.headers!==void 0||C.headersProvider!=null)&&et.warn(`To send headers with the ${O.toString()} request, you must use AJAX, rather than JSONP.`);var ne=_.nextAuthCallbackID.toString();_.nextAuthCallbackID++;var me=_.getDocument(),Me=me.createElement("script");_.auth_callbacks[ne]=function(pt){I(null,pt)};var Fe="Pusher.auth_callbacks['"+ne+"']";Me.src=C.endpoint+"?callback="+encodeURIComponent(Fe)+"&"+y;var ot=me.getElementsByTagName("head")[0]||me.documentElement;ot.insertBefore(Me,ot.firstChild)};const En=ta;class nr{constructor(y){this.src=y}send(y){var C=this,O="Error loading "+C.src;C.script=document.createElement("script"),C.script.id=y.id,C.script.src=C.src,C.script.type="text/javascript",C.script.charset="UTF-8",C.script.addEventListener?(C.script.onerror=function(){y.callback(O)},C.script.onload=function(){y.callback(null)}):C.script.onreadystatechange=function(){(C.script.readyState==="loaded"||C.script.readyState==="complete")&&y.callback(null)},C.script.async===void 0&&document.attachEvent&&/opera/i.test(navigator.userAgent)?(C.errorScript=document.createElement("script"),C.errorScript.id=y.id+"_error",C.errorScript.text=y.name+"('"+O+"');",C.script.async=C.errorScript.async=!1):C.script.async=!0;var I=document.getElementsByTagName("head")[0];I.insertBefore(C.script,I.firstChild),C.errorScript&&I.insertBefore(C.errorScript,C.script.nextSibling)}cleanup(){this.script&&(this.script.onload=this.script.onerror=null,this.script.onreadystatechange=null),this.script&&this.script.parentNode&&this.script.parentNode.removeChild(this.script),this.errorScript&&this.errorScript.parentNode&&this.errorScript.parentNode.removeChild(this.errorScript),this.script=null,this.errorScript=null}}class Ea{constructor(y,C){this.url=y,this.data=C}send(y){if(!this.request){var C=Dr(this.data),O=this.url+"/"+y.number+"?"+C;this.request=Be.createScriptRequest(O),this.request.send(y)}}cleanup(){this.request&&this.request.cleanup()}}var nn=function(_,y){return function(C,O){var I="http"+(y?"s":"")+"://",ne=I+(_.host||_.options.host)+_.options.path,me=Be.createJSONPRequest(ne,C),Me=Be.ScriptReceivers.create(function(Fe,ot){m.remove(Me),me.cleanup(),ot&&ot.host&&(_.host=ot.host),O&&O(Fe,ot)});me.send(Me)}},_n={name:"jsonp",getAgent:nn};const q=_n;function oe(_,y,C){var O=_+(y.useTLS?"s":""),I=y.useTLS?y.hostTLS:y.hostNonTLS;return O+"://"+I+C}function ce(_,y){var C="/app/"+_,O="?protocol="+v.PROTOCOL+"&client=js&version="+v.VERSION+(y?"&"+y:"");return C+O}var Z={getInitial:function(_,y){var C=(y.httpPath||"")+ce(_,"flash=false");return oe("ws",y,C)}},be={getInitial:function(_,y){var C=(y.httpPath||"/pusher")+ce(_);return oe("http",y,C)}},pe={getInitial:function(_,y){return oe("http",y,y.httpPath||"/pusher")},getPath:function(_,y){return ce(_)}};class Te{constructor(){this._callbacks={}}get(y){return this._callbacks[xe(y)]}add(y,C,O){var I=xe(y);this._callbacks[I]=this._callbacks[I]||[],this._callbacks[I].push({fn:C,context:O})}remove(y,C,O){if(!y&&!C&&!O){this._callbacks={};return}var I=y?[xe(y)]:ft(this._callbacks);C||O?this.removeCallback(I,C,O):this.removeAllCallbacks(I)}removeCallback(y,C,O){gt(y,function(I){this._callbacks[I]=Dn(this._callbacks[I]||[],function(ne){return C&&C!==ne.fn||O&&O!==ne.context}),this._callbacks[I].length===0&&delete this._callbacks[I]},this)}removeAllCallbacks(y){gt(y,function(C){delete this._callbacks[C]},this)}}function xe(_){return"_"+_}class je{constructor(y){this.callbacks=new Te,this.global_callbacks=[],this.failThrough=y}bind(y,C,O){return this.callbacks.add(y,C,O),this}bind_global(y){return this.global_callbacks.push(y),this}unbind(y,C,O){return this.callbacks.remove(y,C,O),this}unbind_global(y){return y?(this.global_callbacks=Dn(this.global_callbacks||[],C=>C!==y),this):(this.global_callbacks=[],this)}unbind_all(){return this.unbind(),this.unbind_global(),this}emit(y,C,O){for(var I=0;I0)for(var I=0;I{this.onError(C),this.changeState("closed")}),!1}return this.bindListeners(),et.debug("Connecting",{transport:this.name,url:y}),this.changeState("connecting"),!0}close(){return this.socket?(this.socket.close(),!0):!1}send(y){return this.state==="open"?(He.defer(()=>{this.socket&&this.socket.send(y)}),!0):!1}ping(){this.state==="open"&&this.supportsPing()&&this.socket.ping()}onOpen(){this.hooks.beforeOpen&&this.hooks.beforeOpen(this.socket,this.hooks.urls.getPath(this.key,this.options)),this.changeState("open"),this.socket.onopen=void 0}onError(y){this.emit("error",{type:"WebSocketError",error:y}),this.timeline.error(this.buildTimelineMessage({error:y.toString()}))}onClose(y){y?this.changeState("closed",{code:y.code,reason:y.reason,wasClean:y.wasClean}):this.changeState("closed"),this.unbindListeners(),this.socket=void 0}onMessage(y){this.emit("message",y)}onActivity(){this.emit("activity")}bindListeners(){this.socket.onopen=()=>{this.onOpen()},this.socket.onerror=y=>{this.onError(y)},this.socket.onclose=y=>{this.onClose(y)},this.socket.onmessage=y=>{this.onMessage(y)},this.supportsPing()&&(this.socket.onactivity=()=>{this.onActivity()})}unbindListeners(){this.socket&&(this.socket.onopen=void 0,this.socket.onerror=void 0,this.socket.onclose=void 0,this.socket.onmessage=void 0,this.supportsPing()&&(this.socket.onactivity=void 0))}changeState(y,C){this.state=y,this.timeline.info(this.buildTimelineMessage({state:y,params:C})),this.emit(y,C)}buildTimelineMessage(y){return Ae({cid:this.id},y)}}class Le{constructor(y){this.hooks=y}isSupported(y){return this.hooks.isSupported(y)}createConnection(y,C,O,I){return new ze(this.hooks,y,C,O,I)}}var ke=new Le({urls:Z,handlesActivityChecks:!1,supportsPing:!1,isInitialized:function(){return!!Be.getWebSocketAPI()},isSupported:function(){return!!Be.getWebSocketAPI()},getSocket:function(_){return Be.createWebSocket(_)}}),Je={urls:be,handlesActivityChecks:!1,supportsPing:!0,isInitialized:function(){return!0}},Ve=Ae({getSocket:function(_){return Be.HTTPFactory.createStreamingSocket(_)}},Je),_e=Ae({getSocket:function(_){return Be.HTTPFactory.createPollingSocket(_)}},Je),Re={isSupported:function(){return Be.isXHRSupported()}},Ue=new Le(Ae({},Ve,Re)),st=new Le(Ae({},_e,Re)),At={ws:ke,xhr_streaming:Ue,xhr_polling:st};const Wt=At;var qt=new Le({file:"sockjs",urls:pe,handlesActivityChecks:!0,supportsPing:!1,isSupported:function(){return!0},isInitialized:function(){return window.SockJS!==void 0},getSocket:function(_,y){return new window.SockJS(_,null,{js_path:A.getPath("sockjs",{useTLS:y.useTLS}),ignore_null_origin:y.ignoreNullOrigin})},beforeOpen:function(_,y){_.send(JSON.stringify({path:y}))}}),qn={isSupported:function(_){var y=Be.isXDRSupported(_.useTLS);return y}},Ln=new Le(Ae({},Ve,qn)),an=new Le(Ae({},_e,qn));Wt.xdr_streaming=Ln,Wt.xdr_polling=an,Wt.sockjs=qt;const vn=Wt;class Or extends je{constructor(){super();var y=this;window.addEventListener!==void 0&&(window.addEventListener("online",function(){y.emit("online")},!1),window.addEventListener("offline",function(){y.emit("offline")},!1))}isOnline(){return window.navigator.onLine===void 0?!0:window.navigator.onLine}}var Ro=new Or;class mf{constructor(y,C,O){this.manager=y,this.transport=C,this.minPingDelay=O.minPingDelay,this.maxPingDelay=O.maxPingDelay,this.pingDelay=void 0}createConnection(y,C,O,I){I=Ae({},I,{activityTimeout:this.pingDelay});var ne=this.transport.createConnection(y,C,O,I),me=null,Me=function(){ne.unbind("open",Me),ne.bind("closed",Fe),me=He.now()},Fe=ot=>{if(ne.unbind("closed",Fe),ot.code===1002||ot.code===1003)this.manager.reportDeath();else if(!ot.wasClean&&me){var pt=He.now()-me;pt<2*this.maxPingDelay&&(this.manager.reportDeath(),this.pingDelay=Math.max(pt/2,this.minPingDelay))}};return ne.bind("open",Me),ne}isSupported(y){return this.manager.isAlive()&&this.transport.isSupported(y)}}const vc={decodeMessage:function(_){try{var y=JSON.parse(_.data),C=y.data;if(typeof C=="string")try{C=JSON.parse(y.data)}catch{}var O={event:y.event,channel:y.channel,data:C};return y.user_id&&(O.user_id=y.user_id),O}catch(I){throw{type:"MessageParseError",error:I,data:_.data}}},encodeMessage:function(_){return JSON.stringify(_)},processHandshake:function(_){var y=vc.decodeMessage(_);if(y.event==="pusher:connection_established"){if(!y.data.activity_timeout)throw"No activity timeout specified in handshake";return{action:"connected",id:y.data.socket_id,activityTimeout:y.data.activity_timeout*1e3}}else{if(y.event==="pusher:error")return{action:this.getCloseAction(y.data),error:this.getCloseError(y.data)};throw"Invalid handshake"}},getCloseAction:function(_){return _.code<4e3?_.code>=1002&&_.code<=1004?"backoff":null:_.code===4e3?"tls_only":_.code<4100?"refused":_.code<4200?"backoff":_.code<4300?"retry":"refused"},getCloseError:function(_){return _.code!==1e3&&_.code!==1001?{type:"PusherError",data:{code:_.code,message:_.reason||_.message}}:null}},Pa=vc;class bi extends je{constructor(y,C){super(),this.id=y,this.transport=C,this.activityTimeout=C.activityTimeout,this.bindListeners()}handlesActivityChecks(){return this.transport.handlesActivityChecks()}send(y){return this.transport.send(y)}send_event(y,C,O){var I={event:y,data:C};return O&&(I.channel=O),et.debug("Event sent",I),this.send(Pa.encodeMessage(I))}ping(){this.transport.supportsPing()?this.transport.ping():this.send_event("pusher:ping",{})}close(){this.transport.close()}bindListeners(){var y={message:O=>{var I;try{I=Pa.decodeMessage(O)}catch(ne){this.emit("error",{type:"MessageParseError",error:ne,data:O.data})}if(I!==void 0){switch(et.debug("Event recd",I),I.event){case"pusher:error":this.emit("error",{type:"PusherError",data:I.data});break;case"pusher:ping":this.emit("ping");break;case"pusher:pong":this.emit("pong");break}this.emit("message",I)}},activity:()=>{this.emit("activity")},error:O=>{this.emit("error",O)},closed:O=>{C(),O&&O.code&&this.handleCloseEvent(O),this.transport=null,this.emit("closed")}},C=()=>{_t(y,(O,I)=>{this.transport.unbind(I,O)})};_t(y,(O,I)=>{this.transport.bind(I,O)})}handleCloseEvent(y){var C=Pa.getCloseAction(y),O=Pa.getCloseError(y);O&&this.emit("error",O),C&&this.emit(C,{action:C,error:O})}}class ko{constructor(y,C){this.transport=y,this.callback=C,this.bindListeners()}close(){this.unbindListeners(),this.transport.close()}bindListeners(){this.onMessage=y=>{this.unbindListeners();var C;try{C=Pa.processHandshake(y)}catch(O){this.finish("error",{error:O}),this.transport.close();return}C.action==="connected"?this.finish("connected",{connection:new bi(C.id,this.transport),activityTimeout:C.activityTimeout}):(this.finish(C.action,{error:C.error}),this.transport.close())},this.onClosed=y=>{this.unbindListeners();var C=Pa.getCloseAction(y)||"backoff",O=Pa.getCloseError(y);this.finish(C,{error:O})},this.transport.bind("message",this.onMessage),this.transport.bind("closed",this.onClosed)}unbindListeners(){this.transport.unbind("message",this.onMessage),this.transport.unbind("closed",this.onClosed)}finish(y,C){this.callback(Ae({transport:this.transport,action:y},C))}}class Lr{constructor(y,C){this.timeline=y,this.options=C||{}}send(y,C){this.timeline.isEmpty()||this.timeline.send(Be.TimelineTransport.getAgent(this,y),C)}}class qa extends je{constructor(y,C){super(function(O,I){et.debug("No callbacks on "+y+" for "+O)}),this.name=y,this.pusher=C,this.subscribed=!1,this.subscriptionPending=!1,this.subscriptionCancelled=!1}authorize(y,C){return C(null,{auth:""})}trigger(y,C){if(y.indexOf("client-")!==0)throw new D("Event '"+y+"' does not start with 'client-'");if(!this.subscribed){var O=M.buildLogSuffix("triggeringClientEvents");et.warn(`Client event triggered before channel 'subscription_succeeded' event . ${O}`)}return this.pusher.send_event(y,C,this.name)}disconnect(){this.subscribed=!1,this.subscriptionPending=!1}handleEvent(y){var C=y.event,O=y.data;if(C==="pusher_internal:subscription_succeeded")this.handleSubscriptionSucceededEvent(y);else if(C==="pusher_internal:subscription_count")this.handleSubscriptionCountEvent(y);else if(C.indexOf("pusher_internal:")!==0){var I={};this.emit(C,O,I)}}handleSubscriptionSucceededEvent(y){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):this.emit("pusher:subscription_succeeded",y.data)}handleSubscriptionCountEvent(y){y.data.subscription_count&&(this.subscriptionCount=y.data.subscription_count),this.emit("pusher:subscription_count",y.data)}subscribe(){this.subscribed||(this.subscriptionPending=!0,this.subscriptionCancelled=!1,this.authorize(this.pusher.connection.socket_id,(y,C)=>{y?(this.subscriptionPending=!1,et.error(y.toString()),this.emit("pusher:subscription_error",Object.assign({},{type:"AuthError",error:y.message},y instanceof $e?{status:y.status}:{}))):this.pusher.send_event("pusher:subscribe",{auth:C.auth,channel_data:C.channel_data,channel:this.name})}))}unsubscribe(){this.subscribed=!1,this.pusher.send_event("pusher:unsubscribe",{channel:this.name})}cancelSubscription(){this.subscriptionCancelled=!0}reinstateSubscription(){this.subscriptionCancelled=!1}}class ar extends qa{authorize(y,C){return this.pusher.config.channelAuthorizer({channelName:this.name,socketId:y},C)}}class Mr{constructor(){this.reset()}get(y){return Object.prototype.hasOwnProperty.call(this.members,y)?{id:y,info:this.members[y]}:null}each(y){_t(this.members,(C,O)=>{y(this.get(O))})}setMyID(y){this.myID=y}onSubscription(y){this.members=y.presence.hash,this.count=y.presence.count,this.me=this.get(this.myID)}addMember(y){return this.get(y.user_id)===null&&this.count++,this.members[y.user_id]=y.user_info,this.get(y.user_id)}removeMember(y){var C=this.get(y.user_id);return C&&(delete this.members[y.user_id],this.count--),C}reset(){this.members={},this.count=0,this.myID=null,this.me=null}}var Yt=function(_,y,C,O){function I(ne){return ne instanceof C?ne:new C(function(me){me(ne)})}return new(C||(C=Promise))(function(ne,me){function Me(pt){try{ot(O.next(pt))}catch(Nt){me(Nt)}}function Fe(pt){try{ot(O.throw(pt))}catch(Nt){me(Nt)}}function ot(pt){pt.done?ne(pt.value):I(pt.value).then(Me,Fe)}ot((O=O.apply(_,y||[])).next())})};class Sc extends ar{constructor(y,C){super(y,C),this.members=new Mr}authorize(y,C){super.authorize(y,(O,I)=>Yt(this,void 0,void 0,function*(){if(!O)if(I=I,I.channel_data!=null){var ne=JSON.parse(I.channel_data);this.members.setMyID(ne.user_id)}else if(yield this.pusher.user.signinDonePromise,this.pusher.user.user_data!=null)this.members.setMyID(this.pusher.user.user_data.id);else{let me=M.buildLogSuffix("authorizationEndpoint");et.error(`Invalid auth response for channel '${this.name}', expected 'channel_data' field. ${me}, or the user should be signed in.`),C("Invalid auth response");return}C(O,I)}))}handleEvent(y){var C=y.event;if(C.indexOf("pusher_internal:")===0)this.handleInternalEvent(y);else{var O=y.data,I={};y.user_id&&(I.user_id=y.user_id),this.emit(C,O,I)}}handleInternalEvent(y){var C=y.event,O=y.data;switch(C){case"pusher_internal:subscription_succeeded":this.handleSubscriptionSucceededEvent(y);break;case"pusher_internal:subscription_count":this.handleSubscriptionCountEvent(y);break;case"pusher_internal:member_added":var I=this.members.addMember(O);this.emit("pusher:member_added",I);break;case"pusher_internal:member_removed":var ne=this.members.removeMember(O);ne&&this.emit("pusher:member_removed",ne);break}}handleSubscriptionSucceededEvent(y){this.subscriptionPending=!1,this.subscribed=!0,this.subscriptionCancelled?this.pusher.unsubscribe(this.name):(this.members.onSubscription(y.data),this.emit("pusher:subscription_succeeded",this.members))}disconnect(){this.members.reset(),super.disconnect()}}var wc=f(978),_a=f(594);class $r extends ar{constructor(y,C,O){super(y,C),this.key=null,this.nacl=O}authorize(y,C){super.authorize(y,(O,I)=>{if(O){C(O,I);return}let ne=I.shared_secret;if(!ne){C(new Error(`No shared_secret key in auth payload for encrypted channel: ${this.name}`),null);return}this.key=(0,_a.decode)(ne),delete I.shared_secret,C(null,I)})}trigger(y,C){throw new ge("Client events are not currently supported for encrypted channels")}handleEvent(y){var C=y.event,O=y.data;if(C.indexOf("pusher_internal:")===0||C.indexOf("pusher:")===0){super.handleEvent(y);return}this.handleEncryptedEvent(C,O)}handleEncryptedEvent(y,C){if(!this.key){et.debug("Received encrypted event before key has been retrieved from the authEndpoint");return}if(!C.ciphertext||!C.nonce){et.error("Unexpected format for encrypted event, expected object with `ciphertext` and `nonce` fields, got: "+C);return}let O=(0,_a.decode)(C.ciphertext);if(O.length{if(me){et.error(`Failed to make a request to the authEndpoint: ${Me}. Unable to fetch new key, so dropping encrypted event`);return}if(ne=this.nacl.secretbox.open(O,I,this.key),ne===null){et.error("Failed to decrypt event with new key. Dropping encrypted event");return}this.emit(y,this.getDataToEmit(ne))});return}this.emit(y,this.getDataToEmit(ne))}getDataToEmit(y){let C=(0,wc.D4)(y);try{return JSON.parse(C)}catch{return C}}}class yf extends je{constructor(y,C){super(),this.state="initialized",this.connection=null,this.key=y,this.options=C,this.timeline=this.options.timeline,this.usingTLS=this.options.useTLS,this.errorCallbacks=this.buildErrorCallbacks(),this.connectionCallbacks=this.buildConnectionCallbacks(this.errorCallbacks),this.handshakeCallbacks=this.buildHandshakeCallbacks(this.errorCallbacks);var O=Be.getNetwork();O.bind("online",()=>{this.timeline.info({netinfo:"online"}),(this.state==="connecting"||this.state==="unavailable")&&this.retryIn(0)}),O.bind("offline",()=>{this.timeline.info({netinfo:"offline"}),this.connection&&this.sendActivityCheck()}),this.updateStrategy()}switchCluster(y){this.key=y,this.updateStrategy(),this.retryIn(0)}connect(){if(!(this.connection||this.runner)){if(!this.strategy.isSupported()){this.updateState("failed");return}this.updateState("connecting"),this.startConnecting(),this.setUnavailableTimer()}}send(y){return this.connection?this.connection.send(y):!1}send_event(y,C,O){return this.connection?this.connection.send_event(y,C,O):!1}disconnect(){this.disconnectInternally(),this.updateState("disconnected")}isUsingTLS(){return this.usingTLS}startConnecting(){var y=(C,O)=>{C?this.runner=this.strategy.connect(0,y):O.action==="error"?(this.emit("error",{type:"HandshakeError",error:O.error}),this.timeline.error({handshakeError:O.error})):(this.abortConnecting(),this.handshakeCallbacks[O.action](O))};this.runner=this.strategy.connect(0,y)}abortConnecting(){this.runner&&(this.runner.abort(),this.runner=null)}disconnectInternally(){if(this.abortConnecting(),this.clearRetryTimer(),this.clearUnavailableTimer(),this.connection){var y=this.abandonConnection();y.close()}}updateStrategy(){this.strategy=this.options.getStrategy({key:this.key,timeline:this.timeline,useTLS:this.usingTLS})}retryIn(y){this.timeline.info({action:"retry",delay:y}),y>0&&this.emit("connecting_in",Math.round(y/1e3)),this.retryTimer=new ye(y||0,()=>{this.disconnectInternally(),this.connect()})}clearRetryTimer(){this.retryTimer&&(this.retryTimer.ensureAborted(),this.retryTimer=null)}setUnavailableTimer(){this.unavailableTimer=new ye(this.options.unavailableTimeout,()=>{this.updateState("unavailable")})}clearUnavailableTimer(){this.unavailableTimer&&this.unavailableTimer.ensureAborted()}sendActivityCheck(){this.stopActivityCheck(),this.connection.ping(),this.activityTimer=new ye(this.options.pongTimeout,()=>{this.timeline.error({pong_timed_out:this.options.pongTimeout}),this.retryIn(0)})}resetActivityCheck(){this.stopActivityCheck(),this.connection&&!this.connection.handlesActivityChecks()&&(this.activityTimer=new ye(this.activityTimeout,()=>{this.sendActivityCheck()}))}stopActivityCheck(){this.activityTimer&&this.activityTimer.ensureAborted()}buildConnectionCallbacks(y){return Ae({},y,{message:C=>{this.resetActivityCheck(),this.emit("message",C)},ping:()=>{this.send_event("pusher:pong",{})},activity:()=>{this.resetActivityCheck()},error:C=>{this.emit("error",C)},closed:()=>{this.abandonConnection(),this.shouldRetry()&&this.retryIn(1e3)}})}buildHandshakeCallbacks(y){return Ae({},y,{connected:C=>{this.activityTimeout=Math.min(this.options.activityTimeout,C.activityTimeout,C.connection.activityTimeout||1/0),this.clearUnavailableTimer(),this.setConnection(C.connection),this.socket_id=this.connection.id,this.updateState("connected",{socket_id:this.socket_id})}})}buildErrorCallbacks(){let y=C=>O=>{O.error&&this.emit("error",{type:"WebSocketError",error:O.error}),C(O)};return{tls_only:y(()=>{this.usingTLS=!0,this.updateStrategy(),this.retryIn(0)}),refused:y(()=>{this.disconnect()}),backoff:y(()=>{this.retryIn(1e3)}),retry:y(()=>{this.retryIn(0)})}}setConnection(y){this.connection=y;for(var C in this.connectionCallbacks)this.connection.bind(C,this.connectionCallbacks[C]);this.resetActivityCheck()}abandonConnection(){if(this.connection){this.stopActivityCheck();for(var y in this.connectionCallbacks)this.connection.unbind(y,this.connectionCallbacks[y]);var C=this.connection;return this.connection=null,C}}updateState(y,C){var O=this.state;if(this.state=y,O!==y){var I=y;I==="connected"&&(I+=" with new socket ID "+C.socket_id),et.debug("State changed",O+" -> "+I),this.timeline.info({state:y,params:C}),this.emit("state_change",{previous:O,current:y}),this.emit(y,C)}}shouldRetry(){return this.state==="connecting"||this.state==="connected"}}class Cc{constructor(){this.channels={}}add(y,C){return this.channels[y]||(this.channels[y]=Ec(y,C)),this.channels[y]}all(){return un(this.channels)}find(y){return this.channels[y]}remove(y){var C=this.channels[y];return delete this.channels[y],C}disconnect(){_t(this.channels,function(y){y.disconnect()})}}function Ec(_,y){if(_.indexOf("private-encrypted-")===0){if(y.config.nacl)return Mn.createEncryptedChannel(_,y,y.config.nacl);let C="Tried to subscribe to a private-encrypted- channel but no nacl implementation available",O=M.buildLogSuffix("encryptedChannelSupport");throw new ge(`${C}. ${O}`)}else{if(_.indexOf("private-")===0)return Mn.createPrivateChannel(_,y);if(_.indexOf("presence-")===0)return Mn.createPresenceChannel(_,y);if(_.indexOf("#")===0)throw new U('Cannot create a channel with name "'+_+'".');return Mn.createChannel(_,y)}}var bf={createChannels(){return new Cc},createConnectionManager(_,y){return new yf(_,y)},createChannel(_,y){return new qa(_,y)},createPrivateChannel(_,y){return new ar(_,y)},createPresenceChannel(_,y){return new Sc(_,y)},createEncryptedChannel(_,y,C){return new $r(_,y,C)},createTimelineSender(_,y){return new Lr(_,y)},createHandshake(_,y){return new ko(_,y)},createAssistantToTheTransportManager(_,y,C){return new mf(_,y,C)}};const Mn=bf;class vi{constructor(y){this.options=y||{},this.livesLeft=this.options.lives||1/0}getAssistant(y){return Mn.createAssistantToTheTransportManager(this,y,{minPingDelay:this.options.minPingDelay,maxPingDelay:this.options.maxPingDelay})}isAlive(){return this.livesLeft>0}reportDeath(){this.livesLeft-=1}}class dn{constructor(y,C){this.strategies=y,this.loop=!!C.loop,this.failFast=!!C.failFast,this.timeout=C.timeout,this.timeoutLimit=C.timeoutLimit}isSupported(){return ea(this.strategies,He.method("isSupported"))}connect(y,C){var O=this.strategies,I=0,ne=this.timeout,me=null,Me=(Fe,ot)=>{ot?C(null,ot):(I=I+1,this.loop&&(I=I%O.length),I0&&(ne=new ye(O.timeout,function(){me.abort(),I(!0)})),me=y.connect(C,function(Me,Fe){Me&&ne&&ne.isRunning()&&!O.failFast||(ne&&ne.ensureAborted(),I(Me,Fe))}),{abort:function(){ne&&ne.ensureAborted(),me.abort()},forceMinPriority:function(Me){me.forceMinPriority(Me)}}}}class Sn{constructor(y){this.strategies=y}isSupported(){return ea(this.strategies,He.method("isSupported"))}connect(y,C){return _c(this.strategies,y,function(O,I){return function(ne,me){if(I[O].error=ne,ne){vf(I)&&C(!0);return}gt(I,function(Me){Me.forceMinPriority(me.transport.priority)}),C(null,me)}})}}function _c(_,y,C){var O=Ua(_,function(I,ne,me,Me){return I.connect(y,C(ne,Me))});return{abort:function(){gt(O,Do)},forceMinPriority:function(I){gt(O,function(ne){ne.forceMinPriority(I)})}}}function vf(_){return Ba(_,function(y){return!!y.error})}function Do(_){!_.error&&!_.aborted&&(_.abort(),_.aborted=!0)}class Ac{constructor(y,C,O){this.strategy=y,this.transports=C,this.ttl=O.ttl||18e5,this.usingTLS=O.useTLS,this.timeline=O.timeline}isSupported(){return this.strategy.isSupported()}connect(y,C){var O=this.usingTLS,I=Sf(O),ne=I&&I.cacheSkipCount?I.cacheSkipCount:0,me=[this.strategy];if(I&&I.timestamp+this.ttl>=He.now()){var Me=this.transports[I.transport];Me&&(["ws","wss"].includes(I.transport)||ne>3?(this.timeline.info({cached:!0,transport:I.transport,latency:I.latency}),me.push(new dn([Me],{timeout:I.latency*2+1e3,failFast:!0}))):ne++)}var Fe=He.now(),ot=me.pop().connect(y,function pt(Nt,Ra){Nt?(fs(O),me.length>0?(Fe=He.now(),ot=me.pop().connect(y,pt)):C(Nt)):($n(O,Ra.transport.name,He.now()-Fe,ne),C(null,Ra))});return{abort:function(){ot.abort()},forceMinPriority:function(pt){y=pt,ot&&ot.forceMinPriority(pt)}}}}function Nr(_){return"pusherTransport"+(_?"TLS":"NonTLS")}function Sf(_){var y=Be.getLocalStorage();if(y)try{var C=y[Nr(_)];if(C)return JSON.parse(C)}catch{fs(_)}return null}function $n(_,y,C,O){var I=Be.getLocalStorage();if(I)try{I[Nr(_)]=bn({timestamp:He.now(),transport:y,latency:C,cacheSkipCount:O})}catch{}}function fs(_){var y=Be.getLocalStorage();if(y)try{delete y[Nr(_)]}catch{}}class Si{constructor(y,{delay:C}){this.strategy=y,this.options={delay:C}}isSupported(){return this.strategy.isSupported()}connect(y,C){var O=this.strategy,I,ne=new ye(this.options.delay,function(){I=O.connect(y,C)});return{abort:function(){ne.ensureAborted(),I&&I.abort()},forceMinPriority:function(me){y=me,I&&I.forceMinPriority(me)}}}}class rr{constructor(y,C,O){this.test=y,this.trueBranch=C,this.falseBranch=O}isSupported(){var y=this.test()?this.trueBranch:this.falseBranch;return y.isSupported()}connect(y,C){var O=this.test()?this.trueBranch:this.falseBranch;return O.connect(y,C)}}class Ir{constructor(y){this.strategy=y}isSupported(){return this.strategy.isSupported()}connect(y,C){var O=this.strategy.connect(y,function(I,ne){ne&&O.abort(),C(I,ne)});return O}}function Hr(_){return function(){return _.isSupported()}}var xc=function(_,y,C){var O={};function I(Go,Uc,Bc,Bf,Pf){var Pc=C(_,Go,Uc,Bc,Bf,Pf);return O[Go]=Pc,Pc}var ne=Object.assign({},y,{hostNonTLS:_.wsHost+":"+_.wsPort,hostTLS:_.wsHost+":"+_.wssPort,httpPath:_.wsPath}),me=Object.assign({},ne,{useTLS:!0}),Me=Object.assign({},y,{hostNonTLS:_.httpHost+":"+_.httpPort,hostTLS:_.httpHost+":"+_.httpsPort,httpPath:_.httpPath}),Fe={loop:!0,timeout:15e3,timeoutLimit:6e4},ot=new vi({minPingDelay:1e4,maxPingDelay:_.activityTimeout}),pt=new vi({lives:2,minPingDelay:1e4,maxPingDelay:_.activityTimeout}),Nt=I("ws","ws",3,ne,ot),Ra=I("wss","ws",3,me,ot),zf=I("sockjs","sockjs",1,Me),zc=I("xhr_streaming","xhr_streaming",1,Me,pt),jf=I("xdr_streaming","xdr_streaming",1,Me,pt),Bo=I("xhr_polling","xhr_polling",1,Me),jc=I("xdr_polling","xdr_polling",1,Me),Ur=new dn([Nt],Fe),xi=new dn([Ra],Fe),Uf=new dn([zf],Fe),Ti=new dn([new rr(Hr(zc),zc,jf)],Fe),Po=new dn([new rr(Hr(Bo),Bo,jc)],Fe),qo=new dn([new rr(Hr(Ti),new Sn([Ti,new Si(Po,{delay:4e3})]),Po)],Fe),Ri=new rr(Hr(qo),qo,Uf),ki;return y.useTLS?ki=new Sn([Ur,new Si(Ri,{delay:2e3})]):ki=new Sn([Ur,new Si(xi,{delay:2e3}),new Si(Ri,{delay:5e3})]),new Ac(new Ir(new rr(Hr(Nt),ki,Ri)),O,{ttl:18e5,timeline:y.timeline,useTLS:y.useTLS})};const zr=xc;function wf(){var _=this;_.timeline.info(_.buildTimelineMessage({transport:_.name+(_.options.useTLS?"s":"")})),_.hooks.isInitialized()?_.changeState("initialized"):_.hooks.file?(_.changeState("initializing"),A.load(_.hooks.file,{useTLS:_.options.useTLS},function(y,C){_.hooks.isInitialized()?(_.changeState("initialized"),C(!0)):(y&&_.onError(y),_.onClose(),C(!1))})):_.onClose()}var Tc={getRequest:function(_){var y=new window.XDomainRequest;return y.ontimeout=function(){_.emit("error",new j),_.close()},y.onerror=function(C){_.emit("error",C),_.close()},y.onprogress=function(){y.responseText&&y.responseText.length>0&&_.onChunk(200,y.responseText)},y.onload=function(){y.responseText&&y.responseText.length>0&&_.onChunk(200,y.responseText),_.emit("finished",200),_.close()},y},abortRequest:function(_){_.ontimeout=_.onerror=_.onprogress=_.onload=null,_.abort()}};const Rc=Tc,Oo=256*1024;class Cf extends je{constructor(y,C,O){super(),this.hooks=y,this.method=C,this.url=O}start(y){this.position=0,this.xhr=this.hooks.getRequest(this),this.unloader=()=>{this.close()},Be.addUnloadListener(this.unloader),this.xhr.open(this.method,this.url,!0),this.xhr.setRequestHeader&&this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.send(y)}close(){this.unloader&&(Be.removeUnloadListener(this.unloader),this.unloader=null),this.xhr&&(this.hooks.abortRequest(this.xhr),this.xhr=null)}onChunk(y,C){for(;;){var O=this.advanceBuffer(C);if(O)this.emit("chunk",{status:y,data:O});else break}this.isBufferTooLong(C)&&this.emit("buffer_too_long")}advanceBuffer(y){var C=y.slice(this.position),O=C.indexOf(` +`);return O!==-1?(this.position+=O+1,C.slice(0,O)):null}isBufferTooLong(y){return this.position===y.length&&y.length>Oo}}var Lo;(function(_){_[_.CONNECTING=0]="CONNECTING",_[_.OPEN=1]="OPEN",_[_.CLOSED=3]="CLOSED"})(Lo||(Lo={}));const na=Lo;var Aa=1;class Mo{constructor(y,C){this.hooks=y,this.session=hs(1e3)+"/"+Dc(8),this.location=$o(C),this.readyState=na.CONNECTING,this.openStream()}send(y){return this.sendRaw(JSON.stringify([y]))}ping(){this.hooks.sendHeartbeat(this)}close(y,C){this.onClose(y,C,!0)}sendRaw(y){if(this.readyState===na.OPEN)try{return Be.createSocketRequest("POST",ir(jr(this.location,this.session))).start(y),!0}catch{return!1}else return!1}reconnect(){this.closeStream(),this.openStream()}onClose(y,C,O){this.closeStream(),this.readyState=na.CLOSED,this.onclose&&this.onclose({code:y,reason:C,wasClean:O})}onChunk(y){if(y.status===200){this.readyState===na.OPEN&&this.onActivity();var C,O=y.data.slice(0,1);switch(O){case"o":C=JSON.parse(y.data.slice(1)||"{}"),this.onOpen(C);break;case"a":C=JSON.parse(y.data.slice(1)||"[]");for(var I=0;I{this.onChunk(y)}),this.stream.bind("finished",y=>{this.hooks.onFinished(this,y)}),this.stream.bind("buffer_too_long",()=>{this.reconnect()});try{this.stream.start()}catch(y){He.defer(()=>{this.onError(y),this.onClose(1006,"Could not start streaming",!1)})}}closeStream(){this.stream&&(this.stream.unbind_all(),this.stream.close(),this.stream=null)}}function $o(_){var y=/([^\?]*)\/*(\??.*)/.exec(_);return{base:y[1],queryString:y[2]}}function jr(_,y){return _.base+"/"+y+"/xhr_send"}function ir(_){var y=_.indexOf("?")===-1?"?":"&";return _+y+"t="+ +new Date+"&n="+Aa++}function kc(_,y){var C=/(https?:\/\/)([^\/:]+)((\/|:)?.*)/.exec(_);return C[1]+y+C[3]}function hs(_){return Be.randomInt(_)}function Dc(_){for(var y=[],C=0;C<_;C++)y.push(hs(32).toString(32));return y.join("")}const wi=Mo;var xa={getReceiveURL:function(_,y){return _.base+"/"+y+"/xhr_streaming"+_.queryString},onHeartbeat:function(_){_.sendRaw("[]")},sendHeartbeat:function(_){_.sendRaw("[]")},onFinished:function(_,y){_.onClose(1006,"Connection interrupted ("+y+")",!1)}};const No=xa;var Ci={getReceiveURL:function(_,y){return _.base+"/"+y+"/xhr"+_.queryString},onHeartbeat:function(){},sendHeartbeat:function(_){_.sendRaw("[]")},onFinished:function(_,y){y===200?_.reconnect():_.onClose(1006,"Connection interrupted ("+y+")",!1)}};const Ga=Ci;var Io={getRequest:function(_){var y=Be.getXHRAPI(),C=new y;return C.onreadystatechange=C.onprogress=function(){switch(C.readyState){case 3:C.responseText&&C.responseText.length>0&&_.onChunk(C.status,C.responseText);break;case 4:C.responseText&&C.responseText.length>0&&_.onChunk(C.status,C.responseText),_.emit("finished",C.status),_.close();break}},C},abortRequest:function(_){_.onreadystatechange=null,_.abort()}};const gs=Io;var Oc={createStreamingSocket(_){return this.createSocket(No,_)},createPollingSocket(_){return this.createSocket(Ga,_)},createSocket(_,y){return new wi(_,y)},createXHR(_,y){return this.createRequest(gs,_,y)},createRequest(_,y,C){return new Cf(_,y,C)}};const Ei=Oc;Ei.createXDR=function(_,y){return this.createRequest(Rc,_,y)};var Lc={nextAuthCallbackID:1,auth_callbacks:{},ScriptReceivers:m,DependenciesReceivers:E,getDefaultStrategy:zr,Transports:vn,transportConnectionInitializer:wf,HTTPFactory:Ei,TimelineTransport:q,getXHRAPI(){return window.XMLHttpRequest},getWebSocketAPI(){return window.WebSocket||window.MozWebSocket},setup(_){window.Pusher=_;var y=()=>{this.onDocumentBody(_.ready)};window.JSON?y():A.load("json2",{},y)},getDocument(){return document},getProtocol(){return this.getDocument().location.protocol},getAuthorizers(){return{ajax:Ye,jsonp:En}},onDocumentBody(_){document.body?_():setTimeout(()=>{this.onDocumentBody(_)},0)},createJSONPRequest(_,y){return new Ea(_,y)},createScriptRequest(_){return new nr(_)},getLocalStorage(){try{return window.localStorage}catch{return}},createXHR(){return this.getXHRAPI()?this.createXMLHttpRequest():this.createMicrosoftXHR()},createXMLHttpRequest(){var _=this.getXHRAPI();return new _},createMicrosoftXHR(){return new ActiveXObject("Microsoft.XMLHTTP")},getNetwork(){return Ro},createWebSocket(_){var y=this.getWebSocketAPI();return new y(_)},createSocketRequest(_,y){if(this.isXHRSupported())return this.HTTPFactory.createXHR(_,y);if(this.isXDRSupported(y.indexOf("https:")===0))return this.HTTPFactory.createXDR(_,y);throw"Cross-origin HTTP requests are not supported"},isXHRSupported(){var _=this.getXHRAPI();return!!_&&new _().withCredentials!==void 0},isXDRSupported(_){var y=_?"https:":"http:",C=this.getProtocol();return!!window.XDomainRequest&&C===y},addUnloadListener(_){window.addEventListener!==void 0?window.addEventListener("unload",_,!1):window.attachEvent!==void 0&&window.attachEvent("onunload",_)},removeUnloadListener(_){window.addEventListener!==void 0?window.removeEventListener("unload",_,!1):window.detachEvent!==void 0&&window.detachEvent("onunload",_)},randomInt(_){return Math.floor(function(){return(window.crypto||window.msCrypto).getRandomValues(new Uint32Array(1))[0]/Math.pow(2,32)}()*_)}};const Be=Lc;var Ta;(function(_){_[_.ERROR=3]="ERROR",_[_.INFO=6]="INFO",_[_.DEBUG=7]="DEBUG"})(Ta||(Ta={}));const sr=Ta;class _i{constructor(y,C,O){this.key=y,this.session=C,this.events=[],this.options=O||{},this.sent=0,this.uniqueID=0}log(y,C){y<=this.options.level&&(this.events.push(Ae({},C,{timestamp:He.now()})),this.options.limit&&this.events.length>this.options.limit&&this.events.shift())}error(y){this.log(sr.ERROR,y)}info(y){this.log(sr.INFO,y)}debug(y){this.log(sr.DEBUG,y)}isEmpty(){return this.events.length===0}send(y,C){var O=Ae({session:this.session,bundle:this.sent+1,key:this.key,lib:"js",version:this.options.version,cluster:this.options.cluster,features:this.options.features,timeline:this.events},this.options.params);return this.events=[],y(O,(I,ne)=>{I||this.sent++,C&&C(I,ne)}),!0}generateUniqueID(){return this.uniqueID++,this.uniqueID}}class Ef{constructor(y,C,O,I){this.name=y,this.priority=C,this.transport=O,this.options=I||{}}isSupported(){return this.transport.isSupported({useTLS:this.options.useTLS})}connect(y,C){if(this.isSupported()){if(this.priority{O||(pt(),ne?ne.close():I.close())},forceMinPriority:Nt=>{O||this.priority{var C="socket_id="+encodeURIComponent(_.socketId);for(var O in y.params)C+="&"+encodeURIComponent(O)+"="+encodeURIComponent(y.params[O]);if(y.paramsProvider!=null){let I=y.paramsProvider();for(var O in I)C+="&"+encodeURIComponent(O)+"="+encodeURIComponent(I[O])}return C},Af=_=>{if(typeof Be.getAuthorizers()[_.transport]>"u")throw`'${_.transport}' is not a recognized auth transport`;return(y,C)=>{const O=_f(y,_);Be.getAuthorizers()[_.transport](Be,O,_,R.UserAuthentication,C)}},jo=(_,y)=>{var C="socket_id="+encodeURIComponent(_.socketId);C+="&channel_name="+encodeURIComponent(_.channelName);for(var O in y.params)C+="&"+encodeURIComponent(O)+"="+encodeURIComponent(y.params[O]);if(y.paramsProvider!=null){let I=y.paramsProvider();for(var O in I)C+="&"+encodeURIComponent(O)+"="+encodeURIComponent(I[O])}return C},xf=_=>{if(typeof Be.getAuthorizers()[_.transport]>"u")throw`'${_.transport}' is not a recognized auth transport`;return(y,C)=>{const O=jo(y,_);Be.getAuthorizers()[_.transport](Be,O,_,R.ChannelAuthorization,C)}},Tf=(_,y,C)=>{const O={authTransport:y.transport,authEndpoint:y.endpoint,auth:{params:y.params,headers:y.headers}};return(I,ne)=>{const me=_.channel(I.channelName);C(me,O).authorize(I.socketId,ne)}};function $c(_,y){let C={activityTimeout:_.activityTimeout||v.activityTimeout,cluster:_.cluster,httpPath:_.httpPath||v.httpPath,httpPort:_.httpPort||v.httpPort,httpsPort:_.httpsPort||v.httpsPort,pongTimeout:_.pongTimeout||v.pongTimeout,statsHost:_.statsHost||v.stats_host,unavailableTimeout:_.unavailableTimeout||v.unavailableTimeout,wsPath:_.wsPath||v.wsPath,wsPort:_.wsPort||v.wsPort,wssPort:_.wssPort||v.wssPort,enableStats:Of(_),httpHost:Rf(_),useTLS:Df(_),wsHost:Nc(_),userAuthenticator:Uo(_),channelAuthorizer:Mf(_,y)};return"disabledTransports"in _&&(C.disabledTransports=_.disabledTransports),"enabledTransports"in _&&(C.enabledTransports=_.enabledTransports),"ignoreNullOrigin"in _&&(C.ignoreNullOrigin=_.ignoreNullOrigin),"timelineParams"in _&&(C.timelineParams=_.timelineParams),"nacl"in _&&(C.nacl=_.nacl),C}function Rf(_){return _.httpHost?_.httpHost:_.cluster?`sockjs-${_.cluster}.pusher.com`:v.httpHost}function Nc(_){return _.wsHost?_.wsHost:kf(_.cluster)}function kf(_){return`ws-${_}.pusher.com`}function Df(_){return Be.getProtocol()==="https:"?!0:_.forceTLS!==!1}function Of(_){return"enableStats"in _?_.enableStats:"disableStats"in _?!_.disableStats:!1}const Ic=_=>"customHandler"in _&&_.customHandler!=null;function Uo(_){const y=Object.assign(Object.assign({},v.userAuthentication),_.userAuthentication);return Ic(y)?y.customHandler:Af(y)}function Lf(_,y){let C;if("channelAuthorization"in _)C=Object.assign(Object.assign({},v.channelAuthorization),_.channelAuthorization);else if(C={transport:_.authTransport||v.authTransport,endpoint:_.authEndpoint||v.authEndpoint},"auth"in _&&("params"in _.auth&&(C.params=_.auth.params),"headers"in _.auth&&(C.headers=_.auth.headers)),"authorizer"in _)return{customHandler:Tf(y,C,_.authorizer)};return C}function Mf(_,y){const C=Lf(_,y);return Ic(C)?C.customHandler:xf(C)}class $f extends je{constructor(y){super(function(C,O){et.debug(`No callbacks on watchlist events for ${C}`)}),this.pusher=y,this.bindWatchlistInternalEvent()}handleEvent(y){y.data.events.forEach(C=>{this.emit(C.name,C)})}bindWatchlistInternalEvent(){this.pusher.connection.bind("message",y=>{var C=y.event;C==="pusher_internal:watchlist_events"&&this.handleEvent(y)})}}function Hc(){let _,y;return{promise:new Promise((O,I)=>{_=O,y=I}),resolve:_,reject:y}}const Nf=Hc;class If extends je{constructor(y){super(function(C,O){et.debug("No callbacks on user for "+C)}),this.signin_requested=!1,this.user_data=null,this.serverToUserChannel=null,this.signinDonePromise=null,this._signinDoneResolve=null,this._onAuthorize=(C,O)=>{if(C){et.warn(`Error during signin: ${C}`),this._cleanup();return}this.pusher.send_event("pusher:signin",{auth:O.auth,user_data:O.user_data})},this.pusher=y,this.pusher.connection.bind("state_change",({previous:C,current:O})=>{C!=="connected"&&O==="connected"&&this._signin(),C==="connected"&&O!=="connected"&&(this._cleanup(),this._newSigninPromiseIfNeeded())}),this.watchlist=new $f(y),this.pusher.connection.bind("message",C=>{var O=C.event;O==="pusher:signin_success"&&this._onSigninSuccess(C.data),this.serverToUserChannel&&this.serverToUserChannel.name===C.channel&&this.serverToUserChannel.handleEvent(C)})}signin(){this.signin_requested||(this.signin_requested=!0,this._signin())}_signin(){this.signin_requested&&(this._newSigninPromiseIfNeeded(),this.pusher.connection.state==="connected"&&this.pusher.config.userAuthenticator({socketId:this.pusher.connection.socket_id},this._onAuthorize))}_onSigninSuccess(y){try{this.user_data=JSON.parse(y.user_data)}catch{et.error(`Failed parsing user data after signin: ${y.user_data}`),this._cleanup();return}if(typeof this.user_data.id!="string"||this.user_data.id===""){et.error(`user_data doesn't contain an id. user_data: ${this.user_data}`),this._cleanup();return}this._signinDoneResolve(),this._subscribeChannels()}_subscribeChannels(){const y=C=>{C.subscriptionPending&&C.subscriptionCancelled?C.reinstateSubscription():!C.subscriptionPending&&this.pusher.connection.state==="connected"&&C.subscribe()};this.serverToUserChannel=new qa(`#server-to-user-${this.user_data.id}`,this.pusher),this.serverToUserChannel.bind_global((C,O)=>{C.indexOf("pusher_internal:")===0||C.indexOf("pusher:")===0||this.emit(C,O)}),y(this.serverToUserChannel)}_cleanup(){this.user_data=null,this.serverToUserChannel&&(this.serverToUserChannel.unbind_all(),this.serverToUserChannel.disconnect(),this.serverToUserChannel=null),this.signin_requested&&this._signinDoneResolve()}_newSigninPromiseIfNeeded(){if(!this.signin_requested||this.signinDonePromise&&!this.signinDonePromise.done)return;const{promise:y,resolve:C}=Nf();y.done=!1;const O=()=>{y.done=!0};y.then(O).catch(O),this.signinDonePromise=y,this._signinDoneResolve=C}}class rn{static ready(){rn.isReady=!0;for(var y=0,C=rn.instances.length;yBe.getDefaultStrategy(this.config,I,Ai);this.connection=Mn.createConnectionManager(this.key,{getStrategy:O,timeline:this.timeline,activityTimeout:this.config.activityTimeout,pongTimeout:this.config.pongTimeout,unavailableTimeout:this.config.unavailableTimeout,useTLS:!!this.config.useTLS}),this.connection.bind("connected",()=>{this.subscribeAll(),this.timelineSender&&this.timelineSender.send(this.connection.isUsingTLS())}),this.connection.bind("message",I=>{var ne=I.event,me=ne.indexOf("pusher_internal:")===0;if(I.channel){var Me=this.channel(I.channel);Me&&Me.handleEvent(I)}me||this.global_emitter.emit(I.event,I.data)}),this.connection.bind("connecting",()=>{this.channels.disconnect()}),this.connection.bind("disconnected",()=>{this.channels.disconnect()}),this.connection.bind("error",I=>{et.warn(I)}),rn.instances.push(this),this.timeline.info({instances:rn.instances.length}),this.user=new If(this),rn.isReady&&this.connect()}switchCluster(y){const{appKey:C,cluster:O}=y;this.key=C,this.options=Object.assign(Object.assign({},this.options),{cluster:O}),this.config=$c(this.options,this),this.connection.switchCluster(this.key)}channel(y){return this.channels.find(y)}allChannels(){return this.channels.all()}connect(){if(this.connection.connect(),this.timelineSender&&!this.timelineSenderTimer){var y=this.connection.isUsingTLS(),C=this.timelineSender;this.timelineSenderTimer=new Ne(6e4,function(){C.send(y)})}}disconnect(){this.connection.disconnect(),this.timelineSenderTimer&&(this.timelineSenderTimer.ensureAborted(),this.timelineSenderTimer=null)}bind(y,C,O){return this.global_emitter.bind(y,C,O),this}unbind(y,C,O){return this.global_emitter.unbind(y,C,O),this}bind_global(y){return this.global_emitter.bind_global(y),this}unbind_global(y){return this.global_emitter.unbind_global(y),this}unbind_all(y){return this.global_emitter.unbind_all(),this}subscribeAll(){var y;for(y in this.channels.channels)this.channels.channels.hasOwnProperty(y)&&this.subscribe(y)}subscribe(y){var C=this.channels.add(y,this);return C.subscriptionPending&&C.subscriptionCancelled?C.reinstateSubscription():!C.subscriptionPending&&this.connection.state==="connected"&&C.subscribe(),C}unsubscribe(y){var C=this.channels.find(y);C&&C.subscriptionPending?C.cancelSubscription():(C=this.channels.remove(y),C&&C.subscribed&&C.unsubscribe())}send_event(y,C,O){return this.connection.send_event(y,C,O)}shouldUseTLS(){return this.config.useTLS}signin(){this.user.signin()}}rn.instances=[],rn.isReady=!1,rn.logToConsole=!1,rn.Runtime=Be,rn.ScriptReceivers=Be.ScriptReceivers,rn.DependenciesReceivers=Be.DependenciesReceivers,rn.auth_callbacks=Be.auth_callbacks;const ys=rn;function Hf(_){if(_==null)throw"You must pass your app key when you instantiate Pusher."}Be.setup(rn)}},r={};function i(u){var d=r[u];if(d!==void 0)return d.exports;var f=r[u]={exports:{}};return a[u].call(f.exports,f,f.exports,i),f.exports}i.d=(u,d)=>{for(var f in d)i.o(d,f)&&!i.o(u,f)&&Object.defineProperty(u,f,{enumerable:!0,get:d[f]})},i.o=(u,d)=>Object.prototype.hasOwnProperty.call(u,d);var l=i(721);return l})())})(cf)),cf.exports}var M_=L_();const ny=Xa(M_),Ht=ct("WebSocketManager");class $_{constructor(){$(this,"echo",null);$(this,"subscriptions",new Map);$(this,"initialized",!1);$(this,"config",null)}configure(e){if(this.initialized){Ht.warn("[WebSocketManager] 이미 초기화되어 설정을 변경할 수 없습니다.");return}this.config=e,Ht.log("[WebSocketManager] 설정 완료:",{appKey:e.appKey?"***":"(없음)",host:e.host,port:e.port,scheme:e.scheme})}initialize(){if(this.initialized)return;if(!this.config||!this.config.appKey){Ht.warn("[WebSocketManager] WebSocket 설정이 없습니다. initTemplateApp에서 websocket 옵션을 전달해주세요.");return}const{appKey:e,host:a="localhost",port:r=80,scheme:i="https",authEndpoint:l="/api/broadcasting/auth"}=this.config,u=Number(r)||80,d=i==="https";Ht.log("[WebSocketManager] 연결 설정:",{host:a,port:u,scheme:i,useTLS:d,authEndpoint:l}),window.Pusher=ny;const f=localStorage.getItem("auth_token"),g={wsHost:a,wsPort:u,wssPort:u,forceTLS:d,disableStats:!0,enabledTransports:["ws","wss"],cluster:"mt1",authEndpoint:l,auth:{headers:{Authorization:f?`Bearer ${f}`:"",Accept:"application/json"}}};Ht.log("[WebSocketManager] Pusher 옵션:",g);const m=new ny(e,g);m.connection.bind("connecting",()=>{Ht.log("[WebSocketManager] 연결 시도 중...")}),m.connection.bind("connected",()=>{Ht.log("[WebSocketManager] 연결 성공! Socket ID:",m.connection.socket_id)}),m.connection.bind("failed",()=>{Ht.error("[WebSocketManager] 연결 실패 - WebSocket을 사용할 수 없습니다.")}),m.connection.bind("error",b=>{Ht.error("[WebSocketManager] 연결 오류:",b)}),m.connection.bind("state_change",b=>{Ht.log(`[WebSocketManager] 상태 변경: ${b.previous} → ${b.current}`)}),m.connection.bind("unavailable",()=>{Ht.error("[WebSocketManager] WebSocket 사용 불가 - 연결할 수 없습니다.")}),m.connection.bind("disconnected",()=>{Ht.warn("[WebSocketManager] 연결이 끊어졌습니다.")}),Ht.log("[WebSocketManager] 초기 연결 상태:",m.connection.state),this.echo=new O_({broadcaster:"reverb",client:m}),Ht.log("[WebSocketManager] 연결 시작 시도..."),m.connect(),window.Echo=this.echo,this.initialized=!0,Ht.log("[WebSocketManager] Echo 초기화 완료")}subscribe(e,a,r,i={}){if(this.initialize(),!this.echo)return Ht.warn("[WebSocketManager] Echo가 초기화되지 않았습니다."),"";const{channelType:l="private"}=i,u=`${e}:${a}`;if(this.subscriptions.has(u))return Ht.log(`[WebSocketManager] 이미 구독 중: ${u}`),u;let d;switch(l){case"public":d=this.echo.channel(e);break;case"presence":d=this.echo.join(e);break;default:d=this.echo.private(e)}return d.listen(`.${a}`,r),this.subscriptions.set(u,d),Ht.log(`[WebSocketManager] 구독 완료: ${u} (${l})`),u}unsubscribe(e){const a=this.subscriptions.get(e);if(!a)return;const r=e.lastIndexOf(":"),i=r>=0?e.substring(r+1):"";if(i)try{a.stopListening(`.${i}`)}catch(l){Ht.warn(`[WebSocketManager] stopListening 실패: ${e}`,l)}this.subscriptions.delete(e),Ht.log(`[WebSocketManager] 구독 해제: ${e}`)}leaveChannel(e){if(!this.echo)return;this.echo.leave(e);const a=[];this.subscriptions.forEach((r,i)=>{i.startsWith(`${e}:`)&&a.push(i)}),a.forEach(r=>this.subscriptions.delete(r)),Ht.log(`[WebSocketManager] 채널 구독 해제: ${e}`)}disconnect(){this.echo&&(this.echo.disconnect(),this.subscriptions.clear(),this.initialized=!1,Ht.log("[WebSocketManager] 연결 종료"))}getEcho(){return this.echo}isInitialized(){return this.initialized}isConfigured(){return this.config!==null&&!!this.config.appKey}getSubscriptionCount(){return this.subscriptions.size}}const hc=new $_,bt=ct("DataSourceManager"),N_=new wn;function gc(o,e){const a=o.match(/^\{\{(.+)\}\}$/);if(a){const r=a[1].trim();try{return N_.evaluateExpression(r,e)}catch(i){return bt.error("Failed to evaluate param expression:",r,i),o}}return Qi(o,e,{skipCache:!0})}function ay(o){const e={};for(const a of o.keys()){if(a in e)continue;const r=o.getAll(a);r.length>1?e[a]=r:r.length===1&&(a.endsWith("[]")?e[a]=r:e[a]=r[0])}return e}function I_(){try{return window.G7Core?.devTools}catch{return}}const bc=class bc{constructor(e={}){$(this,"options");$(this,"dataCache",new Map);$(this,"bindingEngine");$(this,"globalHeaders",[]);this.options=e,this.bindingEngine=new wn}setGlobalHeaders(e){this.globalHeaders=e||[]}matchesPattern(e,a){if(a==="*")return!0;const r=a.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`).test(e)}toFormData(e){const a=new FormData;for(const[r,i]of Object.entries(e))i instanceof File||i instanceof Blob?a.append(r,i):i!=null&&a.append(r,typeof i=="object"?JSON.stringify(i):String(i));return a}getMatchingGlobalHeaders(e,a){const r={};for(const i of this.globalHeaders)this.matchesPattern(e,i.pattern)&&Object.entries(i.headers).forEach(([l,u])=>{const d=gc(u,a);d!=null&&d!==""&&(r[l]=String(d))});return r}filterByCondition(e,a={}){const r=new Set,i=[];for(const l of e){if(r.has(l.id))continue;if(!l.if&&!l.conditions){r.add(l.id),i.push(l);continue}Ji({if:l.if,conditions:l.conditions},a,this.bindingEngine,`data_source:${l.id}`)&&(r.add(l.id),i.push(l))}return i}checkErrorCondition(e,a){if(!e.errorCondition?.if||e.errorCondition?.errorCode===void 0)return null;try{const r={response:a};if(Ji({if:e.errorCondition.if},r,this.bindingEngine,`errorCondition:${e.id}`))return bt.log(`errorCondition matched for ${e.id}, triggering error ${e.errorCondition.errorCode}`),e.errorCondition.errorCode}catch(r){bt.error(`Failed to evaluate errorCondition for ${e.id}:`,r)}return null}async fetchDataSources(e,a={},r=new URLSearchParams,i,l){const u={},d=!!this.options.sampleProvider,f=e.filter(g=>(d||g.auto_fetch!==!1)&&g.type!=="websocket");return await Promise.all(f.map(async g=>{try{const m=await this.fetchDataSource(g,a,r,i,l);u[g.id]=m,this.dataCache.set(g.id,{data:m,timestamp:Date.now()});const b=this.checkErrorCondition(g,m);if(b!==null){const v={status:b,message:`Error condition matched for ${g.id}`,data:m},S=pa(),E=S.resolve(b,{errorHandling:g.errorHandling,onError:g.onError});if(E.handler)try{await S.execute(E.handler,v)}catch(A){bt.error(`Error executing errorCondition handler for ${g.id}:`,A)}g.fallback!==void 0&&(u[g.id]=g.fallback,this.dataCache.set(g.id,{data:g.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${g.id} (errorCondition)`));return}if(g.onSuccess)try{const v={data:m,sourceId:g.id};await this.executeOnSuccessHandler(g.onSuccess,v)}catch(v){bt.error(`Error executing onSuccess handler for ${g.id}:`,v)}}catch(m){bt.error(`Failed to fetch data source: ${g.id}`,m);const b=m?.response?.status||m?.status||500,v=m?.response?.data,S=v?.message||m.message||"Unknown error",E={status:b,message:S,errors:v?.errors,data:v,statusText:m?.response?.statusText},A=pa(),T=A.resolve(b,{errorHandling:g.errorHandling,onError:g.onError});if(T.handler)try{await A.execute(T.handler,E)}catch(L){bt.error(`Error executing error handler for ${g.id}:`,L)}g.fallback!==void 0&&(u[g.id]=g.fallback,this.dataCache.set(g.id,{data:g.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${g.id}`)),this.options.onError&&this.options.onError(m,g)}})),u}async fetchDataSourcesWithResults(e,a={},r=new URLSearchParams,i,l,u){if(e.some(m=>m.type==="websocket")){const m=e.filter(b=>b.type==="websocket").map(b=>b.id).join(", ");bt.warn(`fetchDataSourcesWithResults received WebSocket sources (caller should filter them out before calling): ${m}. WebSocket sources are event listeners, not data providers — they should not be passed to fetch.`)}const f=u?.ignoreAutoFetch?e.filter(m=>m.type!=="websocket"):e.filter(m=>m.auto_fetch!==!1&&m.type!=="websocket");return await Promise.all(f.map(async m=>{try{const b=await this.fetchDataSource(m,a,r,i,l);this.dataCache.set(m.id,{data:b,timestamp:Date.now()});const v=this.checkErrorCondition(m,b);if(v!==null){const S={status:v,message:`Error condition matched for ${m.id}`,data:b},E=pa(),A=E.resolve(v,{errorHandling:m.errorHandling,onError:m.onError});let T=!1;if(A.handler)if(m.loading_strategy==="blocking")E.execute(A.handler,S).catch(L=>{bt.error(`Error executing errorCondition handler for ${m.id}:`,L)}),T=!0;else try{await E.execute(A.handler,S),T=!0}catch(L){bt.error(`Error executing errorCondition handler for ${m.id}:`,L)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${m.id} (errorCondition)`),{id:m.id,state:"success",data:m.fallback}):{id:m.id,state:"error",error:new Error(`Error condition matched: ${v}`),errorCode:v,errorHandled:T}}if(m.onSuccess)try{const S={data:b,sourceId:m.id};await this.executeOnSuccessHandler(m.onSuccess,S)}catch(S){bt.error(`Error executing onSuccess handler for ${m.id}:`,S)}return{id:m.id,state:"success",data:b}}catch(b){bt.error(`Failed to fetch data source: ${m.id}`,b);const v=b?.response?.status||b?.status||500,S=b?.response?.data,E=S?.message||b.message||"Unknown error",A={status:v,message:E,errors:S?.errors,data:S,statusText:b?.response?.statusText},T=pa(),L=T.resolve(v,{errorHandling:m.errorHandling,onError:m.onError});let M=!1;if(L.handler)if(m.loading_strategy==="blocking")T.execute(L.handler,A).catch(R=>{bt.error(`Error executing error handler for ${m.id}:`,R)}),M=!0;else try{await T.execute(L.handler,A),M=!0}catch(R){bt.error(`Error executing error handler for ${m.id}:`,R)}return m.fallback!==void 0?(this.dataCache.set(m.id,{data:m.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${m.id}`),{id:m.id,state:"success",data:m.fallback}):(this.options.onError&&this.options.onError(b,m),{id:m.id,state:"error",error:b,errorCode:v,errorHandled:M})}}))}fetchDataSourcesInBackground(e,a={},r=new URLSearchParams,i){e.filter(u=>u.auto_fetch!==!1&&u.loading_strategy==="background"&&u.type!=="websocket").forEach(u=>{this.fetchDataSource(u,a,r).then(async d=>{this.dataCache.set(u.id,{data:d,timestamp:Date.now()});const f=this.checkErrorCondition(u,d);if(f!==null){const g={status:f,message:`Error condition matched for ${u.id}`,data:d},m=pa(),b=m.resolve(f,{errorHandling:u.errorHandling,onError:u.onError});if(b.handler)try{await m.execute(b.handler,g)}catch(v){bt.error(`Error executing errorCondition handler for ${u.id}:`,v)}u.fallback!==void 0&&(this.dataCache.set(u.id,{data:u.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${u.id} (errorCondition)`),i&&i(u.id,u.fallback));return}if(u.onSuccess)try{const g={data:d,sourceId:u.id};await this.executeOnSuccessHandler(u.onSuccess,g)}catch(g){bt.error(`Error executing onSuccess handler for ${u.id}:`,g)}i&&i(u.id,d)}).catch(async d=>{bt.error(`Background fetch failed: ${u.id}`,d);const f=d?.response?.status||d?.status||500,g=d?.response?.data,m=g?.message||d.message||"Unknown error",b={status:f,message:m,errors:g?.errors,data:g,statusText:d?.response?.statusText},v=pa(),S=v.resolve(f,{errorHandling:u.errorHandling,onError:u.onError});if(S.handler)try{await v.execute(S.handler,b)}catch(E){bt.error(`Error executing error handler for ${u.id}:`,E)}if(u.fallback!==void 0){this.dataCache.set(u.id,{data:u.fallback,timestamp:Date.now()}),bt.log(`Using fallback data for ${u.id}`),i&&i(u.id,u.fallback);return}this.options.onError&&this.options.onError(d,u)})})}async fetchDataSource(e,a,r,i,l){switch(e.type){case"api":return this.fetchApiDataSource(e,a,r,i,l);case"static":return e.data;case"route_params":return a;case"query_params":return ay(r);default:throw new Error(`Unknown data source type: ${e.type}`)}}async fetchApiDataSource(e,a,r,i,l){if(this.options.sampleProvider?.has(e.id))return await Promise.resolve(this.options.sampleProvider.resolve(e));if(!e.endpoint)throw new Error(`API data source ${e.id} has no endpoint`);const u={route:a,query:ay(r)};i&&(u._global=i),l&&(u._local=l);let d=Qi(e.endpoint,u,{skipCache:!0});const f=e.method||"GET",m=(e.contentType?typeof e.contentType=="string"&&e.contentType.includes("{{")?Qi(e.contentType,u,{skipCache:!0}):e.contentType:"application/json")==="multipart/form-data";let b=m?{...e.params||{}}:JSON.parse(JSON.stringify(e.params||{}));Object.entries(b).forEach(([E,A])=>{typeof A=="string"&&(b[E]=gc(A,u))}),Object.keys(b).forEach(E=>{b[E]===""&&delete b[E]});const v=I_();let S=null;if(v?.isEnabled()){v.trackDataSourceDefinition({id:e.id,type:e.type,endpoint:d,method:f,autoFetch:e.auto_fetch,initLocal:e.initLocal,initGlobal:e.initGlobal});let E=d;if(f==="GET"&&Object.keys(b).length>0){const A=new URLSearchParams;Object.entries(b).forEach(([L,M])=>{Array.isArray(M)?M.forEach(R=>A.append(L,String(R))):M!=null&&A.append(L,String(M))});const T=A.toString();E=T?`${d}?${T}`:d}S=v.trackRequest(E,f,{requestBody:f!=="GET"?b:void 0,dataSourceId:e.id}),v.trackDataSourceLoading(e.id)}try{let E;const A=e.auth_mode??(e.auth_required===!0?"required":"none"),T=ha.getInstance().isAuthenticated();if(A==="required"&&!T){bt.log(`[DataSourceManager] Skipping ${e.id}: auth_mode is 'required' but no token available`),v?.isEnabled()&&(v.trackDataSourceError(e.id,"Skipped: auth_mode required but not authenticated"),S&&v.failRequest(S,"Skipped: not authenticated"));const M=new Error("Auth required but not authenticated");throw M.response={status:401,statusText:"Unauthorized",data:null},M.status=401,M._authSkipped=!0,M}const L=A==="required"||A==="optional"&&T;if(L){const M=Na(),R=d.startsWith("/api/")?d.substring(4):d.startsWith("/api")?d.substring(4)||"/":d;this.options.onError&&bt.log(`[DataSourceManager] Normalized endpoint: ${d} -> ${R}`);const D=this.getMatchingGlobalHeaders(d,u),U={};e.headers&&Object.entries(e.headers).forEach(([ge,Ee])=>{const Ce=gc(Ee,u);Ce!=null&&Ce!==""&&(U[ge]=String(Ce))});const j={...D,...U},P=Object.keys(j).length>0,X=m?this.toFormData(b):b;switch(f){case"GET":E=await M.get(R,{params:b,...P&&{headers:j}});break;case"POST":P?E=await M.post(R,X,{headers:j}):E=await M.post(R,X);break;case"PUT":P?E=await M.put(R,X,{headers:j}):E=await M.put(R,X);break;case"PATCH":P?E=await M.patch(R,X,{headers:j}):E=await M.patch(R,X);break;case"DELETE":E=await M.delete(R,{params:b,...P&&{headers:j}});break;default:throw new Error(`Unsupported HTTP method: ${f}`)}}else{let M=d;if(f==="GET"){const X=new URLSearchParams(b).toString();M=X?`${d}?${X}`:d}const R={...m?{}:{"Content-Type":"application/json"},Accept:"application/json"};if(typeof window<"u"){const X=localStorage.getItem("g7_locale");X&&(R["Accept-Language"]=X)}const D=this.getMatchingGlobalHeaders(d,u);Object.assign(R,D),e.headers&&Object.entries(e.headers).forEach(([X,ge])=>{const Ee=gc(ge,u);Ee!=null&&Ee!==""&&(R[X]=String(Ee))});let U;f!=="GET"&&(m?(U=this.toFormData(b),delete R["Content-Type"]):U=JSON.stringify(b));const j=await fetch(M,{method:f,headers:R,body:U});let P;try{P=await j.json()}catch{P=null}if(S&&v?.isEnabled()&&(v.completeRequest(S,j.status,P),S=null),!j.ok){v?.isEnabled()&&v.trackDataSourceError(e.id,`HTTP ${j.status}: ${j.statusText}`);const X=new Error(`HTTP error! status: ${j.status}`);throw X.response={status:j.status,statusText:j.statusText,data:P},X.status=j.status,X}E=P,v?.isEnabled()&&v.trackDataSourceLoaded(e.id,E)}return L&&S&&v?.isEnabled()&&(v.completeRequest(S,200,E),v.trackDataSourceLoaded(e.id,E)),E}catch(E){if(S&&v?.isEnabled()){const A=E;A?.response?v.completeRequest(S,A.response.status,A.response.data):v.failRequest(S,E instanceof Error?E.message:String(E)),v.trackDataSourceError(e.id,E instanceof Error?E.message:String(E))}throw E}}getCachedData(e){const a=this.dataCache.get(e);if(!a)return;if(Date.now()-a.timestamp>bc.DATA_CACHE_TTL){this.dataCache.delete(e);return}return a.data}clearCache(){this.dataCache.clear()}subscribeWebSockets(e,a,r={}){const i=[],l=e.filter(u=>u.type==="websocket");return l.length===0||l.forEach(u=>{if(!u.channel||!u.event){bt.warn(`WebSocket source ${u.id} missing channel or event`);return}let d,f;try{d=Qi(u.channel,r,{skipCache:!0}),f=Qi(u.event,r,{skipCache:!0})}catch(b){bt.error(`Failed to resolve WebSocket channel/event for source ${u.id}:`,b);return}const g=b=>!!(!b||b.trim()===""||b.includes("{{")||b.endsWith(".")||b.endsWith(":")||b.startsWith(".")||b.startsWith(":")||b.includes("..")||b.includes("::"));if(g(d)||g(f)){bt.warn(`WebSocket source ${u.id} has invalid channel/event after expression resolution, skipping. Original channel="${u.channel}", resolved="${d}". Original event="${u.event}", resolved="${f}". Available context keys: [${Object.keys(r).join(", ")}]`);return}const m=hc.subscribe(d,f,b=>{const v=u.target_source||u.id;this.dataCache.set(v,{data:b,timestamp:Date.now()}),a(v,b)},{channelType:u.channel_type||"private"});m&&i.push(m)}),i}unsubscribeWebSockets(e){e.length!==0&&e.forEach(a=>{hc.unsubscribe(a)})}async executeOnSuccessHandler(e,a){const r=this.options.actionDispatcher??em();if(!r){bt.warn("ActionDispatcher not available, skipping onSuccess handler");return}const i=Array.isArray(e)?e:[e];for(const l of i)try{await r.dispatchAction({type:"click",...l},{data:{response:a}})}catch(u){throw bt.error("Failed to execute onSuccess handler:",u),u}}};$(bc,"DATA_CACHE_TTL",3e5);let va=bc;const H_=new va,gi=ct("ModalDataSourceWrapper");function z_(o,e){if(!o||!e)return o;const r=e.replace(/\?\./g,".").split(/\.(?![^\[]*\])/).flatMap(l=>{const u=l.match(/^([^\[]*)((?:\[\d+\])*)$/);if(u){const[,d,f]=u,g=[];d&&g.push(d);const m=f.match(/\[\d+\]/g);return m&&g.push(...m),g}return[l]}).filter(l=>l!=="");let i=o;for(const l of r){if(i==null)return;if(l.startsWith("[")&&l.endsWith("]")){const u=parseInt(l.slice(1,-1),10);i=i[u]}else i=i[l]}return i}const ry=({isOpen:o,modalId:e,dataSources:a,dataContext:r,globalStateUpdater:i,bindingEngine:l,debug:u=!1,children:d})=>{const[f,g]=N.useState(!1),[m,b]=N.useState(!1),v=N.useRef(!1),S=N.useRef(null);S.current||(S.current=new va);const E=N.useCallback(async()=>{if(!(!a||a.length===0)&&S.current){g(!0);try{const A=r.route||{},T=new URLSearchParams(window.location.search),L=a.map(R=>{let D={...R};if(R.endpoint&&l){const U=l.resolveBindings(R.endpoint,r,{skipCache:!0});D.endpoint=U}if(R.params&&l){const U={};Object.entries(R.params).forEach(([j,P])=>{typeof P=="string"?U[j]=l.resolveBindings(P,r,{skipCache:!0}):U[j]=P}),D.params=U}return D});u&&gi.log(`[ModalDataSourceWrapper] Modal "${e}" fetching data sources:`,L.map(R=>({id:R.id,endpoint:R.endpoint})));const M=await S.current.fetchDataSourcesWithResults(L,A,T);i&&M.forEach(R=>{if(R.state==="success"&&R.data!==void 0){const D=L.find(U=>U.id===R.id);if(D?.initGlobal){const U=R.data?.data??R.data,j=Array.isArray(D.initGlobal)?D.initGlobal:[D.initGlobal];for(const P of j)if(typeof P=="string")i({[P]:U}),u&&gi.log(`[ModalDataSourceWrapper] Modal "${e}" initGlobal: ${R.id}.data -> _global.${P}`);else if(typeof P=="object"&&P.key){const{key:X,path:ge}=P,Ee=ge?z_(U,ge):U;i({[X]:Ee}),u&&gi.log(`Modal "${e}" initGlobal: ${R.id}.data${ge?"."+ge:""} -> _global.${X}`)}}}else R.state==="error"&&gi.error(`Modal "${e}" data source error:`,R.id,R.error)}),u&&gi.log(`Modal "${e}" data sources fetched successfully`)}catch(A){gi.error(`Modal "${e}" data source fetch error:`,A)}finally{g(!1),b(!0)}}},[a,r,i,l,e,u]);return N.useEffect(()=>{if(!a||a.length===0)return;const A=window.__templateApp;return A?.registerModalDataSources&&A.registerModalDataSources(e,a),()=>{A?.unregisterModalDataSources&&A.unregisterModalDataSources(e)}},[e,a]),N.useEffect(()=>{!o&&v.current&&(b(!1),u&&gi.log(`[ModalDataSourceWrapper] Modal "${e}" closed, reset fetch state`)),o&&!v.current&&a&&a.length>0&&!m&&E(),v.current=o},[o,a,m,E,e,u]),ut.jsx(ut.Fragment,{children:d})};class pc extends Error{constructor(a,r,i={}){super(r);$(this,"code");$(this,"userMessageKey");$(this,"showStack");$(this,"icon");$(this,"recoverable");this.name="TemplateEngineError",this.code=a,this.userMessageKey=r,this.showStack=i.showStack??!0,this.icon=i.icon??"fa-circle-exclamation",this.recoverable=i.recoverable??!0,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}getUserMessage(a,r){if(!r||!a)return this.userMessageKey;try{return r.translate(this.userMessageKey,a)}catch{return this.userMessageKey}}}class j_ extends pc{constructor(){super("TEMPLATE_NOT_FOUND","$t:core.errors.template_not_found",{showStack:!1,icon:"fa-paint-brush",recoverable:!1}),this.name="TemplateNotFoundError"}}function iy(o){if(o instanceof pc)return o;if(o instanceof Error){const e=new pc("TEMPLATE_INIT_ERROR",o.message||"$t:core.errors.template_init_error",{showStack:!0,icon:"fa-circle-exclamation",recoverable:!0});return e.stack=o.stack,e}return new pc("TEMPLATE_INIT_ERROR","$t:core.errors.template_init_error",{showStack:!1,icon:"fa-circle-exclamation",recoverable:!0})}const U_=ct("ErrorDisplay");class uf{static render(e,a){const r=document.getElementById(e);if(!r){U_.error(`Container #${e} not found`);return}const i=a.debug&&a.showStack&&a.stackTrace,l=window.matchMedia("(prefers-color-scheme: dark)").matches,u={container:` display: flex; align-items: center; justify-content: center; @@ -178,23 +178,23 @@ Error generating stack: `+c.message+`
- +

- ${this.escapeHtml(n.title)} + ${this.escapeHtml(a.title)}

- ${this.escapeHtml(n.message)} + ${this.escapeHtml(a.message)}

- ${n.detailMessage?` + ${a.detailMessage?`

- ${this.escapeHtml(n.detailMessage)} + ${this.escapeHtml(a.detailMessage)}

`:`
`} @@ -204,12 +204,12 @@ Error generating stack: `+c.message+`

Stack Trace:

-
${this.escapeHtml(n.stackTrace)}
+
${this.escapeHtml(a.stackTrace)}
`:""} - ${n.showReloadButton?` + ${a.showReloadButton?`
- `}static renderFromError(e,n,r,i=!1,l,u,d){let f,g;if(n.userMessageKey.startsWith("$t:"))if(u&&l)try{f=u.translate(n.userMessageKey,l)}catch{f=n.userMessageKey.replace("$t:","")}else f=n.userMessageKey.replace("$t:","");else f=n.userMessageKey;if(d){const m=d;m.details?.apiMessage?g=m.details.apiMessage:m.response?.data?.message&&(g=m.response.data.message)}this.render(e,{title:r,message:f,detailMessage:g,icon:n.icon,showStack:n.showStack,stackTrace:n.stack,showReloadButton:n.recoverable,debug:i})}static escapeHtml(e){if(!e)return"";const n=document.createElement("div");return n.textContent=e,n.innerHTML}}const ds=dt("Router");class nA{constructor(e){$(this,"routes",[]);$(this,"templateIdentifier");$(this,"eventHandlers",new Map);$(this,"pendingNavigation",null);$(this,"isNavigating",!1);$(this,"handlePopState",()=>{this.navigateToCurrentPath()});this.templateIdentifier=e,this.initPopstateListener()}initPopstateListener(){window.addEventListener("popstate",this.handlePopState)}on(e,n){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(n)}async emit(e,...n){const r=this.eventHandlers.get(e);r&&await Promise.all(r.map(i=>i(...n)))}async loadRoutes(e){try{const n=e!==void 0&&e>0?e:null,r=await fetch(Ma(`/api/templates/${this.templateIdentifier}/routes`,"json",n));if(!r.ok)throw new Error(`Failed to load routes: ${r.statusText}`);const i=await r.json();if(!i.success)throw new Error("Failed to load routes from API");if(i.data&&Array.isArray(i.data.routes))this.routes=i.data.routes,ds.log(`Loaded ${this.routes.length} routes${e?` (v=${e})`:""}`);else throw new Error("Invalid routes data format")}catch(n){throw ds.error("Error loading routes:",n),n}}setRoutes(e){this.routes=e,ds.log(`Set ${this.routes.length} routes`)}match(e){const n=this.normalizePathname(e);for(const r of this.routes){const i=this.matchPattern(r.path,n);if(i!==null)return{route:r,params:i}}return null}normalizePathname(e){return e.length>1&&e.endsWith("/")?e.replace(/\/+$/,"")||"/":e}matchPattern(e,n){const r=[];let i=e.replace(/:([^/]+)/g,(f,g)=>(r.push(g),"([^/]+)"));i.startsWith("*/")&&(i="(?:/[^/]+)?"+i.slice(1)),i=i.replace(/\//g,"\\/");const l=new RegExp(`^${i}$`),u=n.match(l);if(!u)return null;const d={};return r.forEach((f,g)=>{d[f]=u[g+1]}),d}getRoutes(){return[...this.routes]}getAuthType(e,n){return e.auth_type?e.auth_type:n.startsWith("/admin")?"admin":"user"}async navigateToCurrentPath(){const e=window.location.pathname,n=window.location.search,r=this.match(e);if(!r){ds.warn(`No route matched for path: ${e}`),this.emit("routeNotFound",e);return}if(r.route.redirect){ds.log(`Redirecting from ${e} to ${r.route.redirect}`),this.navigate(r.route.redirect);return}if(r.route.auth_required){const u=this.getAuthType(r.route,e),d=ga.getInstance();let f=d.isAuthenticated()&&d.getAuthType()===u;if(f||(f=await d.checkAuth(u)),!f){const g=d.getLoginRedirectUrl(u,e+n);ds.log(`Not authenticated, redirecting to: ${g}`),window.location.href=g;return}}const i=new URLSearchParams(n),l={};for(const u of i.keys()){if(u in l)continue;const d=i.getAll(u);d.length>1?l[u]=d:d.length===1&&(u.endsWith("[]")?l[u]=d:l[u]=d[0])}await this.emit("routeChange",{path:e,layout:r.route.layout,endpoint:r.route.endpoint,params:{...r.route.params||{},...r.params},query:l,auth_required:r.route.auth_required,auth_type:r.route.auth_type,meta:r.route.meta})}navigate(e){if(this.isNavigating){this.pendingNavigation=e;return}this.executeNavigation(e)}async executeNavigation(e){this.isNavigating=!0,this.pendingNavigation=null;try{window.history.pushState({},"",e),await this.navigateToCurrentPath()}finally{if(this.isNavigating=!1,this.pendingNavigation){const n=this.pendingNavigation;this.executeNavigation(n)}}}}const aA=/^\/admin\/layout-editor\/([^/?#]+)\/?$/;function hy(o){const e=aA.exec(o);return e?{templateIdentifier:e[1]}:null}const Pt=dt("ErrorPageHandler");class rA{constructor(e){$(this,"templateId");$(this,"layoutLoader");$(this,"locale");$(this,"debug");$(this,"renderFunction");$(this,"dataSourceManager");$(this,"globalState");$(this,"errorConfig",null);$(this,"configLoaded",!1);this.templateId=e.templateId,this.layoutLoader=e.layoutLoader,this.locale=e.locale,this.debug=e.debug,this.renderFunction=e.renderFunction,this.dataSourceManager=e.dataSourceManager,this.globalState=e.globalState||{}}async loadConfig(){if(this.configLoaded)return this.errorConfig;try{const e=await fetch(Ma(`/api/templates/${this.templateId}/config`,"json"));if(!e.ok)return this.debug&&Pt.warn("Failed to load template config:",e.statusText),this.configLoaded=!0,null;const n=await e.json();if(!n.success||!n.data)return this.debug&&Pt.warn("Invalid template config response"),this.configLoaded=!0,null;const r=n.data;return!r.error_config||!r.error_config.layouts?(this.debug&&Pt.warn("No error_config found in template.json"),this.configLoaded=!0,null):(this.errorConfig=r.error_config,this.configLoaded=!0,this.debug&&Pt.log("Error config loaded:",this.errorConfig),this.errorConfig)}catch(e){return Pt.error("Failed to load error config:",e),this.configLoaded=!0,null}}async renderError(e,n="app"){try{const r=await this.loadConfig();if(!r)return this.debug&&Pt.warn("No error config available, cannot render error page"),!1;const i=r.layouts[e]||r.layouts[String(e)];if(!i)return this.debug&&Pt.warn(`No layout defined for error code: ${e}`),!1;this.debug&&Pt.log(`Loading error layout: ${i} for code: ${e}`);const l=await this.layoutLoader.loadLayout(this.templateId,i);if(!l)return this.debug&&Pt.error(`Failed to load error layout: ${i}`),!1;this.debug&&Pt.log("Error layout loaded:",l);let u={};const d=l.data_sources||[];if(d.length>0){this.debug&&Pt.log("Fetching data sources:",d.map(S=>S.id));const m=d.filter(S=>S.loading_strategy==="blocking"),b=d.filter(S=>!S.loading_strategy||S.loading_strategy==="progressive"),v=[...m,...b];if(v.length>0)try{u=await this.dataSourceManager.fetchDataSources(v,{},new URLSearchParams),this.debug&&Pt.log("Data sources fetched:",Object.keys(u))}catch(S){Pt.error("Failed to fetch data sources:",S)}}const f={};d.length>0&&(this.processInitOptions(d,u,f),this.debug&&Pt.log("initOptions processed:",{globalKeys:Object.keys(this.globalState),localKeys:Object.keys(f)}));const g={...u,errorCode:e,_global:{...this.globalState},_local:{...f}};return await this.renderFunction({containerId:n,layoutJson:l,dataContext:g,translationContext:{templateId:this.templateId,locale:this.locale}}),this.debug&&Pt.log(`Error page ${e} rendered successfully`),!0}catch(r){return Pt.error(`Failed to render error page ${e}:`,r),!1}}processInitOptions(e,n,r){for(const i of e){const l=n[i.id];if(!l)continue;const u=l?.data??l;if(i.initLocal){if(typeof i.initLocal=="string")r[i.initLocal]=u,Pt.log(`initLocal: ${i.id}.data -> _local.${i.initLocal}`);else if(typeof i.initLocal=="object"&&"key"in i.initLocal){const{key:d,path:f}=i.initLocal;r[d]=f?this.getValueByPath(u,f):u,Pt.log(`initLocal: ${i.id}.data${f?"."+f:""} -> _local.${d}`)}}if(i.initGlobal){const d=Array.isArray(i.initGlobal)?i.initGlobal:[i.initGlobal];for(const f of d)if(typeof f=="string")this.globalState[f]=u,Pt.log(`initGlobal: ${i.id}.data -> _global.${f}`);else if(typeof f=="object"&&f!==null&&"key"in f){const{key:g,path:m}=f;this.globalState[g]=m?this.getValueByPath(u,m):u,Pt.log(`initGlobal: ${i.id}.data${m?"."+m:""} -> _global.${g}`)}}}}getValueByPath(e,n){return n.split(".").reduce((r,i)=>r?.[i],e)}updateGlobalState(e){this.globalState={...this.globalState,...e}}updateLocale(e){this.locale=e,this.debug&&Pt.log("Locale updated:",e)}isConfigLoaded(){return this.configLoaded}async hasErrorLayout(e){const n=await this.loadConfig();return n?!!(n.layouts[e]||n.layouts[String(e)]):!1}clearConfigCache(){this.errorConfig=null,this.configLoaded=!1,this.debug&&Pt.log("Config cache cleared")}}const Ht=dt("ModuleAssetLoader");class iA{constructor(){$(this,"loadedAssets",new Map);$(this,"loadingPromises",new Map);$(this,"failedJsAssets",new Set)}hasFailedJsAssets(){return this.failedJsAssets.size>0}getFailedJsAssets(){return[...this.failedJsAssets]}async loadActiveExtensionAssets(e){if(!e||e.length===0){Ht.log("No module assets to load");return}const n=[...e].sort((d,f)=>d.priority-f.priority);Ht.log("Loading module assets:",n.map(d=>d.identifier));const r=n.filter(d=>d.css).map(d=>this.loadCSS(d.identifier,d.css)),i=n.filter(d=>d.js).map(d=>this.loadJS(d.identifier,d.js)),l=await Promise.allSettled([...r,...i]),u=l.filter(d=>d.status==="rejected");if(u.length>0){Ht.warn(`Some module assets failed to load (${u.length}/${l.length}); continuing with the rest`,this.getFailedJsAssets());return}Ht.log("All module assets loaded successfully")}async loadBundle(e,n,r){const i=[];if(r&&i.push(this.loadBundleCss(e,r)),n&&i.push(this.loadBundleJs(e,n)),i.length===0){Ht.log(`No bundle assets to load for: ${e}`);return}await Promise.all(i)}async loadBundleCss(e,n){const r=`ext-bundle-css-${e}`;if(document.getElementById(r)){Ht.log(`Bundle CSS already loaded: ${e}`);return}return new Promise(i=>{const l=document.createElement("link");l.rel="stylesheet",l.href=n,l.id=r,l.onload=()=>{Ht.log(`Bundle CSS loaded: ${e}`),this.registerLoadedAsset(`bundle-${e}`,{type:"css",element:l}),i()},l.onerror=()=>{Ht.warn(`Failed to load bundle CSS: ${e} (${n})`),i()},document.head.appendChild(l)})}async loadBundleJs(e,n){const r=`ext-bundle-js-${e}`;if(document.getElementById(r)){Ht.log(`Bundle JS already loaded: ${e}`);return}const i=this.loadingPromises.get(r);if(i)return Ht.log(`Bundle JS already loading: ${e}`),i;const l=Zg(n,{id:r},{label:`bundle JS: ${e}`}).then(()=>{Ht.log(`Bundle JS loaded: ${e}`);const u=document.getElementById(r);u&&this.registerLoadedAsset(`bundle-${e}`,{type:"js",element:u}),this.loadingPromises.delete(r)}).catch(u=>{throw Ht.warn(`Failed to load bundle JS: ${e} (${n})`,u),this.failedJsAssets.add(e),this.loadingPromises.delete(r),u});return this.loadingPromises.set(r,l),l}async loadCSS(e,n){const r=`module-css-${e}`;if(document.getElementById(r)){Ht.log(`CSS already loaded: ${e}`);return}return new Promise((i,l)=>{const u=document.createElement("link");u.rel="stylesheet",u.href=n,u.id=r,u.onload=()=>{Ht.log(`CSS loaded: ${e}`),this.registerLoadedAsset(e,{type:"css",element:u}),i()},u.onerror=()=>{Ht.warn(`Failed to load CSS: ${e} (${n})`),i()},document.head.appendChild(u)})}async loadJS(e,n){const r=`module-js-${e}`;if(document.getElementById(r)){Ht.log(`JS already loaded: ${e}`);return}const i=this.loadingPromises.get(e);if(i)return Ht.log(`JS already loading: ${e}`),i;const l=Zg(n,{id:r},{label:`JS: ${e}`}).then(()=>{Ht.log(`JS loaded: ${e}`);const u=document.getElementById(r);u&&this.registerLoadedAsset(e,{type:"js",element:u}),this.loadingPromises.delete(e)}).catch(u=>{throw Ht.warn(`Failed to load JS: ${e} (${n})`,u),this.failedJsAssets.add(e),this.loadingPromises.delete(e),u});return this.loadingPromises.set(e,l),l}registerLoadedAsset(e,n){const r=this.loadedAssets.get(e)||[];r.push(n),this.loadedAssets.set(e,r)}unloadExtensionAsset(e){const n=this.loadedAssets.get(e);if(!n||n.length===0){Ht.log(`No assets to unload for: ${e}`);return}n.forEach(r=>{r.element.parentNode&&(r.element.parentNode.removeChild(r.element),Ht.log(`${r.type.toUpperCase()} unloaded: ${e}`))}),this.loadedAssets.delete(e),Ht.log(`All assets unloaded for: ${e}`)}unloadAllAssets(){Array.from(this.loadedAssets.keys()).forEach(n=>{this.unloadExtensionAsset(n)}),Ht.log("All module assets unloaded")}isLoaded(e){return this.loadedAssets.has(e)}getLoadedModules(){return Array.from(this.loadedAssets.keys())}}let hf=null;function gf(){return hf||(hf=new iA),hf}function sA(){if(typeof window>"u")return[];const o=window.G7Config;if(!o?.moduleAssets)return[];const e=[];for(const[n,r]of Object.entries(o.moduleAssets)){const i=r;e.push({identifier:n,js:i.js?Cr(i.js):i.js,css:i.css?Cr(i.css):i.css,priority:i.priority,external:i.external})}return e}function oA(){if(typeof window>"u")return null;const o=window.G7Config;if(!o?.bundleUrls)return null;const e=o.bundleUrls;return{moduleJs:e.moduleJs?Cr(e.moduleJs):e.moduleJs,moduleCss:e.moduleCss?Cr(e.moduleCss):e.moduleCss,pluginJs:e.pluginJs?Cr(e.pluginJs):e.pluginJs,pluginCss:e.pluginCss?Cr(e.pluginCss):e.pluginCss}}function lA(){if(typeof window>"u")return[];const o=window.G7Config;if(!o?.pluginAssets)return[];const e=[];for(const[n,r]of Object.entries(o.pluginAssets)){const i=r;e.push({identifier:n,js:i.js?Cr(i.js):i.js,css:i.css?Cr(i.css):i.css,priority:i.priority,external:i.external})}return e}const pf=dt("SystemBannerManager");class vc{static show(e){this.banners.set(e.id,e),this.render(),pf.log(`Banner shown: ${e.id}`)}static hide(e){this.banners.delete(e)&&(this.render(),pf.log(`Banner hidden: ${e}`))}static hideAll(){this.banners.clear(),this.render(),pf.log("All banners hidden")}static detectLocale(){try{const n=window.G7Core;if(n?.locale?.current)return n.locale.current()}catch{}return(navigator.language||"ko").split("-")[0]}static resolveMessage(e,n){return typeof e=="string"?e:e[n]||e.en||e.ko||Object.values(e)[0]||""}static render(){if(typeof document>"u")return;let e=document.getElementById(this.containerId);if(this.banners.size===0){e&&(e.remove(),this.adjustAppPadding(0));return}e||(e=document.createElement("div"),e.id=this.containerId,e.style.cssText="position:fixed;top:0;left:0;right:0;z-index:99999;",document.body.prepend(e));const n=Array.from(this.banners.values()).sort((i,l)=>(i.order??0)-(l.order??0)),r=this.detectLocale();e.innerHTML=n.map(i=>{const l=this.resolveMessage(i.message,r),u=i.background||"#f59e0b",d=i.color||"white";return`
${l}
`}).join(""),requestAnimationFrame(()=>{e&&this.adjustAppPadding(e.offsetHeight)})}static adjustAppPadding(e){const n=document.getElementById("app");n&&(n.style.paddingTop=e>0?`${e}px`:"")}}$(vc,"banners",new Map),$(vc,"containerId","g7-system-banners");const B=dt("TemplateApp");function gy(o){const e={};for(const n of o.keys()){if(n in e)continue;const r=o.getAll(n);r.length>1?e[n]=r:r.length===1&&(n.endsWith("[]")?e[n]=r:e[n]=r[0])}return e}const Ea=class Ea{constructor(e){$(this,"router",null);$(this,"layoutLoader",null);$(this,"errorPageHandler",null);$(this,"config");$(this,"globalState");$(this,"globalStateListeners",new Set);$(this,"currentRouteChangeId",0);$(this,"currentDataSources",[]);$(this,"currentRawDataSources",[]);$(this,"currentRouteParams",{});$(this,"currentQueryParams",new URLSearchParams);$(this,"currentFetchedData",{});$(this,"currentLayoutName","");$(this,"templateErrorHandling",null);$(this,"currentWebSocketSubscriptions",[]);$(this,"extensionCacheVersion",0);$(this,"currentGlobalHeaders",[]);$(this,"transitionOverlayEl",null);$(this,"skeletonOverlayRoot",null);$(this,"skeletonOverlayContainer",null);$(this,"_spinnerState",null);$(this,"modalDataSources",new Map);this.globalState={sidebarOpen:!1},this.loadG7Config(),this.migrateLocaleStorage();const n=this.loadLocaleFromStorage(),r=n||e.locale||"ko";this.config={...e,locale:r},!n&&e.locale&&this.saveLocaleToStorage(e.locale)}loadG7Config(){if(typeof window<"u"&&window.G7Config){const e=window.G7Config;e.settings&&(this.globalState.settings=e.settings,B.log("Loaded settings from G7Config:",Object.keys(e.settings)),e.settings.upload&&(this.globalState.uploadSettings=e.settings.upload)),e.plugins&&(this.globalState.plugins=e.plugins,B.log("Loaded plugin settings from G7Config:",Object.keys(e.plugins))),e.modules&&(this.globalState.modules=e.modules,B.log("Loaded module settings from G7Config:",Object.keys(e.modules))),e.appConfig&&(this.globalState.appConfig=e.appConfig,B.log("Loaded appConfig from G7Config:",Object.keys(e.appConfig)))}}resolveRouteExpressions(e){const n=new Rn,r={_global:this.globalState};return e.map(i=>{const l={...i};if(l.path&&l.path.includes("{{")){const u=l.path;l.path=n.resolveBindings(l.path,r),l.path=l.path.replace(/\/\/+/g,"/")||"/",l.path.includes("{{")&&(B.warn("Route expression resolution failed, using fallback:",u),l.path=u.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}if(l.redirect&&l.redirect.includes("{{")){const u=l.redirect;l.redirect=n.resolveBindings(l.redirect,r),l.redirect=l.redirect.replace(/\/\/+/g,"/")||"/",l.redirect.includes("{{")&&(B.warn("Route redirect expression resolution failed:",u),l.redirect=u.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}return l})}migrateLocaleStorage(){const e=["locale","g7_template_locale"];for(const n of e)try{const r=localStorage.getItem(n);r&&r!==localStorage.getItem(Ea.LOCALE_STORAGE_KEY)&&(localStorage.setItem(Ea.LOCALE_STORAGE_KEY,r),B.log(`Migrated locale from '${n}' to '${Ea.LOCALE_STORAGE_KEY}'`)),localStorage.removeItem(n)}catch(r){B.warn(`Failed to migrate locale key '${n}':`,r)}}async init(){try{Js.getInstance().setDebug(this.config.debug),B.log("Initializing with config:",this.config),KS();const e=fa.getInstance(),n=ga.getInstance(),r=this.loadCacheVersionFromStorage()||0,[i,l,u,d,f]=await Promise.all([ko({templateId:this.config.templateId,templateType:this.config.templateType,locale:this.config.locale,debug:this.config.debug,cacheVersion:r}),e.loadComponents(this.config.templateId,this.config.templateType),Dl(Ma(`/api/templates/${this.config.templateId}/routes`,"json",r>0?r:null),{label:"routes.json"}).then(x=>{if(!x.ok)throw new Error(`Failed to load routes: ${x.statusText}`);return x.json()}).then(x=>{if(!x.success)throw new Error("Failed to load routes from API");return x.data}).catch(x=>{throw B.error("Error loading routes:",x),x}),n.preloadAuth(this.config.templateType==="admin"?"admin":"user"),fetch(Ma(`/api/templates/${this.config.templateId}/config`,"json")).then(x=>x.ok?x.json():null).then(x=>!x?.success||!x?.data?null:x.data).catch(x=>(B.warn("Error loading template config:",x),null))]);if(B.log("Template Engine initialized"),B.log("ComponentRegistry loaded"),B.log("Routes data loaded"),B.log("User info preloaded"),B.log("Template config loaded:",f),f?.cache_version!==void 0){const x=this.loadCacheVersionFromStorage();if(this.extensionCacheVersion=f.cache_version,this.saveCacheVersionToStorage(this.extensionCacheVersion),B.log("Extension cache version:",this.extensionCacheVersion),x!==null&&x!==this.extensionCacheVersion){B.log("Cache version changed, reloading routes...");const k=await Dl(Ma(`/api/templates/${this.config.templateId}/routes`,"json",this.extensionCacheVersion),{label:"routes.json (reload)"}).then(O=>{if(!O.ok)throw new Error(`Failed to reload routes: ${O.statusText}`);return O.json()}).then(O=>{if(!O.success)throw new Error("Failed to reload routes from API");return O.data});Array.isArray(k.routes)&&(k.routes=this.resolveRouteExpressions(k.routes),Object.assign(u,k),B.log("Routes reloaded with new cache version"));try{const{TranslationEngine:O}=await Promise.resolve().then(()=>rp),M=O.getInstance();M.setCacheVersion(this.extensionCacheVersion);const T=this.config.locale||"ko",D="en";await M.loadTranslations(this.config.templateId,T,"/api",!0),T!==D&&await M.loadTranslations(this.config.templateId,D,"/api",!0),B.log("Translations reloaded with new cache version")}catch(O){B.error("Failed to reload translations:",O)}}}f?.errorHandling&&(this.templateErrorHandling=f.errorHandling,ma().setTemplateConfig(this.templateErrorHandling),B.log("Template errorHandling registered:",this.templateErrorHandling)),n.on("logout",()=>{B.log("User logged out")}),n.on("authStateChange",x=>{B.log("Auth state changed:",x)}),B.log("AuthManager event handlers registered");const g=Ia(),m=this.config.templateType==="admin"?"admin":"user",b=n.getConfig(m);g.setOnUnauthorized(()=>{B.log("Unauthorized - redirecting to login page"),g.removeToken();const x=window.location.pathname+window.location.search,k=n.getLoginRedirectUrl(m,x,"session_expired");window.location.href=k}),B.log("ApiClient onUnauthorized callback registered"),this.layoutLoader=new sf(e),this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),B.log("LayoutLoader initialized:",this.layoutLoader);const v=new Sa({onUnauthorized:()=>{B.warn("Unauthorized request in error page")}});if(this.errorPageHandler=new rA({templateId:this.config.templateId,layoutLoader:this.layoutLoader,locale:this.config.locale,debug:this.config.debug,renderFunction:fs,dataSourceManager:v,globalState:this.globalState}),B.log("ErrorPageHandler initialized with DataSourceManager"),await this.handleServerError()){B.log("Server error handled, skipping normal initialization");return}if(this.router=new nA(this.config.templateId),Array.isArray(u.routes)){const x=this.resolveRouteExpressions(u.routes);this.router.setRoutes(x)}else throw new Error("Invalid routes data format");B.log("Router initialized:",this.router),B.log("Routes set:",this.router.getRoutes());const{getActionDispatcher:S}=await Promise.resolve().then(()=>Ua),_=S();if(_&&(_.setDefaultContext({navigate:x=>this.router?.navigate(x)}),_.setGlobalStateUpdater((x,k)=>this.setGlobalState(x,k)),B.log("Navigate function and setGlobalState injected to ActionDispatcher")),await this.loadExtensionAssets(),this.reinitializeTemplateHandlers(),this.router.on("routeChange",x=>this.handleRouteChange(x)),this.router.on("routeNotFound",x=>this.handleRouteNotFound(x)),typeof window<"u"&&hy(window.location.pathname)){B.log("Layout editor mode detected — skipping router match"),await fs({containerId:"app",layoutJson:{components:[]},dataContext:{},translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),B.log("Template App initialized in layout editor mode");return}this.router.navigateToCurrentPath(),B.log("Template App initialized successfully")}catch(e){B.error("Initialization failed:",e),this.showInitError(e)}}async loadExtensionAssets(){try{const e=gf(),n=oA();if(!n){await this.loadExtensionAssetsIndividually();return}await e.loadBundle("module",n.moduleJs,n.moduleCss),await e.loadBundle("plugin",n.pluginJs,n.pluginCss),B.log("Extension bundle assets loaded successfully")}catch(e){B.warn("Failed to load extension assets:",e)}}async loadExtensionAssetsIndividually(){const e=gf(),n=sA();n.length>0&&(B.log("Loading module assets (individual fallback):",n.map(i=>i.identifier)),await e.loadActiveExtensionAssets(n));const r=lA();r.length>0&&(B.log("Loading plugin assets (individual fallback):",r.map(i=>i.identifier)),await e.loadActiveExtensionAssets(r))}async handleRouteChange(e){const n=++this.currentRouteChangeId;window.__g7ForcedLocalFields=void 0,window.__g7ActionContext=void 0,window.__g7PendingLocalState=void 0,window.__g7LastSetLocalSnapshot=void 0,window.__g7SetLocalOverrideKeys=void 0,window.__g7SequenceLocalSync=void 0,window.__g7AutoBindingPaths=new Map;try{if(B.log("Route changed:",e,"requestId:",n),!this.layoutLoader)throw new Error("LayoutLoader is not initialized");if(!e.layout)throw new Error("Route layout is not defined");let r=e.layout;const i=r==="__preview__";i&&e.params?.token&&(r=`__preview__/${e.params.token}`);const l=await this.layoutLoader.loadLayout(this.config.templateId,r);if(n!==this.currentRouteChangeId){B.log("Route change cancelled (newer request exists):",n);return}B.log("Layout loaded:",l);const u=ma();l.errorHandling?(u.setLayoutConfig(l.errorHandling),B.log("Layout errorHandling registered:",l.errorHandling)):u.clearLayoutConfig();const d=l.data_sources||[],f=e.query||{},g=new URLSearchParams;for(const[ae,ge]of Object.entries(f))if(Array.isArray(ge))for(const A of ge)g.append(ae,A);else g.set(ae,ge);const m={route:e.params||{},query:f,_global:this.globalState};l.scripts&&Array.isArray(l.scripts)&&await this.loadLayoutScripts(l.scripts,m);const{DataSourceManager:b,getActionDispatcher:v}=await Promise.resolve().then(()=>Ua),S=new b;if(i){const ae=v();ae&&ae.setPreviewMode(!0),this.setGlobalState({__isPreview:!0}),vc.show({id:"preview-mode",message:{ko:"⚠ 미리보기 모드 — 페이지 이동이 비활성화됩니다",en:"⚠ Preview Mode — Navigation is disabled"},background:"linear-gradient(90deg, #f59e0b, #d97706)",color:"white"}),B.log("Preview mode activated")}else{const ae=v();ae?.isPreviewMode()&&(ae.setPreviewMode(!1),vc.hide("preview-mode"))}if(this.currentGlobalHeaders=l.globalHeaders||[],this.currentGlobalHeaders.length>0){S.setGlobalHeaders(this.currentGlobalHeaders);const ae=v();ae&&ae.setGlobalHeaders(this.currentGlobalHeaders),B.log("globalHeaders set:",this.currentGlobalHeaders.map(ge=>ge.pattern))}if(l.named_actions&&Object.keys(l.named_actions).length>0){const ae=v();ae&&ae.setNamedActions(l.named_actions)}const _=S.filterByCondition(d,m);d.length!==_.length&&B.log("Data sources filtered by condition:",{before:d.map(ae=>ae.id),after:_.map(ae=>ae.id)});const x=_.filter(ae=>ae.loading_strategy==="blocking"),k=_.filter(ae=>(ae.loading_strategy||"progressive")!=="blocking"&&ae.type!=="websocket"),O=Array.isArray(l.transition_overlay?.wait_for)?l.transition_overlay.wait_for:[],M=O.length>0&&_.some(ae=>O.includes(ae.id)&&ae.type!=="websocket"&&(ae.loading_strategy||"progressive")!=="background");if((x.length>0||M)&&l.transition_overlay){const ae=typeof l.transition_overlay=="boolean"?{enabled:l.transition_overlay,style:"opaque"}:l.transition_overlay;ae.enabled&&ae.style==="skeleton"&&ae.skeleton?.component&&ae.target?this.renderSkeletonOverlay(ae.target,ae.skeleton,l,ae.fallback_target):ae.enabled&&ae.style==="spinner"&&ae.target&&this.renderSpinnerOverlay(ae.target,ae.spinner,ae.fallback_target)}let T={},D={},j={},H={};this.currentWebSocketSubscriptions.length>0&&(B.log("Unsubscribing previous WebSocket subscriptions:",this.currentWebSocketSubscriptions),S.unsubscribeWebSockets(this.currentWebSocketSubscriptions),this.currentWebSocketSubscriptions=[]),this.currentDataSources=_,this.currentRawDataSources=d,this.currentRouteParams=e.params||{},this.currentQueryParams=g,B.log(`handleRouteChange #${n} - queryObject:`,f),B.log(`handleRouteChange #${n} - queryParams:`,g.toString()),x.length>0&&(B.log("Fetching blocking data sources:",x.map(ge=>ge.id)),(await S.fetchDataSourcesWithResults(x,e.params||{},g)).forEach(ge=>{if(ge.state==="success"&&ge.data!==void 0)T[ge.id]=ge.data;else if(ge.state==="error"&&ge.error){const A=ge.error,K=A.response?.data?.message;D[ge.id]={message:K||ge.error.message,status:A.response?.status||A.status}}}),this.processInitOptions(x,T,j,H),this.currentFetchedData={...T},B.log("Blocking data loaded:",Object.keys(T)),Object.keys(D).length>0&&B.log("Data source errors:",D),Object.keys(j).length>0&&B.log("Local state init (blocking):",Object.keys(j)),Object.keys(H).length>0&&B.log("Isolated state init (blocking):",Object.keys(H)));const P=k.length>0;if(P&&(Ha.setPending(!0),B.log("Transition started before rendering")),n!==this.currentRouteChangeId){B.log("Route change cancelled after blocking data (newer request exists):",n);return}const{getState:X}=await Promise.resolve().then(()=>Ua),pe=X().currentDataContext||{},_e=k.map(ae=>ae.id),we={};if(P){_e.forEach(ge=>{pe[ge]!==void 0?we[ge]=pe[ge]:we[ge]=void 0});const ae=Object.keys(we).filter(ge=>we[ge]!==void 0);ae.length>0&&B.log("Preserving previous progressive data:",ae),B.log("Progressive data sources initialized:",_e)}const Me=l.defines||{},qe={...we,...e.params,...T,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState},_globalSetState:ae=>this.setGlobalState(ae),_dataSourceErrors:Object.keys(D).length>0?D:void 0,_localInit:Object.keys(j).length>0?j:void 0,_isolatedInit:Object.keys(H).length>0?H:void 0,_defines:Object.keys(Me).length>0?Me:void 0};if(l.computed&&Object.keys(l.computed).length>0){const ae=this.calculateComputed(l.computed,qe);Object.keys(ae).length>0&&(qe._computed=ae,this.globalState._computed=ae,B.log("Computed values calculated:",Object.keys(ae))),qe._computedDefinitions=l.computed}const Je=l.layout_name||e.layout;this.currentLayoutName!==""&&this.currentLayoutName!==Je&&(B.log("_local reset due to layout change:",{from:this.currentLayoutName,to:Je}),this.globalState._local={},bC()),this.currentLayoutName=Je;const Q=l.initLocal||l.state;if(Q&&Object.keys(Q).length>0){this.globalState._local||(this.globalState._local={});for(const[ae,ge]of Object.entries(Q))this.globalState._local[ae]===void 0&&(this.globalState._local[ae]=JSON.parse(JSON.stringify(ge)));qe._local={...this.globalState._local},B.log("initLocal applied to _local:",Object.keys(Q))}if(l.initGlobal&&Object.keys(l.initGlobal).length>0){for(const[ae,ge]of Object.entries(l.initGlobal))this.globalState[ae]===void 0&&(this.globalState[ae]=JSON.parse(JSON.stringify(ge)));B.log("initGlobal applied to _global:",Object.keys(l.initGlobal))}if(l.initIsolated&&Object.keys(l.initIsolated).length>0){for(const[ae,ge]of Object.entries(l.initIsolated))H[ae]===void 0&&(H[ae]=JSON.parse(JSON.stringify(ge)));B.log("initIsolated applied:",Object.keys(l.initIsolated))}qe._global={...this.globalState,layoutWarnings:l.warnings||[]};const fe=l.initActions||l.init_actions;if(fe&&fe.length>0){await this.executeInitActions(fe,qe),B.log("initActions executed before render");const ae=this.globalState;if(ae._local){if(Object.keys(j).length>0){for(const[ge,A]of Object.entries(j))ae._local[ge]!==void 0?ae._local[ge]=this.deepMerge(ae._local[ge],A):ae._local[ge]=A;B.log("localInit merged into _local after initActions:",Object.keys(j))}qe._local=ae._local,B.log("_local merged into dataContext:",ae._local)}if(qe._global={...this.globalState,layoutWarnings:l.warnings||[]},B.log("_global merged into dataContext after initActions"),l.computed&&Object.keys(l.computed).length>0){const ge=this.calculateComputed(l.computed,qe);Object.keys(ge).length>0&&(qe._computed=ge,this.globalState._computed=ge,B.log("Computed values recalculated after init_actions:",Object.keys(ge)))}if(Object.keys(this.currentFetchedData).length>0){for(const[ge,A]of Object.entries(this.currentFetchedData))qe[ge]=A;B.log("Fetched data sources merged into dataContext after initActions:",Object.keys(this.currentFetchedData))}}if(l.transition_overlay&&!this.skeletonOverlayContainer&&this.showTransitionOverlay(l.transition_overlay,l),await fs({containerId:"app",layoutJson:l,dataContext:qe,translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),B.log("Initial render complete with blocking data"),this.reattachSpinnerOverlay(),k.length>0){if(n!==this.currentRouteChangeId){B.log("Route change cancelled before progressive fetch (newer request exists):",n),Ha.setPending(!1),this.hideTransitionOverlay();return}B.log("Fetching progressive/background data sources:",k.map(A=>A.id));const{updateTemplateData:ae,getState:ge}=await Promise.resolve().then(()=>Ua);try{const A=k.map(async K=>{try{const he=(await S.fetchDataSourcesWithResults([K],e.params||{},g,this.globalState))[0];if(!he){B.log(`Data source ${K.id} skipped (no fetch result)`);return}if(n!==this.currentRouteChangeId){B.log(`Data source ${K.id} fetch cancelled (newer request exists)`);return}if(he.state==="success"&&he.data!==void 0){B.log(`Progressive data source loaded: ${K.id}`),this.currentFetchedData[K.id]=he.data;const le={};this.processInitOptions([K],{[K.id]:he.data},le);const de=ge();if(de.bindingEngine){const He=[K.id];K.initGlobal&&He.push("_global"),K.initLocal&&He.push("_local"),de.bindingEngine.invalidateCacheByKeys(He)}const Oe={[K.id]:he.data};Object.keys(le).length>0&&(Oe._localInit=le,Object.assign(j,le)),K.initGlobal&&(Oe._global={...this.globalState}),ae(Oe)}else if(he.state==="error"&&he.error){const le=he.error,de=le.response?.data?.message;D[K.id]={message:de||he.error.message,status:le.response?.status||le.status},B.log(`Progressive data source error: ${K.id}`,D[K.id]),ae({[K.id]:null,_dataSourceErrors:{...D}})}}catch(ue){B.error(`Failed to fetch data source: ${K.id}`,ue)}});if(await Promise.all(A),n!==this.currentRouteChangeId){B.log("Route change cancelled after progressive fetch (newer request exists):",n);return}B.log("All progressive data sources loaded")}finally{Ha.setPending(!1),this.hideTransitionOverlay()}}else this.hideTransitionOverlay();const ve=_.filter(ae=>ae.type==="websocket");if(ve.length>0){B.log("Subscribing WebSocket data sources:",ve.map(K=>K.id));const{updateTemplateData:ae,getState:ge}=await Promise.resolve().then(()=>Ua),A={...this.currentFetchedData,...e.params,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState}};B.log("WebSocket binding context keys:",Object.keys(A)),this.currentWebSocketSubscriptions=S.subscribeWebSockets(_,(K,ue)=>{B.log(`WebSocket data received for: ${K}`,ue),this.currentFetchedData[K]=ue;const he=_.find(He=>He.id===K),le=ge();if(le.bindingEngine){const He=[K];he?.initGlobal&&He.push("_global"),he?.initLocal&&He.push("_local"),le.bindingEngine.invalidateCacheByKeys(He)}ae({[K]:ue});const Oe=_.find(He=>He.type==="websocket"&&(He.target_source||He.id)===K)?.onReceive;Array.isArray(Oe)&&Oe.length>0&&window.G7Core?.dispatch&&(async()=>{for(const Pe of Oe)try{const ke=this.getActionDispatcher?.();ke&&await ke.dispatchAction(Pe,{navigate:this.getRouter?.()?(Ot,ut)=>this.getRouter().navigate(Ot,ut):void 0,setState:Ot=>this.setGlobalState(Ot),state:this.globalState,data:{...this.globalState,$args:[ue],$event:ue},_isDispatchFallbackContext:!0})}catch(ke){B.error(`WebSocket onReceive action failed for ${K}:`,ke)}})()},A),B.log("WebSocket subscriptions established:",this.currentWebSocketSubscriptions)}B.log("Layout rendered successfully")}catch(r){B.error("Route change handling failed:",r),this.hideTransitionOverlay(),this.showRouteError(r)}}processInitOptions(e,n,r,i){e.some(u=>u.refetchOnMount===!0&&u.initLocal)&&(r._forceLocalInit=Date.now(),B.log("refetchOnMount detected, forcing local state init")),e.forEach(u=>{const d=n[u.id];if(!d)return;const f=d.data??d;if(u.initLocal){if(typeof u.initLocal=="string"){let g=f;if(u.initLocalDefaults&&typeof u.initLocalDefaults=="object"){const b=this.evaluateDefaults(u.initLocalDefaults,n);g={...b,...f},B.log(`initLocalDefaults applied for ${u.initLocal}:`,Object.keys(b))}const m=this.globalState._local?.[u.initLocal];m!==void 0&&typeof m=="object"&&typeof g=="object"?(r[u.initLocal]=this.deepMerge(m,g),B.log(`initLocal (merged): ${u.id}.data -> _local.${u.initLocal}`)):(r[u.initLocal]=g,B.log(`initLocal: ${u.id}.data -> _local.${u.initLocal}`))}else if(typeof u.initLocal=="object"&&u.initLocal.key){const{key:g,path:m}=u.initLocal;let b=m?this.extractValueByPathOrExpression(f,m,u.id):f;if(u.initLocalDefaults&&typeof u.initLocalDefaults=="object"){const S=this.evaluateDefaults(u.initLocalDefaults,n);b={...S,...b},B.log(`initLocalDefaults applied for ${g}:`,Object.keys(S))}const v=this.globalState._local?.[g];v!==void 0&&typeof v=="object"&&typeof b=="object"?(r[g]=this.deepMerge(v,b),B.log(`initLocal (merged): ${u.id}.data${m?"."+m:""} -> _local.${g}`)):(r[g]=b,B.log(`initLocal: ${u.id}.data${m?"."+m:""} -> _local.${g}`))}else if(typeof u.initLocal=="object"){const g=u.initLocal._merge||"deep",m=this.flattenNestedObjectToMappings(u.initLocal);for(const{targetPath:b,sourcePath:v}of m){const S=this.extractValueByPathOrExpression(f,v,u.id);if(b.includes(".")){const _=b.split(".")[0];r[_]===void 0&&this.globalState._local?.[_]!==void 0&&(r[_]=JSON.parse(JSON.stringify(this.globalState._local[_]))),this.setValueAtPath(r,b,S,g),B.log(`initLocal map (${g}): ${u.id} ${v} -> _local.${b}`)}else{const _=this.globalState._local?.[b];g==="replace"?(r[b]=S,B.log(`initLocal map (replace): ${u.id} ${v} -> _local.${b}`)):g==="shallow"?_!==void 0&&typeof _=="object"&&typeof S=="object"?(r[b]={..._,...S},B.log(`initLocal map (shallow): ${u.id} ${v} -> _local.${b}`)):(r[b]=S,B.log(`initLocal map: ${u.id} ${v} -> _local.${b}`)):_!==void 0&&typeof _=="object"&&typeof S=="object"?(r[b]=this.deepMerge(_,S),B.log(`initLocal map (deep): ${u.id} ${v} -> _local.${b}`)):(r[b]=S,B.log(`initLocal map: ${u.id} ${v} -> _local.${b}`))}}}}if(u.initGlobal)if(typeof u.initGlobal=="object"&&!Array.isArray(u.initGlobal)&&!("key"in u.initGlobal))for(const[m,b]of Object.entries(u.initGlobal)){if(typeof b!="string"){B.warn(`initGlobal map value must be string, got ${typeof b} for key ${m}`);continue}const v=this.extractValueByPathOrExpression(f,b,u.id),S=this.globalState[m];S!==void 0&&typeof S=="object"&&typeof v=="object"?(this.globalState[m]=this.deepMerge(S,v),B.log(`initGlobal map (merged): ${u.id} ${b} -> _global.${m}`)):(this.globalState[m]=v,B.log(`initGlobal map: ${u.id} ${b} -> _global.${m}`))}else{const m=Array.isArray(u.initGlobal)?u.initGlobal:[u.initGlobal];for(const b of m)if(typeof b=="string"){const v=this.globalState[b];v!==void 0&&typeof v=="object"&&typeof f=="object"?(this.globalState[b]=this.deepMerge(v,f),B.log(`initGlobal (merged): ${u.id}.data -> _global.${b}`)):(this.globalState[b]=f,B.log(`initGlobal: ${u.id}.data -> _global.${b}`))}else if(typeof b=="object"&&b.key){const{key:v,path:S}=b,_=S?this.extractValueByPathOrExpression(f,S,u.id):f,x=this.globalState[v];x!==void 0&&typeof x=="object"&&typeof _=="object"?(this.globalState[v]=this.deepMerge(x,_),B.log(`initGlobal (merged): ${u.id}.data${S?"."+S:""} -> _global.${v}`)):(this.globalState[v]=_,B.log(`initGlobal: ${u.id}.data${S?"."+S:""} -> _global.${v}`))}}if(i&&u.initIsolated){if(typeof u.initIsolated=="string"){const g=i[u.initIsolated];g!==void 0&&typeof g=="object"&&typeof f=="object"?(i[u.initIsolated]=this.deepMerge(g,f),B.log(`initIsolated (merged): ${u.id}.data -> _isolated.${u.initIsolated}`)):(i[u.initIsolated]=f,B.log(`initIsolated: ${u.id}.data -> _isolated.${u.initIsolated}`))}else if(typeof u.initIsolated=="object"&&u.initIsolated.key){const{key:g,path:m}=u.initIsolated,b=m?this.extractValueByPathOrExpression(f,m,u.id):f,v=i[g];v!==void 0&&typeof v=="object"&&typeof b=="object"?(i[g]=this.deepMerge(v,b),B.log(`initIsolated (merged): ${u.id}.data${m?"."+m:""} -> _isolated.${g}`)):(i[g]=b,B.log(`initIsolated: ${u.id}.data${m?"."+m:""} -> _isolated.${g}`))}else if(typeof u.initIsolated=="object")for(const[g,m]of Object.entries(u.initIsolated)){if(typeof m!="string"){B.warn(`initIsolated map value must be string, got ${typeof m} for key ${g}`);continue}const b=this.extractValueByPathOrExpression(f,m,u.id),v=i[g];v!==void 0&&typeof v=="object"&&typeof b=="object"?(i[g]=this.deepMerge(v,b),B.log(`initIsolated map (merged): ${u.id} ${m} -> _isolated.${g}`)):(i[g]=b,B.log(`initIsolated map: ${u.id} ${m} -> _isolated.${g}`))}}})}evaluateDefaults(e,n){const r={};for(const[i,l]of Object.entries(e))if(typeof l=="string"&&l.startsWith("{{")&&l.endsWith("}}")){const u=l.slice(2,-2).trim();r[i]=this.evaluateExpression(u,n)}else r[i]=l;return r}evaluateExpression(e,n){try{const r=e.split("??").map(i=>i.trim());for(const i of r){if(/^['"].*['"]$/.test(i))return i.slice(1,-1);if(i==="true")return!0;if(i==="false")return!1;if(/^-?\d+(\.\d+)?$/.test(i))return Number(i);const l=this.getNestedValue(n,i);if(l!=null)return l}return}catch(r){B.warn(`Expression evaluation failed: ${e}`,r);return}}getNestedValue(e,n){const r=n.replace(/\?\./g,".");let i=e;const l=r.split(/\.(?![^\[]*\])/).flatMap(u=>{const d=u.match(/^([^\[]*)((?:\[\d+\])*)$/);if(d){const[,f,g]=d,m=[];f&&m.push(f);const b=g.match(/\[\d+\]/g);return b&&m.push(...b),m}return[u]}).filter(u=>u!=="");for(const u of l){if(i==null)return;if(u.startsWith("[")&&u.endsWith("]")){const d=parseInt(u.slice(1,-1),10);i=i[d]}else i=i[u]}return i}extractValueByPathOrExpression(e,n,r){const i=n.match(/^\{\{(.+)\}\}$/);if(i){const l=i[1].trim(),u=new Rn,d={data:e,_global:this.globalState,_local:this.globalState._local||{}};try{const f=u.evaluateExpression(l,d);return B.log(`initLocal/initGlobal expression evaluated: ${n} -> `,f),f}catch(f){B.warn(`initLocal/initGlobal expression evaluation failed for ${r}:`,n,f);return}}return this.getNestedValue(e,n)}async loadLayoutScripts(e,n){const r=[];for(const i of e){if((i.if!==void 0||i.conditions!==void 0)&&!Qi({if:i.if,conditions:i.conditions},n,this.bindingEngine,`script:${i.id}`)){B.log(`Script skipped (condition not met): ${i.id}`);continue}if(document.getElementById(i.id)){B.log(`Script already loaded: ${i.id}`);continue}const u=new Promise((d,f)=>{const g=document.createElement("script");g.src=i.src,g.id=i.id,g.async=i.async??!0,g.onload=()=>{B.log(`Script loaded successfully: ${i.id}`),d()},g.onerror=()=>{B.warn(`Failed to load script: ${i.id} (${i.src})`),d()},document.head.appendChild(g)});r.push(u)}r.length>0&&(await Promise.all(r),B.log(`All scripts loaded: ${e.filter(i=>!document.getElementById(i.id)||r.length>0).map(i=>i.id).join(", ")}`))}evaluateScriptCondition(e,n){try{if(e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2).trim();return new Function("ctx",` + `}static renderFromError(e,a,r,i=!1,l,u,d){let f,g;if(a.userMessageKey.startsWith("$t:"))if(u&&l)try{f=u.translate(a.userMessageKey,l)}catch{f=a.userMessageKey.replace("$t:","")}else f=a.userMessageKey.replace("$t:","");else f=a.userMessageKey;if(d){const m=d;m.details?.apiMessage?g=m.details.apiMessage:m.response?.data?.message&&(g=m.response.data.message)}this.render(e,{title:r,message:f,detailMessage:g,icon:a.icon,showStack:a.showStack,stackTrace:a.stack,showReloadButton:a.recoverable,debug:i})}static escapeHtml(e){if(!e)return"";const a=document.createElement("div");return a.textContent=e,a.innerHTML}}const ls=ct("Router");class B_{constructor(e){$(this,"routes",[]);$(this,"templateIdentifier");$(this,"eventHandlers",new Map);$(this,"pendingNavigation",null);$(this,"isNavigating",!1);$(this,"handlePopState",()=>{this.navigateToCurrentPath()});this.templateIdentifier=e,this.initPopstateListener()}initPopstateListener(){window.addEventListener("popstate",this.handlePopState)}on(e,a){this.eventHandlers.has(e)||this.eventHandlers.set(e,[]),this.eventHandlers.get(e).push(a)}async emit(e,...a){const r=this.eventHandlers.get(e);r&&await Promise.all(r.map(i=>i(...a)))}async loadRoutes(e){try{const a=e!==void 0&&e>0?e:null,r=await fetch(La(`/api/templates/${this.templateIdentifier}/routes`,"json",a));if(!r.ok)throw new Error(`Failed to load routes: ${r.statusText}`);const i=await r.json();if(!i.success)throw new Error("Failed to load routes from API");if(i.data&&Array.isArray(i.data.routes))this.routes=i.data.routes,ls.log(`Loaded ${this.routes.length} routes${e?` (v=${e})`:""}`);else throw new Error("Invalid routes data format")}catch(a){throw ls.error("Error loading routes:",a),a}}setRoutes(e){this.routes=e,ls.log(`Set ${this.routes.length} routes`)}match(e){const a=this.normalizePathname(e);for(const r of this.routes){const i=this.matchPattern(r.path,a);if(i!==null)return{route:r,params:i}}return null}normalizePathname(e){return e.length>1&&e.endsWith("/")?e.replace(/\/+$/,"")||"/":e}matchPattern(e,a){const r=[];let i=e.replace(/:([^/]+)/g,(f,g)=>(r.push(g),"([^/]+)"));i.startsWith("*/")&&(i="(?:/[^/]+)?"+i.slice(1)),i=i.replace(/\//g,"\\/");const l=new RegExp(`^${i}$`),u=a.match(l);if(!u)return null;const d={};return r.forEach((f,g)=>{d[f]=u[g+1]}),d}getRoutes(){return[...this.routes]}getAuthType(e,a){return e.auth_type?e.auth_type:a.startsWith("/admin")?"admin":"user"}async navigateToCurrentPath(){const e=window.location.pathname,a=window.location.search,r=this.match(e);if(!r){ls.warn(`No route matched for path: ${e}`),this.emit("routeNotFound",e);return}if(r.route.redirect){ls.log(`Redirecting from ${e} to ${r.route.redirect}`),this.navigate(r.route.redirect);return}if(r.route.auth_required){const u=this.getAuthType(r.route,e),d=ha.getInstance();let f=d.isAuthenticated()&&d.getAuthType()===u;if(f||(f=await d.checkAuth(u)),!f){const g=d.getLoginRedirectUrl(u,e+a);ls.log(`Not authenticated, redirecting to: ${g}`),window.location.href=g;return}}const i=new URLSearchParams(a),l={};for(const u of i.keys()){if(u in l)continue;const d=i.getAll(u);d.length>1?l[u]=d:d.length===1&&(u.endsWith("[]")?l[u]=d:l[u]=d[0])}await this.emit("routeChange",{path:e,layout:r.route.layout,endpoint:r.route.endpoint,params:{...r.route.params||{},...r.params},query:l,auth_required:r.route.auth_required,auth_type:r.route.auth_type,meta:r.route.meta})}navigate(e){if(this.isNavigating){this.pendingNavigation=e;return}this.executeNavigation(e)}async executeNavigation(e){this.isNavigating=!0,this.pendingNavigation=null;try{window.history.pushState({},"",e),await this.navigateToCurrentPath()}finally{if(this.isNavigating=!1,this.pendingNavigation){const a=this.pendingNavigation;this.executeNavigation(a)}}}}const P_=/^\/admin\/layout-editor\/([^/?#]+)\/?$/;function sy(o){const e=P_.exec(o);return e?{templateIdentifier:e[1]}:null}const zt=ct("ErrorPageHandler");class q_{constructor(e){$(this,"templateId");$(this,"layoutLoader");$(this,"locale");$(this,"debug");$(this,"renderFunction");$(this,"dataSourceManager");$(this,"globalState");$(this,"errorConfig",null);$(this,"configLoaded",!1);this.templateId=e.templateId,this.layoutLoader=e.layoutLoader,this.locale=e.locale,this.debug=e.debug,this.renderFunction=e.renderFunction,this.dataSourceManager=e.dataSourceManager,this.globalState=e.globalState||{}}async loadConfig(){if(this.configLoaded)return this.errorConfig;try{const e=await fetch(La(`/api/templates/${this.templateId}/config`,"json"));if(!e.ok)return this.debug&&zt.warn("Failed to load template config:",e.statusText),this.configLoaded=!0,null;const a=await e.json();if(!a.success||!a.data)return this.debug&&zt.warn("Invalid template config response"),this.configLoaded=!0,null;const r=a.data;return!r.error_config||!r.error_config.layouts?(this.debug&&zt.warn("No error_config found in template.json"),this.configLoaded=!0,null):(this.errorConfig=r.error_config,this.configLoaded=!0,this.debug&&zt.log("Error config loaded:",this.errorConfig),this.errorConfig)}catch(e){return zt.error("Failed to load error config:",e),this.configLoaded=!0,null}}async renderError(e,a="app"){try{const r=await this.loadConfig();if(!r)return this.debug&&zt.warn("No error config available, cannot render error page"),!1;const i=r.layouts[e]||r.layouts[String(e)];if(!i)return this.debug&&zt.warn(`No layout defined for error code: ${e}`),!1;this.debug&&zt.log(`Loading error layout: ${i} for code: ${e}`);const l=await this.layoutLoader.loadLayout(this.templateId,i);if(!l)return this.debug&&zt.error(`Failed to load error layout: ${i}`),!1;this.debug&&zt.log("Error layout loaded:",l);let u={};const d=l.data_sources||[];if(d.length>0){this.debug&&zt.log("Fetching data sources:",d.map(S=>S.id));const m=d.filter(S=>S.loading_strategy==="blocking"),b=d.filter(S=>!S.loading_strategy||S.loading_strategy==="progressive"),v=[...m,...b];if(v.length>0)try{u=await this.dataSourceManager.fetchDataSources(v,{},new URLSearchParams),this.debug&&zt.log("Data sources fetched:",Object.keys(u))}catch(S){zt.error("Failed to fetch data sources:",S)}}const f={};d.length>0&&(this.processInitOptions(d,u,f),this.debug&&zt.log("initOptions processed:",{globalKeys:Object.keys(this.globalState),localKeys:Object.keys(f)}));const g={...u,errorCode:e,_global:{...this.globalState},_local:{...f}};return await this.renderFunction({containerId:a,layoutJson:l,dataContext:g,translationContext:{templateId:this.templateId,locale:this.locale}}),this.debug&&zt.log(`Error page ${e} rendered successfully`),!0}catch(r){return zt.error(`Failed to render error page ${e}:`,r),!1}}processInitOptions(e,a,r){for(const i of e){const l=a[i.id];if(!l)continue;const u=l?.data??l;if(i.initLocal){if(typeof i.initLocal=="string")r[i.initLocal]=u,zt.log(`initLocal: ${i.id}.data -> _local.${i.initLocal}`);else if(typeof i.initLocal=="object"&&"key"in i.initLocal){const{key:d,path:f}=i.initLocal;r[d]=f?this.getValueByPath(u,f):u,zt.log(`initLocal: ${i.id}.data${f?"."+f:""} -> _local.${d}`)}}if(i.initGlobal){const d=Array.isArray(i.initGlobal)?i.initGlobal:[i.initGlobal];for(const f of d)if(typeof f=="string")this.globalState[f]=u,zt.log(`initGlobal: ${i.id}.data -> _global.${f}`);else if(typeof f=="object"&&f!==null&&"key"in f){const{key:g,path:m}=f;this.globalState[g]=m?this.getValueByPath(u,m):u,zt.log(`initGlobal: ${i.id}.data${m?"."+m:""} -> _global.${g}`)}}}}getValueByPath(e,a){return a.split(".").reduce((r,i)=>r?.[i],e)}updateGlobalState(e){this.globalState={...this.globalState,...e}}updateLocale(e){this.locale=e,this.debug&&zt.log("Locale updated:",e)}isConfigLoaded(){return this.configLoaded}async hasErrorLayout(e){const a=await this.loadConfig();return a?!!(a.layouts[e]||a.layouts[String(e)]):!1}clearConfigCache(){this.errorConfig=null,this.configLoaded=!1,this.debug&&zt.log("Config cache cleared")}}const $t=ct("ModuleAssetLoader");class G_{constructor(){$(this,"loadedAssets",new Map);$(this,"loadingPromises",new Map);$(this,"failedJsAssets",new Set)}hasFailedJsAssets(){return this.failedJsAssets.size>0}getFailedJsAssets(){return[...this.failedJsAssets]}async loadActiveExtensionAssets(e){if(!e||e.length===0){$t.log("No module assets to load");return}const a=[...e].sort((d,f)=>d.priority-f.priority);$t.log("Loading module assets:",a.map(d=>d.identifier));const r=a.filter(d=>d.css).map(d=>this.loadCSS(d.identifier,d.css)),i=a.filter(d=>d.js).map(d=>this.loadJS(d.identifier,d.js)),l=await Promise.allSettled([...r,...i]),u=l.filter(d=>d.status==="rejected");if(u.length>0){$t.warn(`Some module assets failed to load (${u.length}/${l.length}); continuing with the rest`,this.getFailedJsAssets());return}$t.log("All module assets loaded successfully")}async loadBundle(e,a,r){const i=[];if(r&&i.push(this.loadBundleCss(e,r)),a&&i.push(this.loadBundleJs(e,a)),i.length===0){$t.log(`No bundle assets to load for: ${e}`);return}await Promise.all(i)}async loadBundleCss(e,a){const r=`ext-bundle-css-${e}`;if(document.getElementById(r)){$t.log(`Bundle CSS already loaded: ${e}`);return}return new Promise(i=>{const l=document.createElement("link");l.rel="stylesheet",l.href=a,l.id=r,l.onload=()=>{$t.log(`Bundle CSS loaded: ${e}`),this.registerLoadedAsset(`bundle-${e}`,{type:"css",element:l}),i()},l.onerror=()=>{$t.warn(`Failed to load bundle CSS: ${e} (${a})`),i()},document.head.appendChild(l)})}async loadBundleJs(e,a){const r=`ext-bundle-js-${e}`;if(document.getElementById(r)){$t.log(`Bundle JS already loaded: ${e}`);return}const i=this.loadingPromises.get(r);if(i)return $t.log(`Bundle JS already loading: ${e}`),i;const l=Jg(a,{id:r},{label:`bundle JS: ${e}`}).then(()=>{$t.log(`Bundle JS loaded: ${e}`);const u=document.getElementById(r);u&&this.registerLoadedAsset(`bundle-${e}`,{type:"js",element:u}),this.loadingPromises.delete(r)}).catch(u=>{throw $t.warn(`Failed to load bundle JS: ${e} (${a})`,u),this.failedJsAssets.add(e),this.loadingPromises.delete(r),u});return this.loadingPromises.set(r,l),l}async loadCSS(e,a){const r=`module-css-${e}`;if(document.getElementById(r)){$t.log(`CSS already loaded: ${e}`);return}return new Promise((i,l)=>{const u=document.createElement("link");u.rel="stylesheet",u.href=a,u.id=r,u.onload=()=>{$t.log(`CSS loaded: ${e}`),this.registerLoadedAsset(e,{type:"css",element:u}),i()},u.onerror=()=>{$t.warn(`Failed to load CSS: ${e} (${a})`),i()},document.head.appendChild(u)})}async loadJS(e,a){const r=`module-js-${e}`;if(document.getElementById(r)){$t.log(`JS already loaded: ${e}`);return}const i=this.loadingPromises.get(e);if(i)return $t.log(`JS already loading: ${e}`),i;const l=Jg(a,{id:r},{label:`JS: ${e}`}).then(()=>{$t.log(`JS loaded: ${e}`);const u=document.getElementById(r);u&&this.registerLoadedAsset(e,{type:"js",element:u}),this.loadingPromises.delete(e)}).catch(u=>{throw $t.warn(`Failed to load JS: ${e} (${a})`,u),this.failedJsAssets.add(e),this.loadingPromises.delete(e),u});return this.loadingPromises.set(e,l),l}registerLoadedAsset(e,a){const r=this.loadedAssets.get(e)||[];r.push(a),this.loadedAssets.set(e,r)}unloadExtensionAsset(e){const a=this.loadedAssets.get(e);if(!a||a.length===0){$t.log(`No assets to unload for: ${e}`);return}a.forEach(r=>{r.element.parentNode&&(r.element.parentNode.removeChild(r.element),$t.log(`${r.type.toUpperCase()} unloaded: ${e}`))}),this.loadedAssets.delete(e),$t.log(`All assets unloaded for: ${e}`)}unloadAllAssets(){Array.from(this.loadedAssets.keys()).forEach(a=>{this.unloadExtensionAsset(a)}),$t.log("All module assets unloaded")}isLoaded(e){return this.loadedAssets.has(e)}getLoadedModules(){return Array.from(this.loadedAssets.keys())}}let df=null;function ff(){return df||(df=new G_),df}function V_(){if(typeof window>"u")return[];const o=window.G7Config;if(!o?.moduleAssets)return[];const e=[];for(const[a,r]of Object.entries(o.moduleAssets)){const i=r;e.push({identifier:a,js:i.js?Cr(i.js):i.js,css:i.css?Cr(i.css):i.css,priority:i.priority,external:i.external})}return e}function F_(){if(typeof window>"u")return null;const o=window.G7Config;if(!o?.bundleUrls)return null;const e=o.bundleUrls;return{moduleJs:e.moduleJs?Cr(e.moduleJs):e.moduleJs,moduleCss:e.moduleCss?Cr(e.moduleCss):e.moduleCss,pluginJs:e.pluginJs?Cr(e.pluginJs):e.pluginJs,pluginCss:e.pluginCss?Cr(e.pluginCss):e.pluginCss}}function K_(){if(typeof window>"u")return[];const o=window.G7Config;if(!o?.pluginAssets)return[];const e=[];for(const[a,r]of Object.entries(o.pluginAssets)){const i=r;e.push({identifier:a,js:i.js?Cr(i.js):i.js,css:i.css?Cr(i.css):i.css,priority:i.priority,external:i.external})}return e}const hf=ct("SystemBannerManager");class mc{static show(e){this.banners.set(e.id,e),this.render(),hf.log(`Banner shown: ${e.id}`)}static hide(e){this.banners.delete(e)&&(this.render(),hf.log(`Banner hidden: ${e}`))}static hideAll(){this.banners.clear(),this.render(),hf.log("All banners hidden")}static detectLocale(){try{const a=window.G7Core;if(a?.locale?.current)return a.locale.current()}catch{}return(navigator.language||"ko").split("-")[0]}static resolveMessage(e,a){return typeof e=="string"?e:e[a]||e.en||e.ko||Object.values(e)[0]||""}static render(){if(typeof document>"u")return;let e=document.getElementById(this.containerId);if(this.banners.size===0){e&&(e.remove(),this.adjustAppPadding(0));return}e||(e=document.createElement("div"),e.id=this.containerId,e.style.cssText="position:fixed;top:0;left:0;right:0;z-index:99999;",document.body.prepend(e));const a=Array.from(this.banners.values()).sort((i,l)=>(i.order??0)-(l.order??0)),r=this.detectLocale();e.innerHTML=a.map(i=>{const l=this.resolveMessage(i.message,r),u=i.background||"#f59e0b",d=i.color||"white";return`
${l}
`}).join(""),requestAnimationFrame(()=>{e&&this.adjustAppPadding(e.offsetHeight)})}static adjustAppPadding(e){const a=document.getElementById("app");a&&(a.style.paddingTop=e>0?`${e}px`:"")}}$(mc,"banners",new Map),$(mc,"containerId","g7-system-banners");const B=ct("TemplateApp");function oy(o){const e={};for(const a of o.keys()){if(a in e)continue;const r=o.getAll(a);r.length>1?e[a]=r:r.length===1&&(a.endsWith("[]")?e[a]=r:e[a]=r[0])}return e}const Ca=class Ca{constructor(e){$(this,"router",null);$(this,"layoutLoader",null);$(this,"errorPageHandler",null);$(this,"config");$(this,"globalState");$(this,"globalStateListeners",new Set);$(this,"currentRouteChangeId",0);$(this,"currentDataSources",[]);$(this,"currentRawDataSources",[]);$(this,"currentRouteParams",{});$(this,"currentQueryParams",new URLSearchParams);$(this,"currentFetchedData",{});$(this,"currentLayoutName","");$(this,"templateErrorHandling",null);$(this,"currentWebSocketSubscriptions",[]);$(this,"extensionCacheVersion",0);$(this,"currentGlobalHeaders",[]);$(this,"transitionOverlayEl",null);$(this,"skeletonOverlayRoot",null);$(this,"skeletonOverlayContainer",null);$(this,"_spinnerState",null);$(this,"modalDataSources",new Map);this.globalState={sidebarOpen:!1},this.loadG7Config(),this.migrateLocaleStorage();const a=this.loadLocaleFromStorage(),r=a||e.locale||"ko";this.config={...e,locale:r},!a&&e.locale&&this.saveLocaleToStorage(e.locale)}loadG7Config(){if(typeof window<"u"&&window.G7Config){const e=window.G7Config;e.settings&&(this.globalState.settings=e.settings,B.log("Loaded settings from G7Config:",Object.keys(e.settings)),e.settings.upload&&(this.globalState.uploadSettings=e.settings.upload)),e.plugins&&(this.globalState.plugins=e.plugins,B.log("Loaded plugin settings from G7Config:",Object.keys(e.plugins))),e.modules&&(this.globalState.modules=e.modules,B.log("Loaded module settings from G7Config:",Object.keys(e.modules))),e.appConfig&&(this.globalState.appConfig=e.appConfig,B.log("Loaded appConfig from G7Config:",Object.keys(e.appConfig)))}}resolveRouteExpressions(e){const a=new wn,r={_global:this.globalState};return e.map(i=>{const l={...i};if(l.path&&l.path.includes("{{")){const u=l.path;l.path=a.resolveBindings(l.path,r),l.path=l.path.replace(/\/\/+/g,"/")||"/",l.path.includes("{{")&&(B.warn("Route expression resolution failed, using fallback:",u),l.path=u.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}if(l.redirect&&l.redirect.includes("{{")){const u=l.redirect;l.redirect=a.resolveBindings(l.redirect,r),l.redirect=l.redirect.replace(/\/\/+/g,"/")||"/",l.redirect.includes("{{")&&(B.warn("Route redirect expression resolution failed:",u),l.redirect=u.replace(/\{\{[^}]+\}\}/g,"").replace(/\/\/+/g,"/")||"/")}return l})}migrateLocaleStorage(){const e=["locale","g7_template_locale"];for(const a of e)try{const r=localStorage.getItem(a);r&&r!==localStorage.getItem(Ca.LOCALE_STORAGE_KEY)&&(localStorage.setItem(Ca.LOCALE_STORAGE_KEY,r),B.log(`Migrated locale from '${a}' to '${Ca.LOCALE_STORAGE_KEY}'`)),localStorage.removeItem(a)}catch(r){B.warn(`Failed to migrate locale key '${a}':`,r)}}async init(){try{Ws.getInstance().setDebug(this.config.debug),B.log("Initializing with config:",this.config),US();const e=da.getInstance(),a=ha.getInstance(),r=this.loadCacheVersionFromStorage()||0,[i,l,u,d,f]=await Promise.all([Ao({templateId:this.config.templateId,templateType:this.config.templateType,locale:this.config.locale,debug:this.config.debug,cacheVersion:r}),e.loadComponents(this.config.templateId,this.config.templateType),xl(La(`/api/templates/${this.config.templateId}/routes`,"json",r>0?r:null),{label:"routes.json"}).then(A=>{if(!A.ok)throw new Error(`Failed to load routes: ${A.statusText}`);return A.json()}).then(A=>{if(!A.success)throw new Error("Failed to load routes from API");return A.data}).catch(A=>{throw B.error("Error loading routes:",A),A}),a.preloadAuth(this.config.templateType==="admin"?"admin":"user"),fetch(La(`/api/templates/${this.config.templateId}/config`,"json")).then(A=>A.ok?A.json():null).then(A=>!A?.success||!A?.data?null:A.data).catch(A=>(B.warn("Error loading template config:",A),null))]);if(B.log("Template Engine initialized"),B.log("ComponentRegistry loaded"),B.log("Routes data loaded"),B.log("User info preloaded"),B.log("Template config loaded:",f),f?.cache_version!==void 0){const A=this.loadCacheVersionFromStorage();if(this.extensionCacheVersion=f.cache_version,this.saveCacheVersionToStorage(this.extensionCacheVersion),B.log("Extension cache version:",this.extensionCacheVersion),A!==null&&A!==this.extensionCacheVersion){B.log("Cache version changed, reloading routes...");const T=await xl(La(`/api/templates/${this.config.templateId}/routes`,"json",this.extensionCacheVersion),{label:"routes.json (reload)"}).then(L=>{if(!L.ok)throw new Error(`Failed to reload routes: ${L.statusText}`);return L.json()}).then(L=>{if(!L.success)throw new Error("Failed to reload routes from API");return L.data});Array.isArray(T.routes)&&(T.routes=this.resolveRouteExpressions(T.routes),Object.assign(u,T),B.log("Routes reloaded with new cache version"));try{const{TranslationEngine:L}=await Promise.resolve().then(()=>np),M=L.getInstance();M.setCacheVersion(this.extensionCacheVersion);const R=this.config.locale||"ko",D="en";await M.loadTranslations(this.config.templateId,R,"/api",!0),R!==D&&await M.loadTranslations(this.config.templateId,D,"/api",!0),B.log("Translations reloaded with new cache version")}catch(L){B.error("Failed to reload translations:",L)}}}f?.errorHandling&&(this.templateErrorHandling=f.errorHandling,pa().setTemplateConfig(this.templateErrorHandling),B.log("Template errorHandling registered:",this.templateErrorHandling)),a.on("logout",()=>{B.log("User logged out")}),a.on("authStateChange",A=>{B.log("Auth state changed:",A)}),B.log("AuthManager event handlers registered");const g=Na(),m=this.config.templateType==="admin"?"admin":"user",b=a.getConfig(m);g.setOnUnauthorized(()=>{B.log("Unauthorized - redirecting to login page"),g.removeToken();const A=window.location.pathname+window.location.search,T=a.getLoginRedirectUrl(m,A,"session_expired");window.location.href=T}),B.log("ApiClient onUnauthorized callback registered"),this.layoutLoader=new nf(e),this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),B.log("LayoutLoader initialized:",this.layoutLoader);const v=new va({onUnauthorized:()=>{B.warn("Unauthorized request in error page")}});if(this.errorPageHandler=new q_({templateId:this.config.templateId,layoutLoader:this.layoutLoader,locale:this.config.locale,debug:this.config.debug,renderFunction:cs,dataSourceManager:v,globalState:this.globalState}),B.log("ErrorPageHandler initialized with DataSourceManager"),await this.handleServerError()){B.log("Server error handled, skipping normal initialization");return}if(this.router=new B_(this.config.templateId),Array.isArray(u.routes)){const A=this.resolveRouteExpressions(u.routes);this.router.setRoutes(A)}else throw new Error("Invalid routes data format");B.log("Router initialized:",this.router),B.log("Routes set:",this.router.getRoutes());const{getActionDispatcher:S}=await Promise.resolve().then(()=>ja),E=S();if(E&&(E.setDefaultContext({navigate:A=>this.router?.navigate(A)}),E.setGlobalStateUpdater((A,T)=>this.setGlobalState(A,T)),B.log("Navigate function and setGlobalState injected to ActionDispatcher")),await this.loadExtensionAssets(),this.reinitializeTemplateHandlers(),this.router.on("routeChange",A=>this.handleRouteChange(A)),this.router.on("routeNotFound",A=>this.handleRouteNotFound(A)),typeof window<"u"&&sy(window.location.pathname)){B.log("Layout editor mode detected — skipping router match"),await cs({containerId:"app",layoutJson:{components:[]},dataContext:{},translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),B.log("Template App initialized in layout editor mode");return}this.router.navigateToCurrentPath(),B.log("Template App initialized successfully")}catch(e){B.error("Initialization failed:",e),this.showInitError(e)}}async loadExtensionAssets(){try{const e=ff(),a=F_();if(!a){await this.loadExtensionAssetsIndividually();return}await e.loadBundle("module",a.moduleJs,a.moduleCss),await e.loadBundle("plugin",a.pluginJs,a.pluginCss),B.log("Extension bundle assets loaded successfully")}catch(e){B.warn("Failed to load extension assets:",e)}}async loadExtensionAssetsIndividually(){const e=ff(),a=V_();a.length>0&&(B.log("Loading module assets (individual fallback):",a.map(i=>i.identifier)),await e.loadActiveExtensionAssets(a));const r=K_();r.length>0&&(B.log("Loading plugin assets (individual fallback):",r.map(i=>i.identifier)),await e.loadActiveExtensionAssets(r))}async handleRouteChange(e){const a=++this.currentRouteChangeId;window.__g7ForcedLocalFields=void 0,window.__g7ActionContext=void 0,window.__g7PendingLocalState=void 0,window.__g7LastSetLocalSnapshot=void 0,window.__g7SetLocalOverrideKeys=void 0,window.__g7SequenceLocalSync=void 0,window.__g7AutoBindingPaths=new Map;try{if(B.log("Route changed:",e,"requestId:",a),!this.layoutLoader)throw new Error("LayoutLoader is not initialized");if(!e.layout)throw new Error("Route layout is not defined");let r=e.layout;const i=r==="__preview__";i&&e.params?.token&&(r=`__preview__/${e.params.token}`);const l=await this.layoutLoader.loadLayout(this.config.templateId,r);if(a!==this.currentRouteChangeId){B.log("Route change cancelled (newer request exists):",a);return}B.log("Layout loaded:",l);const u=pa();l.errorHandling?(u.setLayoutConfig(l.errorHandling),B.log("Layout errorHandling registered:",l.errorHandling)):u.clearLayoutConfig();const d=l.data_sources||[],f=e.query||{},g=new URLSearchParams;for(const[te,he]of Object.entries(f))if(Array.isArray(he))for(const x of he)g.append(te,x);else g.set(te,he);const m={route:e.params||{},query:f,_global:this.globalState};l.scripts&&Array.isArray(l.scripts)&&await this.loadLayoutScripts(l.scripts,m);const{DataSourceManager:b,getActionDispatcher:v}=await Promise.resolve().then(()=>ja),S=new b;if(i){const te=v();te&&te.setPreviewMode(!0),this.setGlobalState({__isPreview:!0}),mc.show({id:"preview-mode",message:{ko:"⚠ 미리보기 모드 — 페이지 이동이 비활성화됩니다",en:"⚠ Preview Mode — Navigation is disabled"},background:"linear-gradient(90deg, #f59e0b, #d97706)",color:"white"}),B.log("Preview mode activated")}else{const te=v();te?.isPreviewMode()&&(te.setPreviewMode(!1),mc.hide("preview-mode"))}if(this.currentGlobalHeaders=l.globalHeaders||[],this.currentGlobalHeaders.length>0){S.setGlobalHeaders(this.currentGlobalHeaders);const te=v();te&&te.setGlobalHeaders(this.currentGlobalHeaders),B.log("globalHeaders set:",this.currentGlobalHeaders.map(he=>he.pattern))}if(l.named_actions&&Object.keys(l.named_actions).length>0){const te=v();te&&te.setNamedActions(l.named_actions)}const E=S.filterByCondition(d,m);d.length!==E.length&&B.log("Data sources filtered by condition:",{before:d.map(te=>te.id),after:E.map(te=>te.id)});const A=E.filter(te=>te.loading_strategy==="blocking"),T=E.filter(te=>(te.loading_strategy||"progressive")!=="blocking"&&te.type!=="websocket"),L=Array.isArray(l.transition_overlay?.wait_for)?l.transition_overlay.wait_for:[],M=L.length>0&&E.some(te=>L.includes(te.id)&&te.type!=="websocket"&&(te.loading_strategy||"progressive")!=="background");if((A.length>0||M)&&l.transition_overlay){const te=typeof l.transition_overlay=="boolean"?{enabled:l.transition_overlay,style:"opaque"}:l.transition_overlay;te.enabled&&te.style==="skeleton"&&te.skeleton?.component&&te.target?this.renderSkeletonOverlay(te.target,te.skeleton,l,te.fallback_target):te.enabled&&te.style==="spinner"&&te.target&&this.renderSpinnerOverlay(te.target,te.spinner,te.fallback_target)}let R={},D={},U={},j={};this.currentWebSocketSubscriptions.length>0&&(B.log("Unsubscribing previous WebSocket subscriptions:",this.currentWebSocketSubscriptions),S.unsubscribeWebSockets(this.currentWebSocketSubscriptions),this.currentWebSocketSubscriptions=[]),this.currentDataSources=E,this.currentRawDataSources=d,this.currentRouteParams=e.params||{},this.currentQueryParams=g,B.log(`handleRouteChange #${a} - queryObject:`,f),B.log(`handleRouteChange #${a} - queryParams:`,g.toString()),A.length>0&&(B.log("Fetching blocking data sources:",A.map(he=>he.id)),(await S.fetchDataSourcesWithResults(A,e.params||{},g)).forEach(he=>{if(he.state==="success"&&he.data!==void 0)R[he.id]=he.data;else if(he.state==="error"&&he.error){const x=he.error,K=x.response?.data?.message;D[he.id]={message:K||he.error.message,status:x.response?.status||x.status}}}),this.processInitOptions(A,R,U,j),this.currentFetchedData={...R},B.log("Blocking data loaded:",Object.keys(R)),Object.keys(D).length>0&&B.log("Data source errors:",D),Object.keys(U).length>0&&B.log("Local state init (blocking):",Object.keys(U)),Object.keys(j).length>0&&B.log("Isolated state init (blocking):",Object.keys(j)));const P=T.length>0;if(P&&(Ia.setPending(!0),B.log("Transition started before rendering")),a!==this.currentRouteChangeId){B.log("Route change cancelled after blocking data (newer request exists):",a);return}const{getState:X}=await Promise.resolve().then(()=>ja),ge=X().currentDataContext||{},Ee=T.map(te=>te.id),Ce={};if(P){Ee.forEach(he=>{ge[he]!==void 0?Ce[he]=ge[he]:Ce[he]=void 0});const te=Object.keys(Ce).filter(he=>Ce[he]!==void 0);te.length>0&&B.log("Preserving previous progressive data:",te),B.log("Progressive data sources initialized:",Ee)}const $e=l.defines||{},Pe={...Ce,...e.params,...R,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState},_globalSetState:te=>this.setGlobalState(te),_dataSourceErrors:Object.keys(D).length>0?D:void 0,_localInit:Object.keys(U).length>0?U:void 0,_isolatedInit:Object.keys(j).length>0?j:void 0,_defines:Object.keys($e).length>0?$e:void 0};if(l.computed&&Object.keys(l.computed).length>0){const te=this.calculateComputed(l.computed,Pe);Object.keys(te).length>0&&(Pe._computed=te,this.globalState._computed=te,B.log("Computed values calculated:",Object.keys(te))),Pe._computedDefinitions=l.computed}const Ye=l.layout_name||e.layout;this.currentLayoutName!==""&&this.currentLayoutName!==Ye&&(B.log("_local reset due to layout change:",{from:this.currentLayoutName,to:Ye}),this.globalState._local={},aC()),this.currentLayoutName=Ye;const Q=l.initLocal||l.state;if(Q&&Object.keys(Q).length>0){this.globalState._local||(this.globalState._local={});for(const[te,he]of Object.entries(Q))this.globalState._local[te]===void 0&&(this.globalState._local[te]=JSON.parse(JSON.stringify(he)));Pe._local={...this.globalState._local},B.log("initLocal applied to _local:",Object.keys(Q))}if(l.initGlobal&&Object.keys(l.initGlobal).length>0){for(const[te,he]of Object.entries(l.initGlobal))this.globalState[te]===void 0&&(this.globalState[te]=JSON.parse(JSON.stringify(he)));B.log("initGlobal applied to _global:",Object.keys(l.initGlobal))}if(l.initIsolated&&Object.keys(l.initIsolated).length>0){for(const[te,he]of Object.entries(l.initIsolated))j[te]===void 0&&(j[te]=JSON.parse(JSON.stringify(he)));B.log("initIsolated applied:",Object.keys(l.initIsolated))}Pe._global={...this.globalState,layoutWarnings:l.warnings||[]};const fe=l.initActions||l.init_actions;if(fe&&fe.length>0){await this.executeInitActions(fe,Pe),B.log("initActions executed before render");const te=this.globalState;if(te._local){if(Object.keys(U).length>0){for(const[he,x]of Object.entries(U))te._local[he]!==void 0?te._local[he]=this.deepMerge(te._local[he],x):te._local[he]=x;B.log("localInit merged into _local after initActions:",Object.keys(U))}Pe._local=te._local,B.log("_local merged into dataContext:",te._local)}if(Pe._global={...this.globalState,layoutWarnings:l.warnings||[]},B.log("_global merged into dataContext after initActions"),l.computed&&Object.keys(l.computed).length>0){const he=this.calculateComputed(l.computed,Pe);Object.keys(he).length>0&&(Pe._computed=he,this.globalState._computed=he,B.log("Computed values recalculated after init_actions:",Object.keys(he)))}if(Object.keys(this.currentFetchedData).length>0){for(const[he,x]of Object.entries(this.currentFetchedData))Pe[he]=x;B.log("Fetched data sources merged into dataContext after initActions:",Object.keys(this.currentFetchedData))}}if(l.transition_overlay&&!this.skeletonOverlayContainer&&this.showTransitionOverlay(l.transition_overlay,l),await cs({containerId:"app",layoutJson:l,dataContext:Pe,translationContext:{templateId:this.config.templateId,locale:this.config.locale}}),B.log("Initial render complete with blocking data"),this.reattachSpinnerOverlay(),T.length>0){if(a!==this.currentRouteChangeId){B.log("Route change cancelled before progressive fetch (newer request exists):",a),Ia.setPending(!1),this.hideTransitionOverlay();return}B.log("Fetching progressive/background data sources:",T.map(x=>x.id));const{updateTemplateData:te,getState:he}=await Promise.resolve().then(()=>ja);try{const x=T.map(async K=>{try{const de=(await S.fetchDataSourcesWithResults([K],e.params||{},g,this.globalState))[0];if(!de){B.log(`Data source ${K.id} skipped (no fetch result)`);return}if(a!==this.currentRouteChangeId){B.log(`Data source ${K.id} fetch cancelled (newer request exists)`);return}if(de.state==="success"&&de.data!==void 0){B.log(`Progressive data source loaded: ${K.id}`),this.currentFetchedData[K.id]=de.data;const ie={};this.processInitOptions([K],{[K.id]:de.data},ie);const ye=he();if(ye.bindingEngine){const Se=[K.id];K.initGlobal&&Se.push("_global"),K.initLocal&&Se.push("_local"),ye.bindingEngine.invalidateCacheByKeys(Se)}const Ne={[K.id]:de.data};Object.keys(ie).length>0&&(Ne._localInit=ie,Object.assign(U,ie)),K.initGlobal&&(Ne._global={...this.globalState}),te(Ne)}else if(de.state==="error"&&de.error){const ie=de.error,ye=ie.response?.data?.message;D[K.id]={message:ye||de.error.message,status:ie.response?.status||ie.status},B.log(`Progressive data source error: ${K.id}`,D[K.id]),te({[K.id]:null,_dataSourceErrors:{...D}})}}catch(re){B.error(`Failed to fetch data source: ${K.id}`,re)}});if(await Promise.all(x),a!==this.currentRouteChangeId){B.log("Route change cancelled after progressive fetch (newer request exists):",a);return}B.log("All progressive data sources loaded")}finally{Ia.setPending(!1),this.hideTransitionOverlay()}}else this.hideTransitionOverlay();const ve=E.filter(te=>te.type==="websocket");if(ve.length>0){B.log("Subscribing WebSocket data sources:",ve.map(K=>K.id));const{updateTemplateData:te,getState:he}=await Promise.resolve().then(()=>ja),x={...this.currentFetchedData,...e.params,route:{...e.params||{},path:e.path},query:f,_global:{...this.globalState}};B.log("WebSocket binding context keys:",Object.keys(x)),this.currentWebSocketSubscriptions=S.subscribeWebSockets(E,(K,re)=>{B.log(`WebSocket data received for: ${K}`,re),this.currentFetchedData[K]=re;const de=E.find(Se=>Se.id===K),ie=he();if(ie.bindingEngine){const Se=[K];de?.initGlobal&&Se.push("_global"),de?.initLocal&&Se.push("_local"),ie.bindingEngine.invalidateCacheByKeys(Se)}te({[K]:re});const Ne=E.find(Se=>Se.type==="websocket"&&(Se.target_source||Se.id)===K)?.onReceive;Array.isArray(Ne)&&Ne.length>0&&window.G7Core?.dispatch&&(async()=>{for(const He of Ne)try{const Ae=this.getActionDispatcher?.();Ae&&await Ae.dispatchAction(He,{navigate:this.getRouter?.()?(Et,kt)=>this.getRouter().navigate(Et,kt):void 0,setState:Et=>this.setGlobalState(Et),state:this.globalState,data:{...this.globalState,$args:[re],$event:re},_isDispatchFallbackContext:!0})}catch(Ae){B.error(`WebSocket onReceive action failed for ${K}:`,Ae)}})()},x),B.log("WebSocket subscriptions established:",this.currentWebSocketSubscriptions)}B.log("Layout rendered successfully")}catch(r){B.error("Route change handling failed:",r),this.hideTransitionOverlay(),this.showRouteError(r)}}processInitOptions(e,a,r,i){e.some(u=>u.refetchOnMount===!0&&u.initLocal)&&(r._forceLocalInit=Date.now(),B.log("refetchOnMount detected, forcing local state init")),e.forEach(u=>{const d=a[u.id];if(!d)return;const f=d.data??d;if(u.initLocal){if(typeof u.initLocal=="string"){let g=f;if(u.initLocalDefaults&&typeof u.initLocalDefaults=="object"){const b=this.evaluateDefaults(u.initLocalDefaults,a);g={...b,...f},B.log(`initLocalDefaults applied for ${u.initLocal}:`,Object.keys(b))}const m=this.globalState._local?.[u.initLocal];m!==void 0&&typeof m=="object"&&typeof g=="object"?(r[u.initLocal]=this.deepMerge(m,g),B.log(`initLocal (merged): ${u.id}.data -> _local.${u.initLocal}`)):(r[u.initLocal]=g,B.log(`initLocal: ${u.id}.data -> _local.${u.initLocal}`))}else if(typeof u.initLocal=="object"&&u.initLocal.key){const{key:g,path:m}=u.initLocal;let b=m?this.extractValueByPathOrExpression(f,m,u.id):f;if(u.initLocalDefaults&&typeof u.initLocalDefaults=="object"){const S=this.evaluateDefaults(u.initLocalDefaults,a);b={...S,...b},B.log(`initLocalDefaults applied for ${g}:`,Object.keys(S))}const v=this.globalState._local?.[g];v!==void 0&&typeof v=="object"&&typeof b=="object"?(r[g]=this.deepMerge(v,b),B.log(`initLocal (merged): ${u.id}.data${m?"."+m:""} -> _local.${g}`)):(r[g]=b,B.log(`initLocal: ${u.id}.data${m?"."+m:""} -> _local.${g}`))}else if(typeof u.initLocal=="object"){const g=u.initLocal._merge||"deep",m=this.flattenNestedObjectToMappings(u.initLocal);for(const{targetPath:b,sourcePath:v}of m){const S=this.extractValueByPathOrExpression(f,v,u.id);if(b.includes(".")){const E=b.split(".")[0];r[E]===void 0&&this.globalState._local?.[E]!==void 0&&(r[E]=JSON.parse(JSON.stringify(this.globalState._local[E]))),this.setValueAtPath(r,b,S,g),B.log(`initLocal map (${g}): ${u.id} ${v} -> _local.${b}`)}else{const E=this.globalState._local?.[b];g==="replace"?(r[b]=S,B.log(`initLocal map (replace): ${u.id} ${v} -> _local.${b}`)):g==="shallow"?E!==void 0&&typeof E=="object"&&typeof S=="object"?(r[b]={...E,...S},B.log(`initLocal map (shallow): ${u.id} ${v} -> _local.${b}`)):(r[b]=S,B.log(`initLocal map: ${u.id} ${v} -> _local.${b}`)):E!==void 0&&typeof E=="object"&&typeof S=="object"?(r[b]=this.deepMerge(E,S),B.log(`initLocal map (deep): ${u.id} ${v} -> _local.${b}`)):(r[b]=S,B.log(`initLocal map: ${u.id} ${v} -> _local.${b}`))}}}}if(u.initGlobal)if(typeof u.initGlobal=="object"&&!Array.isArray(u.initGlobal)&&!("key"in u.initGlobal))for(const[m,b]of Object.entries(u.initGlobal)){if(typeof b!="string"){B.warn(`initGlobal map value must be string, got ${typeof b} for key ${m}`);continue}const v=this.extractValueByPathOrExpression(f,b,u.id),S=this.globalState[m];S!==void 0&&typeof S=="object"&&typeof v=="object"?(this.globalState[m]=this.deepMerge(S,v),B.log(`initGlobal map (merged): ${u.id} ${b} -> _global.${m}`)):(this.globalState[m]=v,B.log(`initGlobal map: ${u.id} ${b} -> _global.${m}`))}else{const m=Array.isArray(u.initGlobal)?u.initGlobal:[u.initGlobal];for(const b of m)if(typeof b=="string"){const v=this.globalState[b];v!==void 0&&typeof v=="object"&&typeof f=="object"?(this.globalState[b]=this.deepMerge(v,f),B.log(`initGlobal (merged): ${u.id}.data -> _global.${b}`)):(this.globalState[b]=f,B.log(`initGlobal: ${u.id}.data -> _global.${b}`))}else if(typeof b=="object"&&b.key){const{key:v,path:S}=b,E=S?this.extractValueByPathOrExpression(f,S,u.id):f,A=this.globalState[v];A!==void 0&&typeof A=="object"&&typeof E=="object"?(this.globalState[v]=this.deepMerge(A,E),B.log(`initGlobal (merged): ${u.id}.data${S?"."+S:""} -> _global.${v}`)):(this.globalState[v]=E,B.log(`initGlobal: ${u.id}.data${S?"."+S:""} -> _global.${v}`))}}if(i&&u.initIsolated){if(typeof u.initIsolated=="string"){const g=i[u.initIsolated];g!==void 0&&typeof g=="object"&&typeof f=="object"?(i[u.initIsolated]=this.deepMerge(g,f),B.log(`initIsolated (merged): ${u.id}.data -> _isolated.${u.initIsolated}`)):(i[u.initIsolated]=f,B.log(`initIsolated: ${u.id}.data -> _isolated.${u.initIsolated}`))}else if(typeof u.initIsolated=="object"&&u.initIsolated.key){const{key:g,path:m}=u.initIsolated,b=m?this.extractValueByPathOrExpression(f,m,u.id):f,v=i[g];v!==void 0&&typeof v=="object"&&typeof b=="object"?(i[g]=this.deepMerge(v,b),B.log(`initIsolated (merged): ${u.id}.data${m?"."+m:""} -> _isolated.${g}`)):(i[g]=b,B.log(`initIsolated: ${u.id}.data${m?"."+m:""} -> _isolated.${g}`))}else if(typeof u.initIsolated=="object")for(const[g,m]of Object.entries(u.initIsolated)){if(typeof m!="string"){B.warn(`initIsolated map value must be string, got ${typeof m} for key ${g}`);continue}const b=this.extractValueByPathOrExpression(f,m,u.id),v=i[g];v!==void 0&&typeof v=="object"&&typeof b=="object"?(i[g]=this.deepMerge(v,b),B.log(`initIsolated map (merged): ${u.id} ${m} -> _isolated.${g}`)):(i[g]=b,B.log(`initIsolated map: ${u.id} ${m} -> _isolated.${g}`))}}})}evaluateDefaults(e,a){const r={};for(const[i,l]of Object.entries(e))if(typeof l=="string"&&l.startsWith("{{")&&l.endsWith("}}")){const u=l.slice(2,-2).trim();r[i]=this.evaluateExpression(u,a)}else r[i]=l;return r}evaluateExpression(e,a){try{const r=e.split("??").map(i=>i.trim());for(const i of r){if(/^['"].*['"]$/.test(i))return i.slice(1,-1);if(i==="true")return!0;if(i==="false")return!1;if(/^-?\d+(\.\d+)?$/.test(i))return Number(i);const l=this.getNestedValue(a,i);if(l!=null)return l}return}catch(r){B.warn(`Expression evaluation failed: ${e}`,r);return}}getNestedValue(e,a){const r=a.replace(/\?\./g,".");let i=e;const l=r.split(/\.(?![^\[]*\])/).flatMap(u=>{const d=u.match(/^([^\[]*)((?:\[\d+\])*)$/);if(d){const[,f,g]=d,m=[];f&&m.push(f);const b=g.match(/\[\d+\]/g);return b&&m.push(...b),m}return[u]}).filter(u=>u!=="");for(const u of l){if(i==null)return;if(u.startsWith("[")&&u.endsWith("]")){const d=parseInt(u.slice(1,-1),10);i=i[d]}else i=i[u]}return i}extractValueByPathOrExpression(e,a,r){const i=a.match(/^\{\{(.+)\}\}$/);if(i){const l=i[1].trim(),u=new wn,d={data:e,_global:this.globalState,_local:this.globalState._local||{}};try{const f=u.evaluateExpression(l,d);return B.log(`initLocal/initGlobal expression evaluated: ${a} -> `,f),f}catch(f){B.warn(`initLocal/initGlobal expression evaluation failed for ${r}:`,a,f);return}}return this.getNestedValue(e,a)}async loadLayoutScripts(e,a){const r=[];for(const i of e){if((i.if!==void 0||i.conditions!==void 0)&&!Ji({if:i.if,conditions:i.conditions},a,this.bindingEngine,`script:${i.id}`)){B.log(`Script skipped (condition not met): ${i.id}`);continue}if(document.getElementById(i.id)){B.log(`Script already loaded: ${i.id}`);continue}const u=new Promise((d,f)=>{const g=document.createElement("script");g.src=i.src,g.id=i.id,g.async=i.async??!0,g.onload=()=>{B.log(`Script loaded successfully: ${i.id}`),d()},g.onerror=()=>{B.warn(`Failed to load script: ${i.id} (${i.src})`),d()},document.head.appendChild(g)});r.push(u)}r.length>0&&(await Promise.all(r),B.log(`All scripts loaded: ${e.filter(i=>!document.getElementById(i.id)||r.length>0).map(i=>i.id).join(", ")}`))}evaluateScriptCondition(e,a){try{if(e.startsWith("{{")&&e.endsWith("}}")){const r=e.slice(2,-2).trim();return new Function("ctx",` with(ctx) { try { return Boolean(${r}); @@ -232,7 +232,7 @@ Error generating stack: `+c.message+` return false; } } - `)(n)}return!!e}catch(r){return B.warn(`Failed to evaluate script condition: ${e}`,r),!1}}showInitError(e){if(id()){B.warn("Init failed while document is unloading — skipping error screen",e);return}const n=fy(e);ff.renderFromError("app",n,"초기화 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}showTransitionOverlay(e,n){const r=typeof e=="boolean"?{enabled:e,style:"opaque",target:void 0,skeleton:void 0,spinner:void 0}:{enabled:e.enabled,style:e.style||"opaque",target:e.target,skeleton:e.skeleton,spinner:e.spinner,fallback_target:e.fallback_target};if(!r.enabled)return;if(this.hideTransitionOverlay(),r.style==="skeleton"&&r.skeleton?.component&&r.target&&n){this.renderSkeletonOverlay(r.target,r.skeleton,n,r.fallback_target);return}if(r.style==="spinner"&&r.target){this.renderSpinnerOverlay(r.target,r.spinner,r.fallback_target);return}const i=document.documentElement.classList.contains("dark");let l,u="";switch(r.style){case"blur":l=i?"rgba(17,24,39,0.3)":"rgba(255,255,255,0.3)",u="backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);";break;case"fade":l=i?"rgba(17,24,39,0.8)":"rgba(255,255,255,0.8)";break;case"skeleton":l=i?"rgb(17,24,39)":"rgb(249,250,251)";break;default:l=i?"rgb(17,24,39)":"rgb(249,250,251)";break}if(r.target){const d=`#${CSS.escape(r.target)}`,f=document.createElement("style");f.id="g7-transition-overlay",f.textContent=`${d}{position:relative;z-index:0;}${d}::after{content:'';position:absolute;inset:0;background:${l};${u}z-index:2147483647;pointer-events:none;}`,document.head.appendChild(f),this.transitionOverlayEl=f}else{const d=document.createElement("div");d.id="g7-transition-overlay",d.setAttribute("aria-hidden","true"),d.style.position="fixed",d.style.inset="0",d.style.zIndex="9999",d.style.pointerEvents="none",d.style.background=l;for(const f of u.split(";")){const g=f.indexOf(":");g!==-1&&d.style.setProperty(f.slice(0,g).trim(),f.slice(g+1).trim())}document.body.appendChild(d),this.transitionOverlayEl=d}}hideTransitionOverlay(){this.transitionOverlayEl&&(this.transitionOverlayEl.remove(),this.transitionOverlayEl=null),this.hideSkeletonOverlay()}renderSkeletonOverlay(e,n,r,i){const u=fa.getInstance().getComponent(n.component);if(!u){B.log(`Skeleton component "${n.component}" not found in registry, falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}let d=document.getElementById(e),f="target";if(!d&&i&&(d=document.getElementById(i),f="fallback"),d||(d=document.getElementById("app"),f="fullpage"),!d){B.log(`Skeleton overlay: no target found (target="#${e}", fallback="${i||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const m=document.documentElement.classList.contains("dark")?"rgb(17,24,39)":"rgb(249,250,251)",b=f==="fullpage"?"app":f==="fallback"?i:e,v=`#${CSS.escape(b)}`,S=document.createElement("style");S.id="g7-skeleton-overlay-style",S.textContent=`${v}{position:relative;z-index:0;}${v}::after{content:'';position:absolute;inset:0;background:${m};z-index:2147483646;pointer-events:none;}`,document.head.appendChild(S),this.transitionOverlayEl=S;const _=document.createElement("div");if(_.id="g7-skeleton-overlay",_.setAttribute("role","status"),_.setAttribute("aria-busy","true"),_.setAttribute("aria-label","Loading..."),f==="fullpage")_.style.cssText=["position:fixed","inset:0","z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";";else{const T=d.getBoundingClientRect(),D=window.scrollX||document.documentElement.scrollLeft,j=window.scrollY||document.documentElement.scrollTop;_.style.cssText=["position:absolute",`top:${T.top+j}px`,`left:${T.left+D}px`,`width:${T.width}px`,`height:${T.height}px`,"z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";"}document.body.appendChild(_),this.skeletonOverlayContainer=_;const x=r.components||[];let k;if(f==="fullpage")k=x;else{const T=f==="fallback"?i:e;k=this.findComponentChildrenById(x,T)}const O=td.createRoot(_);this.skeletonOverlayRoot=O,Ar.flushSync(()=>{O.render(Xe.createElement(u,{components:k,options:{animation:n.animation||"pulse",iteration_count:n.iteration_count||5}}))});const M=f==="fullpage"?"fullpage (#app)":f==="fallback"?`fallback (#${i})`:`target (#${e})`;B.log(`Skeleton overlay rendered [${M}] with "${n.component}" (${k.length} components)`)}findComponentChildrenById(e,n){const r=i=>{for(const l of i){if(l.id===n)return l.children||[];if(l.children&&Array.isArray(l.children)){const u=r(l.children);if(u!==null)return u}}return null};return r(e)||e}renderSpinnerOverlay(e,n,r){let i=document.getElementById(e),l="target";if(!i&&r&&(i=document.getElementById(r),l="fallback"),i||(i=document.getElementById("app"),l="fullpage"),!i){B.log(`Spinner overlay: no target found (target="#${e}", fallback="${r||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const u=l==="fullpage"?"app":l==="fallback"?r:e,d=`#${CSS.escape(u)}`,f=document.createElement("style");f.id="g7-skeleton-overlay-style",f.textContent=[`${d}{position:relative;}`,"@keyframes g7-spin{to{transform:rotate(360deg)}}"].join(""),document.head.appendChild(f),this.transitionOverlayEl=f;const g=n?.text||window.G7Core?.t?.("nav.loading")||"";this._spinnerState={target:e,fallbackTarget:r,spinnerConfig:n,resolvedText:g},this._mountSpinnerInTarget(i);const m=l==="fullpage"?"fullpage (#app)":l==="fallback"?`fallback (#${r})`:`target (#${e})`,b=n?.component||"default spinner";B.log(`Spinner overlay rendered [${m}] with "${b}"`)}_mountSpinnerInTarget(e){if(!this._spinnerState)return;const{spinnerConfig:n,resolvedText:r}=this._spinnerState;if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const i=document.createElement("div");if(i.id="g7-skeleton-overlay",i.setAttribute("role","status"),i.setAttribute("aria-busy","true"),e.appendChild(i),this.skeletonOverlayContainer=i,n?.component){const u=fa.getInstance().getComponent(n.component);if(u){const d=td.createRoot(i);this.skeletonOverlayRoot=d,Ar.flushSync(()=>{d.render(Xe.createElement(u,{options:{text:r}}))});return}B.log(`Spinner component "${n.component}" not found in registry, using default spinner`)}i.innerHTML='
'}reattachSpinnerOverlay(){if(!this._spinnerState)return;const{target:e,fallbackTarget:n}=this._spinnerState;let r=document.getElementById(e);!r&&n&&(r=document.getElementById(n)),r||(r=document.getElementById("app")),r&&(this._mountSpinnerInTarget(r),B.log("Spinner overlay reattached to new DOM target"))}hideSkeletonOverlay(){if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.parentNode&&this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const e=document.getElementById("g7-skeleton-overlay-style");e&&e.remove(),this._spinnerState=null}showRouteError(e){if(e instanceof va&&e.details?.status===401){const r=this.config.templateId||"",i=window.location.pathname,l=r.includes("admin")||i.startsWith("/admin")?"admin":"user",u=i+window.location.search,d=!!Ia().getToken()||e.details?.hadToken===!0,f=ga.getInstance().getLoginRedirectUrl(l,u,d?"session_expired":void 0);window.location.href=f;return}const n=fy(e);ff.renderFromError("app",n,"페이지 로딩 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}async handleServerError(){const e=window.G7Error;if(!e)return!1;B.warn("Server error detected:",e);try{return this.errorPageHandler&&(e.data&&(this.globalState.errorData=e.data),this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(e.code,"app"))?(B.log(`${e.code} error page rendered successfully`),!0):(B.log(`Falling back to ErrorDisplay for ${e.code}`),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0)}catch(n){return B.error(`Failed to render ${e.code} page:`,n),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0}}async handleRouteNotFound(e){B.warn("Route not found:",e);try{if(this.errorPageHandler&&(this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(404,"app"))){B.log("404 error page rendered successfully");return}B.log("Falling back to ErrorDisplay for 404"),this.showRouteError(new Error(`Page not found: ${e}`))}catch(n){B.error("Failed to render 404 page:",n),this.showRouteError(new Error(`Page not found: ${e}`))}}getRouter(){return this.router}getConfig(){return this.config}getLayoutLoader(){return this.layoutLoader}async reloadExtensionState(){B.log("reloadExtensionState: start");let e;try{const n=await fetch(Ma(`/api/templates/${this.config.templateId}/config`,"json",null,`_=${Date.now()}`));if(n.ok){const r=await n.json();r?.success&&r?.data?.cache_version!==void 0&&(e=r.data.cache_version)}}catch(n){B.warn("reloadExtensionState: failed to fetch config.json",n)}e!==void 0&&e!==this.extensionCacheVersion&&(B.log(`reloadExtensionState: cache version ${this.extensionCacheVersion} -> ${e}`),this.extensionCacheVersion=e,this.saveCacheVersionToStorage(e));try{this.router&&(await this.router.loadRoutes(this.extensionCacheVersion),B.log("reloadExtensionState: routes reloaded"))}catch(n){B.error("reloadExtensionState: routes reload failed",n)}try{this.layoutLoader&&(this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),this.layoutLoader.clear(),B.log("reloadExtensionState: layout cache cleared"))}catch(n){B.error("reloadExtensionState: layout cache clear failed",n)}try{const{TranslationEngine:n}=await Promise.resolve().then(()=>rp),r=n.getInstance();this.extensionCacheVersion>0&&r.setCacheVersion(this.extensionCacheVersion);const i=this.config.locale||"ko",l="en";await r.loadTranslations(this.config.templateId,i,"/api",!0),i!==l&&await r.loadTranslations(this.config.templateId,l,"/api",!0),B.log("reloadExtensionState: translations reloaded")}catch(n){B.error("reloadExtensionState: translations reload failed",n)}try{const n=await fetch(`/api/locales/active?_=${Date.now()}`);if(n.ok){const i=(await n.json())?.data?.locales;if(Array.isArray(i)&&i.length>0){const l=this.globalState.appConfig??{};this.setGlobalState({appConfig:{...l,supportedLocales:i}}),B.log("reloadExtensionState: supportedLocales refreshed",i)}}}catch(n){B.warn("reloadExtensionState: supportedLocales refresh failed",n)}B.log("reloadExtensionState: done")}getActionDispatcher(){return er().actionDispatcher}async changeLocale(e){try{if(B.log("Changing locale to:",e),this.config.locale===e){B.log("Locale is already",e);return}this.config.locale=e,this.saveLocaleToStorage(e),await this.saveLocaleToDatabase(e),this.layoutLoader&&(this.layoutLoader.clear(),B.log("Layout cache cleared")),this.errorPageHandler&&(this.errorPageHandler.updateLocale(e),B.log("ErrorPageHandler locale updated")),Sc(),await ko({templateId:this.config.templateId,templateType:this.config.templateType,locale:e,debug:this.config.debug,cacheVersion:this.extensionCacheVersion}),B.log("Template Engine re-initialized with new locale");const{getActionDispatcher:n}=await Promise.resolve().then(()=>Ua),r=n();r&&(r.setDefaultContext({navigate:i=>this.router?.navigate(i)}),r.setGlobalStateUpdater((i,l)=>this.setGlobalState(i,l)),B.log("Navigate function and setGlobalState re-injected to ActionDispatcher")),this.reinitializeModuleHandlers(),B.log("Module handlers re-initialized"),this.reinitializeTemplateHandlers(),B.log("Template handlers re-initialized"),this.reinitializePluginHandlers(),B.log("Plugin handlers re-initialized"),this.router&&this.router.navigateToCurrentPath(),B.log("Locale changed successfully to",e)}catch(n){throw B.error("Failed to change locale:",n),n}}loadLocaleFromStorage(){try{return localStorage.getItem(Ea.LOCALE_STORAGE_KEY)}catch(e){return B.warn("Failed to load locale from storage:",e),null}}saveLocaleToStorage(e){try{localStorage.setItem(Ea.LOCALE_STORAGE_KEY,e)}catch(n){B.warn("Failed to save locale to storage:",n)}}async saveLocaleToDatabase(e){const n=localStorage.getItem("auth_token");if(!n){B.log("No auth token, skipping DB locale save");return}let r,i;if(this.config.localeApi)r=this.config.localeApi.endpoint,i=this.config.localeApi.method;else{const l=this.config.templateType==="admin";r=l?"/api/admin/users/me/language":"/api/user/profile/update-language",i=l?"PATCH":"POST"}try{const l=this.getXsrfToken(),u=await fetch(r,{method:i,headers:{"Content-Type":"application/json",Accept:"application/json",...l&&{"X-XSRF-TOKEN":l},Authorization:`Bearer ${n}`},credentials:"include",body:JSON.stringify({language:e})});u.ok?B.log("Locale saved to DB:",e):B.warn("Failed to save locale to DB (UI will still change):",u.statusText)}catch(l){B.warn("Failed to call locale API (UI will still change):",l)}}getXsrfToken(){if(typeof document>"u")return null;const n=`; ${document.cookie}`.split("; XSRF-TOKEN=");return n.length===2?decodeURIComponent(n.pop()?.split(";").shift()||""):null}loadCacheVersionFromStorage(){try{const e=localStorage.getItem(Ea.CACHE_VERSION_STORAGE_KEY);return e?parseInt(e,10):null}catch(e){return B.warn("Failed to load cache version from storage:",e),null}}saveCacheVersionToStorage(e){try{localStorage.setItem(Ea.CACHE_VERSION_STORAGE_KEY,String(e))}catch(n){B.warn("Failed to save cache version to storage:",n)}}reinitializeModuleHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(r=>{if(r.startsWith(e)&&typeof n[r]?.initModule=="function")try{n[r].initModule(),B.log(`Module handler re-initialized: ${r}`)}catch(i){B.warn(`Failed to re-initialize module handlers for ${r}:`,i)}})}reinitializeTemplateHandlers(){if(typeof window>"u")return;const e=window.G7TemplateHandlers;if(!e){B.warn("Template handlers not found on window.G7TemplateHandlers");return}const n=this.getActionDispatcher();if(!n){B.warn("ActionDispatcher not available for template handler registration");return}Object.entries(e).forEach(([r,i])=>{n.registerHandler(r,i)}),B.log(`${Object.keys(e).length} template handler(s) re-registered:`,Object.keys(e))}reinitializePluginHandlers(){if(typeof window>"u")return;const e="__",n=window;Object.keys(n).forEach(r=>{if(r.startsWith(e)&&typeof n[r]?.initPlugin=="function")try{n[r].initPlugin(),B.log(`Plugin handler re-initialized: ${r}`)}catch(i){B.warn(`Failed to re-initialize plugin handlers for ${r}:`,i)}})}getLocale(){return this.config.locale}getErrorPageHandler(){return this.errorPageHandler}getGlobalState(){return{...this.globalState}}setGlobalState(e,n){const r={...this.globalState};typeof e=="function"?this.globalState=e(this.globalState):this.globalState={...this.globalState,...e},B.log("Global state updated:",this.globalState),window.G7Core?.devTools?.captureStateSnapshot?.({source:"setGlobalState",prev:r,next:this.globalState}),this.globalStateListeners.forEach(l=>{l(this.globalState)}),n?.render!==!1&&Promise.resolve().then(()=>Ua).then(({updateTemplateData:l,getState:u})=>{const d=u();d.reactRoot&&d.currentLayoutJson&&l({_global:{...this.globalState},_local:this.globalState._local||{}})})}onGlobalStateChange(e){this.globalStateListeners.add(e)}offGlobalStateChange(e){this.globalStateListeners.delete(e)}async refetchDataSource(e,n){let r=this.currentDataSources.find(i=>i.id===e);if(!r)for(const[,i]of this.modalDataSources){const l=i.find(u=>u.id===e);if(l){r=l;break}}if(!r){B.warn(`Data source not found: ${e}`);return}B.log(`Refetching data source: ${e}`,n?.sync?"(sync mode)":"",n?.globalStateOverride?"(with global override)":"",n?.localStateOverride?"(with local override)":""),Ha.setPending(!0);try{const i=new Sa;this.currentGlobalHeaders.length>0&&i.setGlobalHeaders(this.currentGlobalHeaders);const l=er(),u=l.currentDataContext?._global||{},d=n?.globalStateOverride?{...u,...n.globalStateOverride}:u,f=l.currentDataContext?._local||{},g=n?.localStateOverride?{...f,...n.localStateOverride}:f,b=(await i.fetchDataSourcesWithResults([r],this.currentRouteParams,this.currentQueryParams,d,g,{ignoreAutoFetch:!0}))[0];if(b.state==="success"&&b.data!==void 0){this.currentFetchedData[e]=b.data;const v={};this.processInitOptions([r],{[e]:b.data},v);const S=er();if(S.bindingEngine){const x=[e];r.initGlobal&&x.push("_global"),r.initLocal&&x.push("_local"),S.bindingEngine.invalidateCacheByKeys(x)}const _={[e]:b.data};return Object.keys(v).length>0&&(_._localInit=v),r.initGlobal&&(_._global={...this.globalState}),hs(_,n?.sync?{sync:!0}:void 0),B.log(`Data source refetched successfully: ${e}`),b.data}else if(b.state==="error"){B.error(`Failed to refetch data source: ${e}`,b.error);return}}catch(i){B.error(`Error refetching data source: ${e}`,i);return}finally{Ha.setPending(!1)}}registerModalDataSources(e,n){this.modalDataSources.set(e,n),B.log(`Modal data sources registered: ${e} (${n.length} sources)`)}unregisterModalDataSources(e){this.modalDataSources.delete(e),B.log(`Modal data sources unregistered: ${e}`)}getDataSource(e){return this.currentFetchedData[e]}setDataSource(e,n,r){if(!e){B.warn("setDataSource: dataSourceId is required");return}const{merge:i=!1,sync:l=!1}=r||{};i&&this.currentFetchedData[e]?this.currentFetchedData[e]={...this.currentFetchedData[e],...n}:this.currentFetchedData[e]=n,Promise.resolve().then(()=>Ua).then(({updateTemplateData:u})=>{u({[e]:this.currentFetchedData[e]},l?{sync:!0}:void 0)}),B.log(`setDataSource: Updated ${e}`,i?"(merged)":"(replaced)")}updateDataSourceItem(e,n,r,i,l){const{idField:u="id",merge:d=!0,skipRender:f=!1}=l||{},g=this.currentFetchedData[e];if(!g)return B.warn(`updateDataSourceItem: DataSource '${e}' not found`),!1;const m=this.parseItemPath(n);let b=g;for(const _ of m)if(b=b?.[_],b===void 0)return B.warn(`updateDataSourceItem: Path '${n}' not found in dataSource`),!1;if(!Array.isArray(b))return B.warn(`updateDataSourceItem: Target at '${n}' is not an array`),!1;const v=b.findIndex(_=>String(_[u])===String(r));if(v===-1)return B.warn(`updateDataSourceItem: Item with ${u}='${r}' not found`),!1;d?b[v]=this.deepMerge(b[v],i):b[v]={...b[v],...i};const S=window.G7Core?.devTools;return S?.isEnabled?.()&&S.trackDataSourceUpdate?.({dataSourceId:e,updateType:"partial",itemPath:n,itemId:r,updates:i,timestamp:Date.now()}),f||Promise.resolve().then(()=>Ua).then(({updateTemplateData:_})=>{_({[e]:this.currentFetchedData[e]},{sync:!1})}),B.log(`updateDataSourceItem: Updated ${e}.${n}[${u}=${r}]`),!0}parseItemPath(e){const n=[],r=/([^\.\[\]]+)|\[(\d+)\]/g;let i;for(;(i=r.exec(e))!==null;)i[1]!==void 0?n.push(i[1]):i[2]!==void 0&&n.push(parseInt(i[2],10));return n}deepMerge(e,n){if(n==null)return e;if(typeof n!="object"||Array.isArray(n))return n;const r={...e};for(const i of Object.keys(n))typeof n[i]=="object"&&n[i]!==null&&!Array.isArray(n[i])?r[i]=this.deepMerge(r[i]||{},n[i]):r[i]=n[i];return r}setValueAtPath(e,n,r,i="deep"){const l=n.split(".");let u=e;for(let g=0;g0?l.filterByCondition(this.currentRawDataSources,d):this.currentDataSources;this.currentDataSources=f;const g=f.filter(x=>x.auto_fetch!==!1&&x.type!=="websocket");if(g.length===0){B.log("No auto_fetch data sources to refetch");return}B.log(`Refetching ${g.length} auto_fetch data sources`),Ha.setPending(!0);const m=er().currentLayoutJson,b=m?.transition_overlay,v=Array.isArray(b?.wait_for)?b.wait_for:[],S=g.some(x=>(x.loading_strategy||"progressive")==="blocking"),_=v.length>0&&g.some(x=>v.includes(x.id)&&x.type!=="websocket"&&(x.loading_strategy||"progressive")!=="background");if((S||_)&&b&&typeof b=="object"){const x=n?.transitionOverlayTarget||b.target;b.enabled&&b.style==="skeleton"&&b.skeleton?.component&&x?this.renderSkeletonOverlay(x,b.skeleton,m,b.fallback_target):b.enabled&&b.style==="spinner"&&x&&this.renderSpinnerOverlay(x,b.spinner,b.fallback_target)}try{const x=new Sa;this.currentGlobalHeaders.length>0&&x.setGlobalHeaders(this.currentGlobalHeaders);const k=er(),O=k.currentDataContext?._global||{},M=await x.fetchDataSourcesWithResults(g,this.currentRouteParams,this.currentQueryParams,O,void 0,{ignoreAutoFetch:!1}),T={},D={},j=new Map(g.map(H=>[H.id,H]));for(const H of M){const P=j.get(H.id);if(!P){B.warn(`Refetch result id not found in autoFetchDataSources: ${H.id}`);continue}const X=H.id;H.state==="success"&&H.data!==void 0?(this.currentFetchedData[X]=H.data,T[X]=H.data,this.processInitOptions([P],{[X]:H.data},D),B.log(`Data source refetched: ${X}`)):H.state==="error"&&B.error(`Failed to refetch data source: ${X}`,H.error)}if(k.bindingEngine){const H=Object.keys(T);H.push("query"),k.bindingEngine.invalidateCacheByKeys(H)}if(Object.keys(D).length>0&&(T._localInit=D),Object.keys(T).length>0){if(T.query=gy(this.currentQueryParams),k.currentLayoutJson?.computed&&Object.keys(k.currentLayoutJson.computed).length>0){const H={...k.currentDataContext,...T,query:T.query},P=this.calculateComputed(k.currentLayoutJson.computed,H);Object.keys(P).length>0&&(T._computed=P,this.globalState._computed=P,B.log("Computed values recalculated in updateQueryParams:",Object.keys(P)))}hs(T,{sync:!0}),B.log("Template data updated with new query params")}}catch(x){B.error("Error in updateQueryParams:",x)}finally{Ha.setPending(!1),this.hideTransitionOverlay()}}calculateComputed(e,n){const r={},i=new Rn;for(const[l,u]of Object.entries(e))try{let d;if(this.isComputedSwitchDefinition(u))d=i.resolveSwitch(u,n,{skipCache:!0});else if(typeof u=="string")if(u.startsWith("{{")&&u.endsWith("}}")){const f=u.slice(2,-2).trim();d=this.evaluateComputedExpression(f,n)}else d=u;r[l]=d,B.log(`Computed ${l}:`,d)}catch(d){B.warn(`Failed to calculate computed value: ${l}`,d),r[l]=void 0}return r}isComputedSwitchDefinition(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"$switch"in e&&"$cases"in e}evaluateComputedExpression(e,n){try{return new Function("ctx",` + `)(a)}return!!e}catch(r){return B.warn(`Failed to evaluate script condition: ${e}`,r),!1}}showInitError(e){if(nd()){B.warn("Init failed while document is unloading — skipping error screen",e);return}const a=iy(e);uf.renderFromError("app",a,"초기화 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}showTransitionOverlay(e,a){const r=typeof e=="boolean"?{enabled:e,style:"opaque",target:void 0,skeleton:void 0,spinner:void 0}:{enabled:e.enabled,style:e.style||"opaque",target:e.target,skeleton:e.skeleton,spinner:e.spinner,fallback_target:e.fallback_target};if(!r.enabled)return;if(this.hideTransitionOverlay(),r.style==="skeleton"&&r.skeleton?.component&&r.target&&a){this.renderSkeletonOverlay(r.target,r.skeleton,a,r.fallback_target);return}if(r.style==="spinner"&&r.target){this.renderSpinnerOverlay(r.target,r.spinner,r.fallback_target);return}const i=document.documentElement.classList.contains("dark");let l,u="";switch(r.style){case"blur":l=i?"rgba(17,24,39,0.3)":"rgba(255,255,255,0.3)",u="backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);";break;case"fade":l=i?"rgba(17,24,39,0.8)":"rgba(255,255,255,0.8)";break;case"skeleton":l=i?"rgb(17,24,39)":"rgb(249,250,251)";break;default:l=i?"rgb(17,24,39)":"rgb(249,250,251)";break}if(r.target){const d=`#${CSS.escape(r.target)}`,f=document.createElement("style");f.id="g7-transition-overlay",f.textContent=`${d}{position:relative;z-index:0;}${d}::after{content:'';position:absolute;inset:0;background:${l};${u}z-index:2147483647;pointer-events:none;}`,document.head.appendChild(f),this.transitionOverlayEl=f}else{const d=document.createElement("div");d.id="g7-transition-overlay",d.setAttribute("aria-hidden","true"),d.style.position="fixed",d.style.inset="0",d.style.zIndex="9999",d.style.pointerEvents="none",d.style.background=l;for(const f of u.split(";")){const g=f.indexOf(":");g!==-1&&d.style.setProperty(f.slice(0,g).trim(),f.slice(g+1).trim())}document.body.appendChild(d),this.transitionOverlayEl=d}}hideTransitionOverlay(){this.transitionOverlayEl&&(this.transitionOverlayEl.remove(),this.transitionOverlayEl=null),this.hideSkeletonOverlay()}renderSkeletonOverlay(e,a,r,i){const u=da.getInstance().getComponent(a.component);if(!u){B.log(`Skeleton component "${a.component}" not found in registry, falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}let d=document.getElementById(e),f="target";if(!d&&i&&(d=document.getElementById(i),f="fallback"),d||(d=document.getElementById("app"),f="fullpage"),!d){B.log(`Skeleton overlay: no target found (target="#${e}", fallback="${i||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const m=document.documentElement.classList.contains("dark")?"rgb(17,24,39)":"rgb(249,250,251)",b=f==="fullpage"?"app":f==="fallback"?i:e,v=`#${CSS.escape(b)}`,S=document.createElement("style");S.id="g7-skeleton-overlay-style",S.textContent=`${v}{position:relative;z-index:0;}${v}::after{content:'';position:absolute;inset:0;background:${m};z-index:2147483646;pointer-events:none;}`,document.head.appendChild(S),this.transitionOverlayEl=S;const E=document.createElement("div");if(E.id="g7-skeleton-overlay",E.setAttribute("role","status"),E.setAttribute("aria-busy","true"),E.setAttribute("aria-label","Loading..."),f==="fullpage")E.style.cssText=["position:fixed","inset:0","z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";";else{const R=d.getBoundingClientRect(),D=window.scrollX||document.documentElement.scrollLeft,U=window.scrollY||document.documentElement.scrollTop;E.style.cssText=["position:absolute",`top:${R.top+U}px`,`left:${R.left+D}px`,`width:${R.width}px`,`height:${R.height}px`,"z-index:20","overflow:hidden","pointer-events:none",`background:${m}`].join(";")+";"}document.body.appendChild(E),this.skeletonOverlayContainer=E;const A=r.components||[];let T;if(f==="fullpage")T=A;else{const R=f==="fallback"?i:e;T=this.findComponentChildrenById(A,R)}const L=Ju.createRoot(E);this.skeletonOverlayRoot=L,Ar.flushSync(()=>{L.render(We.createElement(u,{components:T,options:{animation:a.animation||"pulse",iteration_count:a.iteration_count||5}}))});const M=f==="fullpage"?"fullpage (#app)":f==="fallback"?`fallback (#${i})`:`target (#${e})`;B.log(`Skeleton overlay rendered [${M}] with "${a.component}" (${T.length} components)`)}findComponentChildrenById(e,a){const r=i=>{for(const l of i){if(l.id===a)return l.children||[];if(l.children&&Array.isArray(l.children)){const u=r(l.children);if(u!==null)return u}}return null};return r(e)||e}renderSpinnerOverlay(e,a,r){let i=document.getElementById(e),l="target";if(!i&&r&&(i=document.getElementById(r),l="fallback"),i||(i=document.getElementById("app"),l="fullpage"),!i){B.log(`Spinner overlay: no target found (target="#${e}", fallback="${r||"none"}"), falling back to opaque overlay`),this.showTransitionOverlay({enabled:!0,style:"opaque",target:e});return}this.hideSkeletonOverlay();const u=l==="fullpage"?"app":l==="fallback"?r:e,d=`#${CSS.escape(u)}`,f=document.createElement("style");f.id="g7-skeleton-overlay-style",f.textContent=[`${d}{position:relative;}`,"@keyframes g7-spin{to{transform:rotate(360deg)}}"].join(""),document.head.appendChild(f),this.transitionOverlayEl=f;const g=a?.text||window.G7Core?.t?.("nav.loading")||"";this._spinnerState={target:e,fallbackTarget:r,spinnerConfig:a,resolvedText:g},this._mountSpinnerInTarget(i);const m=l==="fullpage"?"fullpage (#app)":l==="fallback"?`fallback (#${r})`:`target (#${e})`,b=a?.component||"default spinner";B.log(`Spinner overlay rendered [${m}] with "${b}"`)}_mountSpinnerInTarget(e){if(!this._spinnerState)return;const{spinnerConfig:a,resolvedText:r}=this._spinnerState;if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const i=document.createElement("div");if(i.id="g7-skeleton-overlay",i.setAttribute("role","status"),i.setAttribute("aria-busy","true"),e.appendChild(i),this.skeletonOverlayContainer=i,a?.component){const u=da.getInstance().getComponent(a.component);if(u){const d=Ju.createRoot(i);this.skeletonOverlayRoot=d,Ar.flushSync(()=>{d.render(We.createElement(u,{options:{text:r}}))});return}B.log(`Spinner component "${a.component}" not found in registry, using default spinner`)}i.innerHTML='
'}reattachSpinnerOverlay(){if(!this._spinnerState)return;const{target:e,fallbackTarget:a}=this._spinnerState;let r=document.getElementById(e);!r&&a&&(r=document.getElementById(a)),r||(r=document.getElementById("app")),r&&(this._mountSpinnerInTarget(r),B.log("Spinner overlay reattached to new DOM target"))}hideSkeletonOverlay(){if(this.skeletonOverlayRoot){try{this.skeletonOverlayRoot.unmount()}catch{}this.skeletonOverlayRoot=null}if(this.skeletonOverlayContainer){try{this.skeletonOverlayContainer.parentNode&&this.skeletonOverlayContainer.remove()}catch{}this.skeletonOverlayContainer=null}const e=document.getElementById("g7-skeleton-overlay-style");e&&e.remove(),this._spinnerState=null}showRouteError(e){if(e instanceof ba&&e.details?.status===401){const r=this.config.templateId||"",i=window.location.pathname,l=r.includes("admin")||i.startsWith("/admin")?"admin":"user",u=i+window.location.search,d=!!Na().getToken()||e.details?.hadToken===!0,f=ha.getInstance().getLoginRedirectUrl(l,u,d?"session_expired":void 0);window.location.href=f;return}const a=iy(e);uf.renderFromError("app",a,"페이지 로딩 실패",this.config.debug,{templateId:this.config.templateId,locale:this.config.locale},void 0,e)}async handleServerError(){const e=window.G7Error;if(!e)return!1;B.warn("Server error detected:",e);try{return this.errorPageHandler&&(e.data&&(this.globalState.errorData=e.data),this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(e.code,"app"))?(B.log(`${e.code} error page rendered successfully`),!0):(B.log(`Falling back to ErrorDisplay for ${e.code}`),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0)}catch(a){return B.error(`Failed to render ${e.code} page:`,a),this.showInitError(new Error(`Service unavailable (${e.code})`)),!0}}async handleRouteNotFound(e){B.warn("Route not found:",e);try{if(this.errorPageHandler&&(this.errorPageHandler.updateGlobalState(this.globalState),this.errorPageHandler.updateLocale(this.config.locale),await this.errorPageHandler.renderError(404,"app"))){B.log("404 error page rendered successfully");return}B.log("Falling back to ErrorDisplay for 404"),this.showRouteError(new Error(`Page not found: ${e}`))}catch(a){B.error("Failed to render 404 page:",a),this.showRouteError(new Error(`Page not found: ${e}`))}}getRouter(){return this.router}getConfig(){return this.config}getLayoutLoader(){return this.layoutLoader}async reloadExtensionState(){B.log("reloadExtensionState: start");let e;try{const a=await fetch(La(`/api/templates/${this.config.templateId}/config`,"json",null,`_=${Date.now()}`));if(a.ok){const r=await a.json();r?.success&&r?.data?.cache_version!==void 0&&(e=r.data.cache_version)}}catch(a){B.warn("reloadExtensionState: failed to fetch config.json",a)}e!==void 0&&e!==this.extensionCacheVersion&&(B.log(`reloadExtensionState: cache version ${this.extensionCacheVersion} -> ${e}`),this.extensionCacheVersion=e,this.saveCacheVersionToStorage(e));try{this.router&&(await this.router.loadRoutes(this.extensionCacheVersion),B.log("reloadExtensionState: routes reloaded"))}catch(a){B.error("reloadExtensionState: routes reload failed",a)}try{this.layoutLoader&&(this.extensionCacheVersion>0&&this.layoutLoader.setCacheVersion(this.extensionCacheVersion),this.layoutLoader.clear(),B.log("reloadExtensionState: layout cache cleared"))}catch(a){B.error("reloadExtensionState: layout cache clear failed",a)}try{const{TranslationEngine:a}=await Promise.resolve().then(()=>np),r=a.getInstance();this.extensionCacheVersion>0&&r.setCacheVersion(this.extensionCacheVersion);const i=this.config.locale||"ko",l="en";await r.loadTranslations(this.config.templateId,i,"/api",!0),i!==l&&await r.loadTranslations(this.config.templateId,l,"/api",!0),B.log("reloadExtensionState: translations reloaded")}catch(a){B.error("reloadExtensionState: translations reload failed",a)}try{const a=await fetch(`/api/locales/active?_=${Date.now()}`);if(a.ok){const i=(await a.json())?.data?.locales;if(Array.isArray(i)&&i.length>0){const l=this.globalState.appConfig??{};this.setGlobalState({appConfig:{...l,supportedLocales:i}}),B.log("reloadExtensionState: supportedLocales refreshed",i)}}}catch(a){B.warn("reloadExtensionState: supportedLocales refresh failed",a)}B.log("reloadExtensionState: done")}getActionDispatcher(){return er().actionDispatcher}async changeLocale(e){try{if(B.log("Changing locale to:",e),this.config.locale===e){B.log("Locale is already",e);return}this.config.locale=e,this.saveLocaleToStorage(e),await this.saveLocaleToDatabase(e),this.layoutLoader&&(this.layoutLoader.clear(),B.log("Layout cache cleared")),this.errorPageHandler&&(this.errorPageHandler.updateLocale(e),B.log("ErrorPageHandler locale updated")),yc(),await Ao({templateId:this.config.templateId,templateType:this.config.templateType,locale:e,debug:this.config.debug,cacheVersion:this.extensionCacheVersion}),B.log("Template Engine re-initialized with new locale");const{getActionDispatcher:a}=await Promise.resolve().then(()=>ja),r=a();r&&(r.setDefaultContext({navigate:i=>this.router?.navigate(i)}),r.setGlobalStateUpdater((i,l)=>this.setGlobalState(i,l)),B.log("Navigate function and setGlobalState re-injected to ActionDispatcher")),this.reinitializeModuleHandlers(),B.log("Module handlers re-initialized"),this.reinitializeTemplateHandlers(),B.log("Template handlers re-initialized"),this.reinitializePluginHandlers(),B.log("Plugin handlers re-initialized"),this.router&&this.router.navigateToCurrentPath(),B.log("Locale changed successfully to",e)}catch(a){throw B.error("Failed to change locale:",a),a}}loadLocaleFromStorage(){try{return localStorage.getItem(Ca.LOCALE_STORAGE_KEY)}catch(e){return B.warn("Failed to load locale from storage:",e),null}}saveLocaleToStorage(e){try{localStorage.setItem(Ca.LOCALE_STORAGE_KEY,e)}catch(a){B.warn("Failed to save locale to storage:",a)}}async saveLocaleToDatabase(e){const a=localStorage.getItem("auth_token");if(!a){B.log("No auth token, skipping DB locale save");return}let r,i;if(this.config.localeApi)r=this.config.localeApi.endpoint,i=this.config.localeApi.method;else{const l=this.config.templateType==="admin";r=l?"/api/admin/users/me/language":"/api/user/profile/update-language",i=l?"PATCH":"POST"}try{const l=this.getXsrfToken(),u=await fetch(r,{method:i,headers:{"Content-Type":"application/json",Accept:"application/json",...l&&{"X-XSRF-TOKEN":l},Authorization:`Bearer ${a}`},credentials:"include",body:JSON.stringify({language:e})});u.ok?B.log("Locale saved to DB:",e):B.warn("Failed to save locale to DB (UI will still change):",u.statusText)}catch(l){B.warn("Failed to call locale API (UI will still change):",l)}}getXsrfToken(){if(typeof document>"u")return null;const a=`; ${document.cookie}`.split("; XSRF-TOKEN=");return a.length===2?decodeURIComponent(a.pop()?.split(";").shift()||""):null}loadCacheVersionFromStorage(){try{const e=localStorage.getItem(Ca.CACHE_VERSION_STORAGE_KEY);return e?parseInt(e,10):null}catch(e){return B.warn("Failed to load cache version from storage:",e),null}}saveCacheVersionToStorage(e){try{localStorage.setItem(Ca.CACHE_VERSION_STORAGE_KEY,String(e))}catch(a){B.warn("Failed to save cache version to storage:",a)}}reinitializeModuleHandlers(){if(typeof window>"u")return;const e="__",a=window;Object.keys(a).forEach(r=>{if(r.startsWith(e)&&typeof a[r]?.initModule=="function")try{a[r].initModule(),B.log(`Module handler re-initialized: ${r}`)}catch(i){B.warn(`Failed to re-initialize module handlers for ${r}:`,i)}})}reinitializeTemplateHandlers(){if(typeof window>"u")return;const e=window.G7TemplateHandlers;if(!e){B.warn("Template handlers not found on window.G7TemplateHandlers");return}const a=this.getActionDispatcher();if(!a){B.warn("ActionDispatcher not available for template handler registration");return}Object.entries(e).forEach(([r,i])=>{a.registerHandler(r,i)}),B.log(`${Object.keys(e).length} template handler(s) re-registered:`,Object.keys(e))}reinitializePluginHandlers(){if(typeof window>"u")return;const e="__",a=window;Object.keys(a).forEach(r=>{if(r.startsWith(e)&&typeof a[r]?.initPlugin=="function")try{a[r].initPlugin(),B.log(`Plugin handler re-initialized: ${r}`)}catch(i){B.warn(`Failed to re-initialize plugin handlers for ${r}:`,i)}})}getLocale(){return this.config.locale}getErrorPageHandler(){return this.errorPageHandler}getGlobalState(){return{...this.globalState}}setGlobalState(e,a){const r={...this.globalState};typeof e=="function"?this.globalState=e(this.globalState):this.globalState={...this.globalState,...e},B.log("Global state updated:",this.globalState),window.G7Core?.devTools?.captureStateSnapshot?.({source:"setGlobalState",prev:r,next:this.globalState}),this.globalStateListeners.forEach(l=>{l(this.globalState)}),a?.render!==!1&&Promise.resolve().then(()=>ja).then(({updateTemplateData:l,getState:u})=>{const d=u();d.reactRoot&&d.currentLayoutJson&&l({_global:{...this.globalState},_local:this.globalState._local||{}})})}onGlobalStateChange(e){this.globalStateListeners.add(e)}offGlobalStateChange(e){this.globalStateListeners.delete(e)}async refetchDataSource(e,a){let r=this.currentDataSources.find(i=>i.id===e);if(!r)for(const[,i]of this.modalDataSources){const l=i.find(u=>u.id===e);if(l){r=l;break}}if(!r){B.warn(`Data source not found: ${e}`);return}B.log(`Refetching data source: ${e}`,a?.sync?"(sync mode)":"",a?.globalStateOverride?"(with global override)":"",a?.localStateOverride?"(with local override)":""),Ia.setPending(!0);try{const i=new va;this.currentGlobalHeaders.length>0&&i.setGlobalHeaders(this.currentGlobalHeaders);const l=er(),u=l.currentDataContext?._global||{},d=a?.globalStateOverride?{...u,...a.globalStateOverride}:u,f=l.currentDataContext?._local||{},g=a?.localStateOverride?{...f,...a.localStateOverride}:f,b=(await i.fetchDataSourcesWithResults([r],this.currentRouteParams,this.currentQueryParams,d,g,{ignoreAutoFetch:!0}))[0];if(b.state==="success"&&b.data!==void 0){this.currentFetchedData[e]=b.data;const v={};this.processInitOptions([r],{[e]:b.data},v);const S=er();if(S.bindingEngine){const A=[e];r.initGlobal&&A.push("_global"),r.initLocal&&A.push("_local"),S.bindingEngine.invalidateCacheByKeys(A)}const E={[e]:b.data};return Object.keys(v).length>0&&(E._localInit=v),r.initGlobal&&(E._global={...this.globalState}),us(E,a?.sync?{sync:!0}:void 0),B.log(`Data source refetched successfully: ${e}`),b.data}else if(b.state==="error"){B.error(`Failed to refetch data source: ${e}`,b.error);return}}catch(i){B.error(`Error refetching data source: ${e}`,i);return}finally{Ia.setPending(!1)}}registerModalDataSources(e,a){this.modalDataSources.set(e,a),B.log(`Modal data sources registered: ${e} (${a.length} sources)`)}unregisterModalDataSources(e){this.modalDataSources.delete(e),B.log(`Modal data sources unregistered: ${e}`)}getDataSource(e){return this.currentFetchedData[e]}setDataSource(e,a,r){if(!e){B.warn("setDataSource: dataSourceId is required");return}const{merge:i=!1,sync:l=!1}=r||{};i&&this.currentFetchedData[e]?this.currentFetchedData[e]={...this.currentFetchedData[e],...a}:this.currentFetchedData[e]=a,Promise.resolve().then(()=>ja).then(({updateTemplateData:u})=>{u({[e]:this.currentFetchedData[e]},l?{sync:!0}:void 0)}),B.log(`setDataSource: Updated ${e}`,i?"(merged)":"(replaced)")}updateDataSourceItem(e,a,r,i,l){const{idField:u="id",merge:d=!0,skipRender:f=!1}=l||{},g=this.currentFetchedData[e];if(!g)return B.warn(`updateDataSourceItem: DataSource '${e}' not found`),!1;const m=this.parseItemPath(a);let b=g;for(const E of m)if(b=b?.[E],b===void 0)return B.warn(`updateDataSourceItem: Path '${a}' not found in dataSource`),!1;if(!Array.isArray(b))return B.warn(`updateDataSourceItem: Target at '${a}' is not an array`),!1;const v=b.findIndex(E=>String(E[u])===String(r));if(v===-1)return B.warn(`updateDataSourceItem: Item with ${u}='${r}' not found`),!1;d?b[v]=this.deepMerge(b[v],i):b[v]={...b[v],...i};const S=window.G7Core?.devTools;return S?.isEnabled?.()&&S.trackDataSourceUpdate?.({dataSourceId:e,updateType:"partial",itemPath:a,itemId:r,updates:i,timestamp:Date.now()}),f||Promise.resolve().then(()=>ja).then(({updateTemplateData:E})=>{E({[e]:this.currentFetchedData[e]},{sync:!1})}),B.log(`updateDataSourceItem: Updated ${e}.${a}[${u}=${r}]`),!0}parseItemPath(e){const a=[],r=/([^\.\[\]]+)|\[(\d+)\]/g;let i;for(;(i=r.exec(e))!==null;)i[1]!==void 0?a.push(i[1]):i[2]!==void 0&&a.push(parseInt(i[2],10));return a}deepMerge(e,a){if(a==null)return e;if(typeof a!="object"||Array.isArray(a))return a;const r={...e};for(const i of Object.keys(a))typeof a[i]=="object"&&a[i]!==null&&!Array.isArray(a[i])?r[i]=this.deepMerge(r[i]||{},a[i]):r[i]=a[i];return r}setValueAtPath(e,a,r,i="deep"){const l=a.split(".");let u=e;for(let g=0;g0?l.filterByCondition(this.currentRawDataSources,d):this.currentDataSources;this.currentDataSources=f;const g=f.filter(A=>A.auto_fetch!==!1&&A.type!=="websocket");if(g.length===0){B.log("No auto_fetch data sources to refetch");return}B.log(`Refetching ${g.length} auto_fetch data sources`),Ia.setPending(!0);const m=er().currentLayoutJson,b=m?.transition_overlay,v=Array.isArray(b?.wait_for)?b.wait_for:[],S=g.some(A=>(A.loading_strategy||"progressive")==="blocking"),E=v.length>0&&g.some(A=>v.includes(A.id)&&A.type!=="websocket"&&(A.loading_strategy||"progressive")!=="background");if((S||E)&&b&&typeof b=="object"){const A=a?.transitionOverlayTarget||b.target;b.enabled&&b.style==="skeleton"&&b.skeleton?.component&&A?this.renderSkeletonOverlay(A,b.skeleton,m,b.fallback_target):b.enabled&&b.style==="spinner"&&A&&this.renderSpinnerOverlay(A,b.spinner,b.fallback_target)}try{const A=new va;this.currentGlobalHeaders.length>0&&A.setGlobalHeaders(this.currentGlobalHeaders);const T=er(),L=T.currentDataContext?._global||{},M=await A.fetchDataSourcesWithResults(g,this.currentRouteParams,this.currentQueryParams,L,void 0,{ignoreAutoFetch:!1}),R={},D={},U=new Map(g.map(j=>[j.id,j]));for(const j of M){const P=U.get(j.id);if(!P){B.warn(`Refetch result id not found in autoFetchDataSources: ${j.id}`);continue}const X=j.id;j.state==="success"&&j.data!==void 0?(this.currentFetchedData[X]=j.data,R[X]=j.data,this.processInitOptions([P],{[X]:j.data},D),B.log(`Data source refetched: ${X}`)):j.state==="error"&&B.error(`Failed to refetch data source: ${X}`,j.error)}if(T.bindingEngine){const j=Object.keys(R);j.push("query"),T.bindingEngine.invalidateCacheByKeys(j)}if(Object.keys(D).length>0&&(R._localInit=D),Object.keys(R).length>0){if(R.query=oy(this.currentQueryParams),T.currentLayoutJson?.computed&&Object.keys(T.currentLayoutJson.computed).length>0){const j={...T.currentDataContext,...R,query:R.query},P=this.calculateComputed(T.currentLayoutJson.computed,j);Object.keys(P).length>0&&(R._computed=P,this.globalState._computed=P,B.log("Computed values recalculated in updateQueryParams:",Object.keys(P)))}us(R,{sync:!0}),B.log("Template data updated with new query params")}}catch(A){B.error("Error in updateQueryParams:",A)}finally{Ia.setPending(!1),this.hideTransitionOverlay()}}calculateComputed(e,a){const r={},i=new wn;for(const[l,u]of Object.entries(e))try{let d;if(this.isComputedSwitchDefinition(u))d=i.resolveSwitch(u,a,{skipCache:!0});else if(typeof u=="string")if(u.startsWith("{{")&&u.endsWith("}}")){const f=u.slice(2,-2).trim();d=this.evaluateComputedExpression(f,a)}else d=u;r[l]=d,B.log(`Computed ${l}:`,d)}catch(d){B.warn(`Failed to calculate computed value: ${l}`,d),r[l]=void 0}return r}isComputedSwitchDefinition(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)&&"$switch"in e&&"$cases"in e}evaluateComputedExpression(e,a){try{return new Function("ctx",` with(ctx) { try { return ${e}; @@ -240,5 +240,5 @@ Error generating stack: `+c.message+` return undefined; } } - `)(n)}catch(r){B.warn(`Expression evaluation failed: ${e}`,r);return}}async executeInitActions(e,n={}){const{getActionDispatcher:r}=await Promise.resolve().then(()=>Ua),i=r();if(!i){B.warn("ActionDispatcher not available for init_actions");return}B.log("Executing init_actions:",e.map(d=>d.handler)),B.log("Init actions dataContext:",n),B.log("Init actions dataContext._global:",n._global);const l=e.map(d=>d.handler).filter(d=>d.includes("."));l.length>0&&await this.waitForHandlers(i,l);let u={...n};for(const d of e)try{const f={type:"click",handler:d.handler,target:d.target,params:d.params,resultTo:d.resultTo,onSuccess:d.onSuccess,onError:d.onError,if:d.if,conditions:d.conditions,auth_mode:d.auth_mode,auth_required:d.auth_required},g=i.createHandler(f,u),m=new Event("init");await g(m),B.log(`Init action executed: ${d.handler}`);const b=this.globalState;u={...u,_global:{...b},_local:b._local||u._local||{}},B.log(`Data context refreshed after ${d.handler}:`,{cartKey:u._global?.cartKey})}catch(f){B.error(`Failed to execute init action: ${d.handler}`,f)}}async waitForHandlers(e,n,r=5e3){const i=Date.now(),l=50,u=()=>n.every(f=>e.customHandlers?.has(f));if(u()){B.log("All module handlers already registered");return}const d=gf().getFailedJsAssets();if(d.length>0){const f=d.some(b=>b==="module"||b==="plugin"),g=n.filter(b=>!e.customHandlers?.has(b)),m=g.length>0&&g.every(b=>d.some(v=>b.startsWith(`${v}.`)));if(f||m){B.warn("Extension asset load failed — not waiting for handlers that will never register:",{pending:g,failedAssets:d});return}}return B.log("Waiting for module handlers:",n),new Promise(f=>{const g=()=>{if(u()){B.log("All module handlers now registered"),f();return}if(Date.now()-i>=r){const m=n.filter(b=>!e.customHandlers?.has(b));B.warn("Timeout waiting for handlers:",m),f();return}setTimeout(g,l)};g()})}};$(Ea,"LOCALE_STORAGE_KEY","g7_locale"),$(Ea,"CACHE_VERSION_STORAGE_KEY","g7_cache_version");let _o=Ea;function mf(o){const e=new _o(o);return o.websocket&&(mc.configure(o.websocket),B.log("WebSocket 설정 완료")),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{e.init(),window.__templateApp=e}):(e.init(),window.__templateApp=e),e}typeof window<"u"&&(window.G7Core=window.G7Core||{},window.G7Core.initTemplateApp=mf,window.G7Core.dataSource={refetch:async(o,e)=>{const n=window.__templateApp;if(!n){B.warn("TemplateApp not initialized (G7Core.dataSource.refetch)");return}return n.refetchDataSource(o,e)},get:o=>{const e=window.__templateApp;if(!e){B.warn("TemplateApp not initialized (G7Core.dataSource.get)");return}return e.getDataSource(o)}});function cA(o={}){const{checked:e,value:n,name:r,type:i="checkbox"}=o,u=n!==void 0?n:i==="checkbox"||i==="radio"?e??!1:String(e??""),d={checked:e??!1,value:u,name:r??"",type:i};return{target:d,currentTarget:d,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("change"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"change",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{}}}function uA(o={}){const{button:e=0,clientX:n=0,clientY:r=0}=o;return{button:e,clientX:n,clientY:r,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new MouseEvent("click"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"click",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div")}}function dA(){return{preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("submit"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"submit",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("form"),currentTarget:document.createElement("form")}}function fA(o,e="keydown"){return{key:o,code:o.length===1?`Key${o.toUpperCase()}`:o,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new KeyboardEvent(e,{key:o}),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:e,isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div"),altKey:!1,ctrlKey:!1,metaKey:!1,shiftKey:!1,repeat:!1}}const hA={justify:/^justify-(start|end|center|between|around|evenly)$/,items:/^items-(start|end|center|baseline|stretch)$/,content:/^content-(start|end|center|between|around|evenly|stretch)$/,self:/^self-(auto|start|end|center|stretch|baseline)$/,flex:/^flex-(row|row-reverse|col|col-reverse|wrap|wrap-reverse|nowrap|1|auto|initial|none)$/,grow:/^(grow|grow-0)$/,shrink:/^(shrink|shrink-0)$/,basis:/^basis-/,order:/^(order-|-)order-/,gap:/^gap(-x|-y)?-/,gridCols:/^grid-cols-/,gridRows:/^grid-rows-/,colSpan:/^col-(span-|start-|end-)/,rowSpan:/^row-(span-|start-|end-)/,display:/^(block|inline-block|inline|flex|inline-flex|table|inline-table|table-caption|table-cell|table-column|table-column-group|table-footer-group|table-header-group|table-row-group|table-row|flow-root|grid|inline-grid|contents|list-item|hidden)$/,position:/^(static|fixed|absolute|relative|sticky)$/,inset:/^(inset|top|right|bottom|left)-/,zIndex:/^z-/,width:/^w-/,minWidth:/^min-w-/,maxWidth:/^max-w-/,height:/^h-/,minHeight:/^min-h-/,maxHeight:/^max-h-/,padding:/^p[xytblr]?-/,margin:/^-?m[xytblr]?-/,space:/^space-(x|y)-/,fontSize:/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,fontWeight:/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,fontStyle:/^(italic|not-italic)$/,fontFamily:/^font-(sans|serif|mono)/,textAlign:/^text-(left|center|right|justify|start|end)$/,textColor:/^text-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,textDecoration:/^(underline|overline|line-through|no-underline)$/,textTransform:/^(uppercase|lowercase|capitalize|normal-case)$/,lineHeight:/^leading-/,letterSpacing:/^tracking-/,textOverflow:/^(truncate|text-ellipsis|text-clip)$/,whitespace:/^whitespace-/,wordBreak:/^break-/,bgColor:/^bg-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,bgGradient:/^bg-gradient-/,bgSize:/^bg-(auto|cover|contain)$/,bgPosition:/^bg-(bottom|center|left|left-bottom|left-top|right|right-bottom|right-top|top)$/,bgRepeat:/^bg-(repeat|no-repeat|repeat-x|repeat-y|repeat-round|repeat-space)$/,bgAttachment:/^bg-(fixed|local|scroll)$/,bgClip:/^bg-clip-/,bgOrigin:/^bg-origin-/,borderWidth:/^border(-[xytblr])?(-0|-2|-4|-8)?$/,borderColor:/^border-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,borderStyle:/^border-(solid|dashed|dotted|double|hidden|none)$/,borderRadius:/^rounded(-[tblrse]{1,2})?(-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?$/,ringWidth:/^ring(-0|-1|-2|-4|-8|-inset)?$/,ringColor:/^ring-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,ringOffset:/^ring-offset-/,shadow:/^shadow(-sm|-md|-lg|-xl|-2xl|-inner|-none)?$/,opacity:/^opacity-/,mixBlend:/^mix-blend-/,bgBlend:/^bg-blend-/,blur:/^blur(-none|-sm|-md|-lg|-xl|-2xl|-3xl)?$/,brightness:/^brightness-/,contrast:/^contrast-/,grayscale:/^grayscale(-0)?$/,hueRotate:/^-?hue-rotate-/,invert:/^invert(-0)?$/,saturate:/^saturate-/,sepia:/^sepia(-0)?$/,backdropBlur:/^backdrop-blur-/,backdropBrightness:/^backdrop-brightness-/,backdropContrast:/^backdrop-contrast-/,backdropGrayscale:/^backdrop-grayscale-/,backdropHueRotate:/^backdrop-hue-rotate-/,backdropInvert:/^backdrop-invert-/,backdropOpacity:/^backdrop-opacity-/,backdropSaturate:/^backdrop-saturate-/,backdropSepia:/^backdrop-sepia-/,scale:/^scale(-x|-y)?-/,rotate:/^-?rotate-/,translate:/^-?translate-[xy]-/,skew:/^-?skew-[xy]-/,transformOrigin:/^origin-/,transition:/^transition(-none|-all|-colors|-opacity|-shadow|-transform)?$/,duration:/^duration-/,ease:/^ease-(linear|in|out|in-out)$/,delay:/^delay-/,animate:/^animate-/,cursor:/^cursor-/,userSelect:/^select-/,pointerEvents:/^pointer-events-/,resize:/^resize(-none|-x|-y)?$/,scrollBehavior:/^scroll-(auto|smooth)$/,touchAction:/^touch-/,overflow:/^overflow(-x|-y)?-(auto|hidden|clip|visible|scroll)$/,overscroll:/^overscroll(-x|-y)?-(auto|contain|none)$/,visibility:/^(visible|invisible|collapse)$/,aspectRatio:/^aspect-/,columns:/^columns-/,breakAfter:/^break-after-/,breakBefore:/^break-before-/,breakInside:/^break-inside-/,boxDecorationBreak:/^box-decoration-/,boxSizing:/^box-(border|content)$/,float:/^float-(right|left|none)$/,clear:/^clear-(left|right|both|none)$/,isolation:/^(isolate|isolation-auto)$/,objectFit:/^object-(contain|cover|fill|none|scale-down)$/,objectPosition:/^object-/};function py(o){const e=o.replace(/^(dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+/,"");for(const[n,r]of Object.entries(hA))if(r.test(e))return n;return null}function my(o){const e=o.match(/^((dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+)/);return e?e[1]:""}function gA(o,e){if(!e||e.trim()==="")return o;if(!o||o.trim()==="")return e;const n=o.split(/\s+/).filter(Boolean),r=e.split(/\s+/).filter(Boolean),i=new Map;for(const u of r){const d=py(u);if(d){const f=my(u),g=`${d}:${f}`;i.set(g,u)}}return[...n.filter(u=>{const d=py(u);if(!d)return!0;const f=my(u),g=`${d}:${f}`;return!i.has(g)}),...r].join(" ")}function pA(o){return Object.entries(o).filter(([,e])=>e).map(([e])=>e).join(" ")}function mA(...o){return o.filter(e=>typeof e=="string"&&e.length>0).join(" ")}const Se=dt("G7CoreGlobals");function yA(){window.React=Xe,window.ReactDOM={...nd,createPortal:Ar.createPortal,unstable_batchedUpdates:o=>o()},window.ReactJSXRuntime=hC,Se.log("전역 객체 window.React, window.ReactDOM, window.ReactJSXRuntime에 노출됨")}function bA(o){const e=new Map,n=Tr.getInstance();o.componentEvent={on:(r,i)=>(e.has(r)||e.set(r,new Set),e.get(r).add(i),n.trackEventSubscribe(r),()=>{e.get(r)?.delete(i),n.trackEventUnsubscribe(r)}),emit:async(r,i)=>{const l=e.get(r),u=l?.size??0;if(!l||l.size===0)return n.trackEventEmit(r,i,0),[];let d,f=[];try{f=await Promise.all(Array.from(l).map(async g=>{try{return await g(i)}catch(m){throw Se.error(`componentEvent: Error in listener for "${r}":`,m),m}}))}catch(g){d=g instanceof Error?g:new Error(String(g))}if(n.trackEventEmit(r,i,u,f,d),d)throw d;return f},off:r=>{e.delete(r),n.trackEventOff(r)},clear:()=>{e.clear(),n.trackEventClear()}},Se.log("전역 객체 window.G7Core.componentEvent에 노출됨")}function vA(o){o.__runtime||(o.__runtime={DynamicRenderer:ja,ComponentRegistry:fa,TranslationEngine:Er,DataSourceManager:Sa,dataSourceManager:Q_,DataBindingEngine:Rn,dataBindingEngine:aw,ActionDispatcher:fo,TranslationReactContext:of,TranslationProvider:hc,useTranslation:ty,ResponsiveContext:Hd,ResponsiveProvider:ec,useResponsive:zd,responsiveManager:ui,BREAKPOINT_PRESETS:$l,AuthManager:ga,createLogger:dt,G7DevToolsCore:Tr},Se.log("전역 객체 window.G7Core.__runtime(코어 런타임 표면)에 노출됨"))}function SA(o){if(o.layoutEditor&&o.layoutEditor.__isStub!==!0||o.layoutEditor&&o.layoutEditor.__isStub===!0)return;const e=[],n=[];o.layoutEditor={__isStub:!0,__queue:e,__readyCallbacks:n,registerWidget:(r,i)=>e.push(["widget",r,i]),registerNodeEditor:(r,i)=>e.push(["nodeEditor",r,i]),registerCanvasOverlay:(r,i)=>e.push(["canvasOverlay",r,i]),onReady:r=>{typeof r=="function"&&n.push(r)}},Se.log("전역 객체 window.G7Core.layoutEditor 예약 접수함(stub) 노출됨")}function wA(o,e){o.useTranslation=ty,o.t=(n,r)=>{const i=e.getState();if(!i.translationEngine||!i.translationContext)return n;if(r){const l="|"+Object.entries(r).map(([u,d])=>`${u}=${d}`).join("|");return i.translationEngine.translate(n,i.translationContext,l)}return i.translationEngine.translate(n,i.translationContext)},Se.log("전역 객체 window.G7Core.useTranslation에 노출됨")}function CA(o,e){o.createChangeEvent=cA,o.createClickEvent=uA,o.createSubmitEvent=dA,o.createKeyboardEvent=fA,o.uuid=()=>typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{const r=Math.random()*16|0;return(n==="x"?r:r&3|8).toString(16)}),o.createLogger=dt,Se.log("전역 객체 window.G7Core.createLogger에 노출됨"),o.renderItemChildren=(n,r,i,l,u)=>{const d=e.getState(),m={_global:window.__templateApp?.getGlobalState?.()||{},_local:r._local||{},_computed:r._computed||{},...r},b=new Set,v=(_,x)=>{const k=m._global?._remountKeys;return _&&k?.[_]&&!b.has(_)?(b.add(_),`${_}-remount-${k[_]}`):_||x},S={translationContext:d.translationContext,translationEngine:d.translationEngine,bindingEngine:d.bindingEngine,actionDispatcher:d.actionDispatcher,getRemountKey:v,...u};return no(n,m,i,l,S)},o.getComponentMap=()=>fa.getInstance().getComponentMap(),o.renderComponentLayout=(n,r,i)=>{if(!n||!Array.isArray(n)||n.length===0)return null;const l=fa.getInstance().getComponentMap(),u=e.getState(),f=window.__templateApp?.getGlobalState?.()||{},g={_global:f._global||{},_local:f._local||{},_computed:f._computed||{},...r},m=new Set,b=(v,S)=>{const _=g._global?._remountKeys;return v&&_?.[v]&&!m.has(v)?(m.add(v),`${v}-remount-${_[v]}`):v||S};return no(n,g,l,i,{translationContext:u.translationContext,translationEngine:u.translationEngine,bindingEngine:u.bindingEngine,actionDispatcher:u.actionDispatcher,getRemountKey:b})},o.renderExpandContent=n=>{const{children:r,row:i,expandContext:l,componentContext:u,keyPrefix:d}=n;if(!r||!Array.isArray(r)||r.length===0)return null;const f=n.componentMap||fa.getInstance().getComponentMap(),g=e.getState(),m=o.state?.get?.()||{},b=u?.stateRef?.current??u?.state??{},v={...m._local||{},...b},S=u?.computedRef?.current??m._computed??{},_=g.bindingEngine??o.getDataBindingEngine?.(),x={...m,_local:v,_computed:S,row:i,item:i,$item:i};let k={};if(l&&typeof l=="object"&&_)try{for(const[D,j]of Object.entries(l))if(typeof j=="string"){const H=j.match(/^\{\{([^}]+)\}\}$/);if(H){const P=H[1].trim();try{k[D]=_.evaluateExpression(P,x,{skipCache:!0})}catch{k[D]=void 0}}else k[D]=_.resolveBindings(j,x,{skipCache:!0})}else k[D]=j}catch(D){Se.warn("renderExpandContent: expandContext 평가 오류:",D)}const O={row:i,item:i,$item:i,_local:v,_global:m._global||{},_computed:m._computed||{},...k},M=new Set,T=(D,j)=>{const H=O._global?._remountKeys;return D&&H?.[D]&&!M.has(D)?(M.add(D),`${D}-remount-${H[D]}`):D||j};return no(r,O,f,d,{translationContext:g.translationContext,translationEngine:g.translationEngine??void 0,bindingEngine:g.bindingEngine??void 0,actionDispatcher:g.actionDispatcher,componentContext:u,getRemountKey:T})},o.$get=function(r,i,l=void 0){if(r==null)return l;const u=Array.isArray(i)?i:[i];if(u.length===0)return r;let d=r;for(const f of u){if(d==null||f==null)return l;d=d[f]}return d??l},Se.log("전역 객체 window.G7Core.renderItemChildren에 노출됨"),Se.log("전역 객체 window.G7Core.renderComponentLayout에 노출됨"),Se.log("전역 객체 window.G7Core.renderExpandContent에 노출됨"),Se.log("전역 객체 window.G7Core.$get에 노출됨")}function EA(o){o.dispatch=async(e,n)=>{const r=window.__templateApp;if(!r)return Se.warn("G7Core.dispatch: TemplateApp이 초기화되지 않았습니다."),{success:!1,error:new Error("TemplateApp이 초기화되지 않았습니다.")};const i=r.getActionDispatcher?.();if(!i)return Se.warn("G7Core.dispatch: ActionDispatcher를 찾을 수 없습니다."),{success:!1,error:new Error("ActionDispatcher를 찾을 수 없습니다.")};const l=r.getRouter?.(),u=r.getGlobalState?.(),d=r.setGlobalState?.bind(r),g=n?.componentContext||window.__g7ActionContext,m=window.__g7PendingLocalState;let b=g?.state??u;m&&g&&(b=m,Se.log("[dispatch] Using __g7PendingLocalState for context.state:",m));let v=g?.data??u;m&&g?.data?._local&&(v={...g.data,_local:m},Se.log("[dispatch] Updated context.data._local with pendingLocalState"));const S={navigate:l?(_,x)=>l.navigate(_,x):void 0,setState:g?.setState??d,state:b,data:v,_isDispatchFallbackContext:!g?.setState};if(e.debounce){const _=e.debounceKey||`dispatch-${e.handler}`,x=typeof e.debounce=="number"?e.debounce:e.debounce.delay;return i.debouncedCall(_,x,()=>{const{debounce:k,debounceKey:O,...M}=e;i.dispatchAction(M,S)}),{success:!0,debounced:!0}}try{return await i.dispatchAction(e,S)}catch(_){return Se.error("G7Core.dispatch: 액션 실행 오류:",_),{success:!1,error:_ instanceof Error?_:new Error(String(_))}}},Se.log("전역 객체 window.G7Core.dispatch에 노출됨")}function Ao(o){const e={};for(const[n,r]of Object.entries(o))if(n.includes(".")){const i=n.split(".");let l=e;for(let u=0;u0&&e.every(n=>/^\d+$/.test(n))}function by(o,e){const n={...o};for(const r of Object.keys(e))r in n?n[r]!==null&&typeof n[r]=="object"&&!Array.isArray(n[r])&&e[r]!==null&&typeof e[r]=="object"&&!Array.isArray(e[r])&&(n[r]=by(n[r],e[r])):n[r]=e[r];return n}function ta(o,e){if(Array.isArray(o)&&!Array.isArray(e)&&_A(e)){const r=Object.keys(e).map(l=>parseInt(l,10));if(!((r.length>0?Math.max(...r):0)>=o.length+r.length+10)){const l=[...o];for(const[u,d]of Object.entries(e)){const f=parseInt(u,10);f>=0&&f=l.length&&(l[f]=d)}return l}}const n={...o};for(const[r,i]of Object.entries(e))i!==null&&typeof i=="object"&&!Array.isArray(i)?n[r]!==null&&typeof n[r]=="object"?n[r]=ta(n[r],i):n[r]={...i}:n[r]=i;return n}function AA(o){o.state={get:()=>window.__templateApp?.getGlobalState?.()||{},set:(e,n)=>{const r=window.__templateApp;if(r?.setGlobalState&&r?.getGlobalState){const i=n?.merge||"deep",l=Ao(e);let u;if(i==="replace")u=l;else if(i==="shallow")u={...r.getGlobalState(),...l};else{const d=r.getGlobalState();u=ta(d,l)}r.setGlobalState(u,{render:n?.render})}else Se.warn("G7Core.state.set: TemplateApp이 초기화되지 않았습니다.")},setLocal:(e,n)=>{const r=window.__templateApp;if(n?.render===!1&&!n?.selfManaged){const k=window.__g7AutoBindingPaths;k&&k.size>0&&yy(Ao(e)).some(M=>k.has(M))&&(Se.log("[setLocal] render:false + 자동바인딩 경로 겹침 감지 → render:true 자동 승격 (engine-v1.43.0)"),n={...n,render:!0})}if(n?.debounce){const k=r?.getActionDispatcher?.();if(k){const O=n.debounceKey||`setLocal-${Object.keys(e).join(",")}`,{debounce:M,debounceKey:T,...D}=n;k.debouncedCall(O,n.debounce,()=>{o.state.setLocal(e,D)});return}}const i=n?.scope??"current",l=n?.merge||"deep",u=window.__g7ActionContext,d=Ao(e);if(i==="parent"||i==="root"){const k=window.__g7LayoutContextStack||[];if(k.length>0){const O=i==="parent"?k[k.length-1]:k[0];if(O?.setState){O.setState(M=>l==="replace"?d:l==="shallow"?{...M||{},...d}:ta(M||{},d)),Se.log(`[setLocal] scope=${i}: 타겟 컨텍스트에 상태 업데이트 (mergeMode=${l})`,d);return}}Se.warn(`[setLocal] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const f=window.__g7PendingLocalState,g=r?.getGlobalState?.()?._local||{},v=!((window.__g7LayoutContextStack||[]).length>0)&&u?.state?u.state:void 0,S=v?by(g,v):g,_=f||S;let x;if(l==="replace"?x=d:l==="shallow"?x={..._,...d}:x=ta(_,d),r?.setGlobalState&&(r.setGlobalState({_local:x},{render:n?.render}),Se.log(`[setLocal] globalLocal updated via setGlobalState (mergeMode=${l}, render=${n?.render??!0}):`,d)),window.__g7PendingLocalState=x,Se.log("[setLocal] __g7PendingLocalState updated:",x),window.__g7SequenceLocalSync=x,window.__g7LastSetLocalSnapshot=x,l==="replace")window.__g7ForcedLocalFields=d;else{const k=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=ta(k,d)}if(Se.log("[setLocal] __g7ForcedLocalFields updated:",d),l==="replace")window.__g7SetLocalOverrideKeys=d;else{const k=window.__g7SetLocalOverrideKeys||{};window.__g7SetLocalOverrideKeys=ta(k,d)}Se.log("[setLocal] __g7SetLocalOverrideKeys updated:",d),u?.setState&&u.setState(k=>l==="replace"?d:l==="shallow"?{...k||{},...d}:ta(k||{},d)),r?.setGlobalState||Se.warn("G7Core.state.setLocal: TemplateApp이 없습니다.")},getLocal:()=>{const r=(window.__templateApp?.getGlobalState?.()||{})._local||{},i=window.__g7PendingLocalState;if(i)return ta(r,i);const l=window.__g7LastSetLocalSnapshot;return l&&l!==r?ta(r,l):r},update:e=>{const n=window.__templateApp;if(n?.getGlobalState&&n?.setGlobalState){const r=n.getGlobalState(),i=e(r);n.setGlobalState(i)}else Se.warn("G7Core.state.update: TemplateApp이 초기화되지 않았습니다.")},subscribe:e=>{const n=window.__templateApp;return n?.onGlobalStateChange?n.onGlobalStateChange(e):(Se.warn("G7Core.state.subscribe: TemplateApp이 초기화되지 않았습니다."),()=>{})},getDataSource:e=>window.__templateApp?.getDataSource?.(e),getIsolated:e=>e?window.__g7IsolatedStates?.[e]?.state??null:window.__g7ActionContext?.isolatedContext?.state??null,setIsolated:(e,n,r)=>{let i,l,u;typeof e=="string"?(i=e,l=n||{},u=r):(l=e,u=n&&typeof n=="object"&&"merge"in n?n:r);const d=u?.merge||"deep",f=Ao(l);if(i){const b=window.__g7IsolatedStates?.[i];b?.mergeState?b.mergeState(f,d):Se.warn(`G7Core.state.setIsolated: scopeId '${i}'를 찾을 수 없습니다.`);return}const g=window.__g7ActionContext;g?.isolatedContext?.mergeState?g.isolatedContext.mergeState(f,d):Se.warn("G7Core.state.setIsolated: 액션 컨텍스트에 isolatedContext가 없습니다.")},getParent:()=>{const e=window.__g7LayoutContextStack||[];if(e.length===0)return Se.log("[getParent] 레이아웃 컨텍스트 스택이 비어있습니다."),null;const n=e[e.length-1];if(!n)return null;const r=n.dataContext||{};return{_local:n.state||r._local||{},_global:r._global||o.state.get()||{},setState:n.setState}},setParentLocal:(e,n,r)=>{const i=window.__g7LayoutContextStack||[];if(i.length===0){Se.warn("[setParentLocal] 레이아웃 컨텍스트 스택이 비어있습니다.");return}const l=i[i.length-1];if(!l?.setState){Se.warn("[setParentLocal] 부모 컨텍스트에 setState가 없습니다.");return}let u,d;typeof e=="string"?(u={[e]:n},d=r):(u=e,d=n&&typeof n=="object"&&"merge"in n?n:r);const f=d?.merge||"deep",g=Ao(u),m=l.state?._local!==void 0,b=m?l.state._local:l.state||{};let v;f==="replace"?v=g:f==="shallow"?v={...b,...g}:v=ta(b,g),window.__g7PendingLocalState=v;const S=window.__templateApp;if(S?.setGlobalState&&S.setGlobalState({_local:v}),l.setState(v),m?l.state._local=v:l.state=v,l.dataContext&&(l.dataContext._local=v),f==="replace")window.__g7ForcedLocalFields=g;else{const _=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=ta(_,g)}$d()},setParentGlobal:(e,n)=>{let r;typeof e=="string"?r={[e]:n}:r=e,o.state.set(r),Se.log("[setParentGlobal] 전역 상태 업데이트:",r)}},window.__g7IsolatedStates||(window.__g7IsolatedStates={}),o.state.getGlobal=o.state.get,o.state.setGlobal=o.state.set,Se.log("전역 객체 window.G7Core.state에 노출됨")}function xA(o){o.dataSource={get:e=>window.__templateApp?.getDataSource?.(e),set:(e,n,r)=>{const i=window.__templateApp;i?.setDataSource?i.setDataSource(e,n,r):Se.warn("G7Core.dataSource.set: TemplateApp이 초기화되지 않았습니다.")},refetch:async(e,n)=>{const r=window.__templateApp;if(r?.refetchDataSource)return r.refetchDataSource(e,n);Se.warn("G7Core.dataSource.refetch: TemplateApp이 초기화되지 않았습니다.")},updateItem:(e,n,r,i,l)=>{const u=window.__templateApp;return u?.updateDataSourceItem?u.updateDataSourceItem(e,n,r,i,l):(Se.warn("G7Core.dataSource.updateItem: TemplateApp이 초기화되지 않았습니다."),!1)},updateData:(e,n,r,i="append")=>{const l=window.__templateApp;if(!l?.getDataSource||!l?.setDataSource)return Se.warn("G7Core.dataSource.updateData: TemplateApp이 초기화되지 않았습니다."),!1;const u=l.getDataSource(e);if(!u)return Se.warn(`G7Core.dataSource.updateData: 데이터 소스 '${e}'를 찾을 수 없습니다.`),!1;let d,f=u,g=null;if(n){const b=n.split(".");g=b.pop();for(const v of b)if(f&&typeof f=="object"&&v in f)f=f[v];else return Se.warn(`G7Core.dataSource.updateData: 경로 '${n}'를 찾을 수 없습니다.`),!1;d=f[g]}else d=u;if(!Array.isArray(d))return Se.warn("G7Core.dataSource.updateData: 대상 경로의 데이터가 배열이 아닙니다."),!1;if(!Array.isArray(r))return Se.warn("G7Core.dataSource.updateData: newData가 배열이 아닙니다."),!1;const m=i==="prepend"?[...r,...d]:[...d,...r];return n&&g?(f[g]=m,l.setDataSource(e,u,{merge:!1})):l.setDataSource(e,m,{merge:!1}),Se.log(`G7Core.dataSource.updateData: '${e}'에 ${r.length}개 항목 ${i==="prepend"?"앞에":"뒤에"} 추가됨`),!0}},Se.log("전역 객체 window.G7Core.dataSource에 노출됨")}function TA(o,e){o.locale={current:()=>window.__templateApp?.getLocale?.()||"ko",supported:()=>{const r=window.__templateApp?.globalState?.appConfig?.supportedLocales;return Array.isArray(r)&&r.length>0?r:e.getState().templateMetadata?.locales||["ko","en"]},change:async n=>{const r=window.__templateApp;r?.changeLocale?await r.changeLocale(n):Se.warn("G7Core.locale.change: TemplateApp이 초기화되지 않았습니다.")}},Se.log("전역 객체 window.G7Core.locale에 노출됨")}function RA(o){o.toast={show:(e,n)=>{o.dispatch({handler:"toast",params:{message:e,type:n?.type||"info",...n?.duration&&{duration:n.duration}}})},success:(e,n)=>{o.toast.show(e,{type:"success",duration:n})},error:(e,n)=>{o.toast.show(e,{type:"error",duration:n})},warning:(e,n)=>{o.toast.show(e,{type:"warning",duration:n})},info:(e,n)=>{o.toast.show(e,{type:"info",duration:n})}},Se.log("전역 객체 window.G7Core.toast에 노출됨")}function kA(o){o.modal={open:e=>{o.dispatch({handler:"openModal",target:e})},close:e=>{o.dispatch({handler:"closeModal",...e&&{target:e}})},closeAll:()=>{o.dispatch({handler:"closeAllModals"})},isOpen:e=>{const n=o.state.get();return n._global?.activeModal===e||n._global?.modalStack?.includes(e)||!1},getStack:()=>o.state.get()._global?.modalStack||[]},Se.log("전역 객체 window.G7Core.modal에 노출됨")}function DA(o){o.style={mergeClasses:gA,conditionalClass:pA,joinClasses:mA},Se.log("전역 객체 window.G7Core.style에 노출됨")}function OA(o,e){const{webSocketManager:n}=e;o.websocket={manager:n,subscribe:(r,i,l,u)=>n.subscribe(r,i,l,u),unsubscribe:r=>n.unsubscribe(r),leaveChannel:r=>n.leaveChannel(r),disconnect:()=>n.disconnect(),isInitialized:()=>n.isInitialized(),getSubscriptionCount:()=>n.getSubscriptionCount()},Se.log("전역 객체 window.G7Core.websocket에 노출됨")}function LA(o,e){const{transitionManager:n}=e;o.navigation={isPending:()=>n?.getIsPending?.()||!1,onComplete:r=>{if(!n)return r(),()=>{};let i=n.getIsPending();const l=n.subscribe(u=>{u?i=!0:i&&(r(),l())});return l}},Se.log("전역 객체 window.G7Core.navigation에 노출됨")}function MA(o){o.plugin={getSettings:e=>window.G7Config?.plugins?.[e],get:(e,n,r)=>window.G7Config?.plugins?.[e]?.[n]??r,getAll:()=>window.G7Config?.plugins??{}},Se.log("전역 객체 window.G7Core.plugin에 노출됨")}function $A(o){o.module={getSettings:e=>window.G7Config?.modules?.[e],get:(e,n,r)=>window.G7Config?.modules?.[e]?.[n]??r,getAll:()=>window.G7Config?.modules??{}},Se.log("전역 객체 window.G7Core.module에 노출됨")}function NA(o,e){const{transitionManager:n,responsiveManager:r}=e;o.AuthManager=ga,o.api=Ia(),o.TransitionManager=n,o.useTransitionState=dm,o.ResponsiveManager=r,o.useResponsive=zd,o.useControllableState=_C,o.shallowArrayEqual=AC,o.shallowObjectEqual=hm,o.updateQueryParams=async(i,l)=>{const u=window.__templateApp;if(u?.updateQueryParams)return u.updateQueryParams(i,l);Se.warn("G7Core.updateQueryParams: TemplateApp이 초기화되지 않았습니다.")},Se.log("전역 객체 window.G7Core.AuthManager에 노출됨"),Se.log("전역 객체 window.G7Core.api에 노출됨"),Se.log("전역 객체 window.G7Core.ResponsiveManager에 노출됨"),Se.log("전역 객체 window.G7Core.useResponsive에 노출됨"),Se.log("전역 객체 window.G7Core.useControllableState에 노출됨"),Se.log("전역 객체 window.G7Core.updateQueryParams에 노출됨")}function IA(o,e){o.getSlotContext=()=>window.__slotContextValue??null,o.getDynamicRenderer=()=>window.__DynamicRenderer??null,o.getComponentRegistry=()=>e.getComponentRegistry?.()??fa.getInstance(),o.getDataBindingEngine=()=>e.getDataBindingEngine?.()??e.getState().bindingEngine,o.getTranslationEngine=()=>e.getTranslationEngine?.()??e.getState().translationEngine,o.getActionDispatcher=()=>e.getActionDispatcher?.()??e.getState().actionDispatcher,Se.log("전역 객체 window.G7Core 슬롯 API에 노출됨 (getSlotContext, getDynamicRenderer 등)")}function HA(o){o.identity={setLauncher:wt.setLauncher.bind(wt),handle:wt.handle.bind(wt),isIdentityRequired:wt.isIdentityRequired.bind(wt),hasLauncher:wt.hasLauncher.bind(wt),markDomainNoticeShown:wt.markDomainNoticeShown.bind(wt),redirectExternally:wt.redirectExternally.bind(wt),createDeferred:wt.createDeferred.bind(wt),resolveDeferred:wt.resolveDeferred.bind(wt),reset:wt.reset.bind(wt),redirectStashKey:Rd},Se.log("전역 객체 window.G7Core 본인인증(IDV) API에 노출됨 (identity)")}function zA(o){if(typeof window>"u")return;yA(),window.G7Core||(window.G7Core={});const e=window.G7Core;vA(e),bA(e),SA(e),NA(e,o),wA(e,o),CA(e,o),EA(e),AA(e),xA(e),TA(e,o),RA(e),kA(e),DA(e),OA(e,o),LA(e,o),MA(e),$A(e),IA(e,o),HA(e),Se.log("G7Core 전역 객체 초기화 완료")}function jA(){const o=window.G7Core;if(!o){Se.warn("G7Core가 초기화되지 않았습니다.");return}const e=()=>Tr.getInstance(),n={isEnabled:()=>{try{return e().isEnabled()}catch{return!1}},trackRender:r=>{try{e().isEnabled()&&e().trackRender(r)}catch{}},trackIteration:(r,i,l,u,d)=>{try{e().isEnabled()&&e().trackIteration(r,{source:i,itemVar:l,indexVar:u,sourceLength:d})}catch{}},trackIfCondition:(r,i,l,u)=>{try{e().isEnabled()&&e().trackIfCondition(r,i,l)}catch{}},trackMount:(r,i)=>{try{e().isEnabled()&&e().trackMount(r,{...i,id:r})}catch{}},trackUnmount:r=>{try{e().isEnabled()&&e().trackUnmount(r)}catch{}},trackExpressionEval:r=>{try{e().isEnabled()&&e().trackExpressionEval(r)}catch{}},trackBindingEval:r=>{try{e().isEnabled()&&e().trackBindingEval(r)}catch{}},recordCacheHit:()=>{try{e().isEnabled()&&e().recordCacheHit()}catch{}},recordCacheMiss:()=>{try{e().isEnabled()&&e().recordCacheMiss()}catch{}},trackHandlerRegistration:(r,i,l,u)=>{try{e().isEnabled()&&e().trackHandlerRegistration(r,i,l,u)}catch{}},trackHandlerUnregistration:r=>{try{e().isEnabled()&&e().trackHandlerUnregistration(r)}catch{}},logAction:r=>{try{e().isEnabled()&&e().logAction(r)}catch{}},trackRequest:(r,i)=>{try{return e().isEnabled()?e().trackRequest(r,i):""}catch{return""}},completeRequest:(r,i,l)=>{try{e().isEnabled()&&e().completeRequest(r,i,l)}catch{}},failRequest:(r,i)=>{try{e().isEnabled()&&e().failRequest(r,i)}catch{}},trackDataSourceDefinition:r=>{try{e().isEnabled()&&e().trackDataSourceDefinition(r)}catch{}},trackDataSourceLoading:r=>{try{e().isEnabled()&&e().trackDataSourceLoading(r)}catch{}},trackDataSourceLoaded:(r,i,l)=>{try{e().isEnabled()&&e().trackDataSourceLoaded(r,i,l)}catch{}},trackDataSourceError:(r,i)=>{try{e().isEnabled()&&e().trackDataSourceError(r,i)}catch{}},trackForm:(r,i,l)=>{try{e().isEnabled()&&e().trackForm(r,i,l)}catch{}},untrackForm:r=>{try{e().isEnabled()&&e().untrackForm(r)}catch{}},startStateChange:(r,i,l,u)=>{try{return e().isEnabled()?e().startStateChange(r,i,l,u):""}catch{return""}},completeStateChange:r=>{try{e().isEnabled()&&e().completeStateChange(r)}catch{}},trackComponentRender:(r,i,l,u,d,f)=>{try{e().isEnabled()&&e().trackComponentRender(r,i,l,u,d,f)}catch{}},updateLocalState:r=>{try{e().isEnabled()&&e().updateLocalState(r)}catch{}},updateComputedState:r=>{try{e().isEnabled()&&e().updateComputedState(r)}catch{}},updateParentContext:r=>{try{e().isEnabled()&&e().updateParentContext(r)}catch{}},trackComponentStateSource:(r,i,l,u)=>{try{e().isEnabled()&&e().trackComponentStateSource(r,i,l,u)}catch{}},trackDynamicState:(r,i)=>{try{e().isEnabled()&&e().trackDynamicState(r,i)}catch{}},trackContextFlow:(r,i,l,u,d,f)=>{try{e().isEnabled()&&e().trackContextFlow(r,i,l,u,d,f)}catch{}},trackComponentStyle:(r,i,l,u)=>{try{e().isEnabled()&&e().trackComponentStyle(r,i,l,u)}catch{}},trackAuthEvent:(r,i,l,u)=>{try{e().isEnabled()&&e().trackAuthEvent(r,i,l,u)}catch{}},trackAuthHeader:(r,i,l,u,d)=>{try{e().isEnabled()&&e().trackAuthHeader(r,i,l,u,d)}catch{}},trackLog:(r,i,l)=>{try{e().isEnabled()&&e().trackLog(r,i,l)}catch{}},trackAction:r=>{try{e().isEnabled()&&e().trackAction?.(r)}catch{}},trackDataSourceUpdate:r=>{try{e().isEnabled()&&e().trackDataSourceUpdate?.(r)}catch{}},startSequenceExecution:r=>{try{return e().isEnabled()?e().startSequenceExecution(r):""}catch{return""}},captureSequenceActionBefore:(r,i,l,u,d)=>{try{e().isEnabled()&&e().captureSequenceActionBefore(r,i,l,u,d)}catch{}},captureSequenceActionAfter:(r,i,l,u,d,f)=>{try{e().isEnabled()&&e().captureSequenceActionAfter(r,i,l,u,d,f)}catch{}},endSequenceExecution:(r,i)=>{try{e().isEnabled()&&e().endSequenceExecution(r,i)}catch{}},registerStateCaptureForHandler:(r,i,l)=>{try{e().isEnabled()&&e().registerStateCaptureForHandler(r,i,l)}catch{}},detectStaleClosure:(r,i,l,u,d)=>{try{return e().isEnabled()?e().detectStaleClosure(r,i,l,u,d):[]}catch{return[]}},trackStaleClosureWarning:r=>{try{e().isEnabled()&&e().trackStaleClosureWarning(r)}catch{}},trackModalOpen:r=>{try{e().isEnabled()&&e().trackModalOpen(r)}catch{}},trackModalClose:(r,i)=>{try{e().isEnabled()&&e().trackModalClose(r,i)}catch{}},trackModalStateChange:r=>{try{e().isEnabled()&&e().trackModalStateChange(r)}catch{}},trackNestedContext:r=>{try{return e().isEnabled()?e().trackNestedContext(r):""}catch{return""}},trackNestedContextAccess:(r,i)=>{try{e().isEnabled()&&e().trackNestedContextAccess(r,i)}catch{}},trackComputedProperty:(r,i,l,u,d,f,g)=>{try{e().isEnabled()&&e().trackComputedProperty(r,i,l,u,d,f,g)}catch{}},trackComputedRecalc:(r,i,l,u,d,f,g)=>{try{e().isEnabled()&&e().trackComputedRecalc(r,i,l,u,d,f,g)}catch{}},setNamedActionDefinitions:r=>{try{e().isEnabled()&&e().setNamedActionDefinitions(r)}catch{}},trackNamedActionRef:r=>{try{e().isEnabled()&&e().trackNamedActionRef(r)}catch{}}};o.devTools=n,Se.log("G7Core.devTools 인터페이스 초기화 완료")}let xo=null;function UA(){const o=window.G7Core;return o?.__devtools?Promise.resolve(o.__devtools):xo||(xo=new Promise((e,n)=>{const r=window.G7Config?.coreDevToolsAsset||"/build/core/devtools.min.js",i="g7-devtools-bundle",l=()=>{const f=window.G7Core?.__devtools;f?e(f):n(new Error("DevTools 번들 로드됨 — 그러나 __devtools 미노출"))};window.G7Core=window.G7Core||{},window.G7Core.__onDevToolsReady=l;const u=document.getElementById(i);if(u){u.addEventListener("load",l,{once:!0}),u.addEventListener("error",()=>n(new Error(`DevTools 번들 로드 실패: ${r}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=r,d.async=!1,d.addEventListener("load",l,{once:!0}),d.addEventListener("error",()=>{xo=null,n(new Error(`DevTools 번들 로드 실패: ${r}`))},{once:!0}),document.head.appendChild(d)}),xo)}function BA(){const o=window.G7Core;if(!o){Se.warn("G7Core가 초기화되지 않았습니다.");return}const e=Tr.getInstance();if(e.initialize(),!e.isEnabled()){window.G7DevTools={isEnabled:()=>!1,enable:()=>{Se.log("환경설정 > 고급 설정 > 디버그 모드를 켜세요")}},rd(),Se.log("G7DevTools 비활성화됨 (디버그 모드 꺼짐)");return}PA(o,e)}async function PA(o,e){let n;try{n=await UA()}catch(g){Se.warn("DevTools 번들 로드 실패 — 최소 API 로 폴백:",g),window.G7DevTools={isEnabled:()=>!1,enable:()=>{}},rd();return}const{DiagnosticEngine:r,getServerConnector:i,getStyleTracker:l}=n;jA(),rd();try{l().enable(),Se.log("StyleTracker 활성화됨")}catch(g){Se.warn("StyleTracker 활성화 실패:",g)}const u=new r,d=i(),f={isEnabled:()=>e.isEnabled(),state:{get:()=>e.getState(),getHistory:()=>e.getStateHistory(),watch:(g,m)=>e.watchState(g,m)},actions:{getHistory:()=>e.getActionHistory(),watch:g=>e.watchActions(g),getMetrics:()=>e.getActionMetrics()},binding:{evaluate:g=>{const m=o.getDataBindingEngine?.();if(!m){Se.warn("DataBindingEngine이 없습니다.");return}return m.evaluateExpression(g,e.getState())},getCacheStats:()=>e.getCacheStats(),clearStats:()=>{e.resetCacheStats(),Se.log("캐시 통계 초기화됨")}},diagnose:{analyze:g=>u.analyze(g),suggestFix:g=>u.suggestFix(g),getCommonIssues:()=>u.getCommonIssues(),getRulesByCategory:g=>u.getRulesByCategory(g)},server:{dumpState:g=>d.dumpState(g),sendLog:g=>d.sendLog(g),sendError:(g,m)=>d.sendError(g,m),isConnected:()=>d.isConnected(),testConnection:()=>d.testConnection()},config:{isDebugMode:()=>e.isEnabled(),setLogLevel:g=>e.setLogLevel(g),setMaxHistory:g=>e.setMaxHistory(g)},lifecycle:{getMountedComponents:()=>e.getLifecycleInfo().mountedComponents,getOrphanedListeners:()=>e.getLifecycleInfo().orphanedListeners},performance:{getRenderCount:()=>e.getPerformanceInfo().renderCounts,getBindingEvalCount:()=>e.getPerformanceInfo().bindingEvalCount,getMemoryWarnings:()=>e.getPerformanceInfo().memoryWarnings,startProfiling:()=>e.startProfiling(),stopProfiling:()=>e.stopProfiling()},network:{getActiveRequests:()=>e.getNetworkInfo().activeRequests,getRequestHistory:()=>e.getNetworkInfo().requestHistory,getPendingDataSources:()=>e.getNetworkInfo().pendingDataSources},conditional:{getIfConditions:()=>e.getConditionalInfo().ifConditions,getIterations:()=>e.getConditionalInfo().iterations},form:{getForms:()=>e.getFormInfo()},websocket:{getInfo:()=>e.getWebSocketInfo()}};window.G7DevTools=f,o.devtools=f,Se.log("G7DevTools 전역 객체 초기화 완료 (window.G7DevTools)"),qA(n.DevToolsPanel)}function qA(o){if(document.getElementById("g7-devtools-root")){Se.log("DevToolsPanel 이미 렌더링됨");return}const e=document.createElement("div");e.id="g7-devtools-root",document.body.appendChild(e);try{nd.createRoot(e).render(Xe.createElement(o)),Se.log("DevToolsPanel UI 렌더링 완료")}catch(n){Se.error("DevToolsPanel UI 렌더링 실패:",n)}}const et=dt("TemplateEngine"),re={templateId:null,locale:"ko",isInitialized:!1,reactRoot:null,containerId:null,currentLayoutJson:null,currentDataContext:{},translationContext:{templateId:"",locale:"ko"},registry:null,bindingEngine:null,translationEngine:null,actionDispatcher:null,templateMetadata:null};let To=!1;const Ro=[];async function GA(o){try{et.log("템플릿 메타데이터 로드 중...",o);const e=o.split("-").map(r=>r.split("_").map(i=>i.charAt(0).toUpperCase()+i.slice(1)).join("")).join(""),n=window[e];return n?.templateMetadata?(et.log("템플릿 메타데이터 로드 완료 (전역 변수)",n.templateMetadata),n.templateMetadata):(et.warn(`템플릿 번들에 메타데이터가 없습니다. 전역 변수: ${e}`),{identifier:o,locales:["ko","en"],name:{ko:o,en:o},description:{ko:"",en:""},version:"1.0.0",type:"admin"})}catch(e){return et.error("템플릿 메타데이터 로드 실패",e),{identifier:o,locales:["ko","en"],name:{ko:o,en:o},description:{ko:"",en:""},version:"1.0.0",type:"admin"}}}function vy(){const e=(typeof window<"u"?window.__templateApp:void 0)?.globalState?.appConfig?.supportedLocales,n=re.templateMetadata?.locales;return{$locale:re.locale,$locales:Array.isArray(e)&&e.length>0?e:n||["ko","en"],$templateLocales:n||["ko","en"],$templateId:re.templateId}}async function ko(o){try{if(et.log("템플릿 엔진 초기화 시작",o),re.isInitialized)throw new Error("템플릿 엔진이 이미 초기화되었습니다. destroyTemplate()을 먼저 호출하세요.");if(!o.templateId)throw new eA;To=o.debug??!1,typeof window<"u"&&(window.G7Config||(window.G7Config={}),window.G7Config.debug=To),BA(),re.templateId=o.templateId,re.locale=o.locale||"ko",et.log("엔진 인스턴스 생성 중..."),re.registry=fa.getInstance(),re.bindingEngine=new Rn,re.translationEngine=Er.getInstance(),o.cacheVersion!==void 0&&o.cacheVersion>0&&re.translationEngine.setCacheVersion(o.cacheVersion),re.translationContext={templateId:o.templateId,locale:re.locale},re.actionDispatcher=new fo({},re.translationEngine,re.translationContext),mC(re.actionDispatcher),et.log("다국어 파일 로드 중...",o.templateId,re.locale);const e="en",n=[re.translationEngine.loadTranslations(o.templateId,re.locale).then(()=>{et.log(`다국어 파일 로드 완료: ${re.locale}`)}).catch(r=>{et.warn(`다국어 파일 로드 실패 (${re.locale}):`,r instanceof Error?r.message:r)})];re.locale!==e&&n.push(re.translationEngine.loadTranslations(o.templateId,e).then(()=>{et.log(`폴백 다국어 파일 로드 완료: ${e}`)}).catch(r=>{et.warn(`폴백 다국어 파일 로드 실패 (${e}):`,r instanceof Error?r.message:r)})),await Promise.allSettled(n),et.log("템플릿 메타데이터 로드 중...",o.templateId),re.templateMetadata=await GA(o.templateId),re.isInitialized=!0,et.log("템플릿 엔진 초기화 완료 (ComponentRegistry는 별도 로드)")}catch(e){throw et.error("템플릿 엔진 초기화 실패",e),re.isInitialized=!1,re.registry=null,re.bindingEngine=null,re.translationEngine=null,re.actionDispatcher=null,e}}let Do=null;function yf(){const o=window.G7Core;return o?.__LayoutEditorChrome?Promise.resolve(o.__LayoutEditorChrome):Do||(Do=new Promise((e,n)=>{const r=window.G7Config?.coreEditorAsset||"/build/core/layout-editor.min.js",i="g7-layout-editor-bundle",l=document.getElementById(i),u=()=>{const f=window.G7Core?.__LayoutEditorChrome;f?e(f):n(new Error("편집기 번들 로드됨 — 그러나 __LayoutEditorChrome 미노출"))};if(window.G7Core=window.G7Core||{},window.G7Core.__onChromeReady=u,l){l.addEventListener("load",u,{once:!0}),l.addEventListener("error",()=>n(new Error(`편집기 번들 로드 실패: ${r}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=r,d.async=!1,d.addEventListener("load",u,{once:!0}),d.addEventListener("error",()=>{Do=null,n(new Error(`편집기 번들 로드 실패: ${r}`))},{once:!0}),document.head.appendChild(d)}),Do)}async function fs(o){try{if(et.log("템플릿 렌더링 시작",o),!re.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다. initTemplateEngine()을 먼저 호출하세요.");if(!o.containerId)throw new Error("containerId는 필수입니다.");if(!o.layoutJson)throw new Error("layoutJson은 필수입니다.");const e=document.getElementById(o.containerId);if(!e)throw new Error(`컨테이너를 찾을 수 없습니다: #${o.containerId}`);const n=vy();if(re.containerId=o.containerId,re.currentLayoutJson=o.layoutJson,re.currentDataContext={...n,...o.dataContext||{}},re.translationContext=o.translationContext||{templateId:re.templateId||"",locale:re.locale},re.reactRoot||(et.log("React Root 생성"),re.reactRoot=nd.createRoot(e)),typeof window<"u"){const l=hy(window.location.pathname);if(l){et.log("레이아웃 편집기 모드 진입",l);let u;try{u=await yf()}catch(f){et.error("레이아웃 편집기 번들 로드 실패",f),ff.render(o.containerId,{title:"레이아웃 편집기 로드 실패",message:f instanceof Error?f.message:"편집기 번들을 불러오지 못했습니다.",icon:"fas fa-triangle-exclamation",showStack:!1,showReloadButton:!0,debug:To});return}const d=Xe.createElement(u,{templateIdentifier:l.templateIdentifier,initialLocale:re.locale});re.reactRoot.render(Xe.createElement(hc,{translationEngine:re.translationEngine,translationContext:re.translationContext,children:Xe.createElement(Nd,{children:Xe.createElement(ec,{children:Xe.createElement(lf,{children:d})})})}));return}}const r=o.layoutJson.components||[];if(r.length===0){et.warn("렌더링할 컴포넌트가 없습니다.");return}const i=o.layoutJson.modals||[];if(et.log("modals 배열:",i),et.log("modals 개수:",i.length),et.log("DynamicRenderer로 렌더링 시작"),re.reactRoot.render(Xe.createElement(hc,{translationEngine:re.translationEngine,translationContext:re.translationContext},Xe.createElement(Nd,null,Xe.createElement(ec,null,Xe.createElement(lf,null,[...r.map((l,u)=>{const d=re.currentLayoutJson?.layout_name||"";return Xe.createElement(ja,{key:d&&!l._fromBase?`${l.id}_${d}`:l.id,componentDef:l,dataContext:re.currentDataContext,translationContext:re.translationContext,registry:re.registry,bindingEngine:re.bindingEngine,translationEngine:re.translationEngine,actionDispatcher:re.actionDispatcher,isRootRenderer:u===0,layoutKey:d})}),Xe.createElement(sm,{key:"__modal_parent_context_provider"},i.map(l=>{const u=re.currentDataContext._global?.modalStack||[],f=u.includes(l.id)||re.currentDataContext._global?.activeModal===l.id,g=u.indexOf(l.id),m=g>=0?50+g:50,b=window.__g7LayoutContextStack||[],S=b[b.length-1]?.dataContext,_=Xe.createElement(ja,{key:`modal_${l.id}_renderer`,componentDef:{...l,props:{...l.props,isOpen:f,style:{...l.props?.style,zIndex:m},onClose:re.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},re.currentDataContext)}},dataContext:re.currentDataContext,translationContext:re.translationContext,registry:re.registry,bindingEngine:re.bindingEngine,translationEngine:re.translationEngine,actionDispatcher:re.actionDispatcher,parentDataContext:S});return l.data_sources&&l.data_sources.length>0?Xe.createElement(dy,{key:`modal_${l.id}`,isOpen:f,modalId:l.id,dataSources:l.data_sources,dataContext:re.currentDataContext,globalStateUpdater:re.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:re.bindingEngine,debug:To,children:_}):_}))]))))),et.log("템플릿 렌더링 완료"),Ro.length>0){et.log(`대기 중인 데이터 업데이트 ${Ro.length}건 적용`);const l=[...Ro];Ro.length=0;for(const{data:u,options:d}of l)hs(u,d)}}catch(e){throw et.error("템플릿 렌더링 실패",e),e}}function hs(o,e){try{if(et.log("템플릿 데이터 업데이트 시작",Object.keys(o)),!re.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다.");if(!re.reactRoot||!re.currentLayoutJson){et.log("React 미준비 - 데이터 업데이트 큐잉:",Object.keys(o)),Ro.push({data:o,options:e});return}const n=vy(),r={...re.currentDataContext._global||{},...o._global||{}},i=CC(re.currentDataContext._localInit,o._localInit);re.currentDataContext={...n,...re.currentDataContext,...o,_global:r,_localInit:i},r._local!==void 0&&(re.currentDataContext._local=r._local),r._computed!==void 0&&(re.currentDataContext._computed=r._computed);const l=re.currentLayoutJson.components||[],u=re.currentLayoutJson.modals||[];et.log("updateTemplateData - modals 배열:",u),et.log("updateTemplateData - modals 개수:",u.length),et.log("updateTemplateData - activeModal:",re.currentDataContext._global?.activeModal);const d=()=>{re.reactRoot.render(Xe.createElement(hc,{translationEngine:re.translationEngine,translationContext:re.translationContext},Xe.createElement(Nd,null,Xe.createElement(ec,null,Xe.createElement(lf,null,[...l.map((f,g)=>{const m=re.currentLayoutJson?.layout_name||"";return Xe.createElement(ja,{key:m&&!f._fromBase?`${f.id}_${m}`:f.id,componentDef:f,dataContext:re.currentDataContext,translationContext:re.translationContext,registry:re.registry,bindingEngine:re.bindingEngine,translationEngine:re.translationEngine,actionDispatcher:re.actionDispatcher,isRootRenderer:g===0,layoutKey:m})}),Xe.createElement(sm,{key:"__modal_parent_context_provider_update"},u.map(f=>{const g=re.currentDataContext._global?.modalStack||[],b=g.includes(f.id)||re.currentDataContext._global?.activeModal===f.id,v=g.indexOf(f.id),S=v>=0?50+v:50,_=window.__g7LayoutContextStack||[],k=_[_.length-1]?.dataContext,O=Xe.createElement(ja,{key:`modal_${f.id}_renderer`,componentDef:{...f,props:{...f.props,isOpen:b,style:{...f.props?.style,zIndex:S},onClose:re.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},re.currentDataContext)}},dataContext:re.currentDataContext,translationContext:re.translationContext,registry:re.registry,bindingEngine:re.bindingEngine,translationEngine:re.translationEngine,actionDispatcher:re.actionDispatcher,parentDataContext:k});return f.data_sources&&f.data_sources.length>0?Xe.createElement(dy,{key:`modal_${f.id}`,isOpen:b,modalId:f.id,dataSources:f.data_sources,dataContext:re.currentDataContext,globalStateUpdater:re.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:re.bindingEngine,debug:To,children:O}):O}))])))))};e?.sync?(et.log("재렌더링 시작 (sync mode - 즉시 렌더링)"),d()):(et.log("재렌더링 시작 (with startTransition)"),I.startTransition(()=>{d()})),et.log("템플릿 데이터 업데이트 완료")}catch(n){throw et.error("템플릿 데이터 업데이트 실패",n),n}}function Sc(){try{et.log("템플릿 정리 시작"),re.reactRoot&&(et.log("React Root 언마운트"),re.reactRoot.unmount(),re.reactRoot=null),re.templateId=null,re.locale="ko",re.isInitialized=!1,re.containerId=null,re.currentLayoutJson=null,re.currentDataContext={},re.translationContext={templateId:"",locale:"ko"},re.registry=null,re.bindingEngine=null,re.translationEngine=null,re.actionDispatcher=null,re.templateMetadata=null,et.log("템플릿 정리 완료")}catch(o){throw et.error("템플릿 정리 실패",o),o}}function er(){return Object.freeze({...re})}function Sy(){return re.actionDispatcher}const Oo={initTemplateEngine:ko,renderTemplate:fs,updateTemplateData:hs,destroyTemplate:Sc,getState:er};typeof window<"u"&&(window.G7Core||(window.G7Core={}),window.G7Core.TemplateEngine=Oo,zA({getState:()=>({translationEngine:re.translationEngine,translationContext:re.translationContext,bindingEngine:re.bindingEngine,actionDispatcher:re.actionDispatcher,templateMetadata:re.templateMetadata}),transitionManager:Ha,responsiveManager:ui,webSocketManager:mc}),et.log("전역 객체 window.G7Core.TemplateEngine에 노출됨"));const Ua=Object.freeze(Object.defineProperty({__proto__:null,DataSourceManager:Sa,LayoutLoader:sf,TemplateApp:_o,TemplateEngine:Oo,default:Oo,destroyTemplate:Sc,getActionDispatcher:Sy,getState:er,initTemplateApp:mf,initTemplateEngine:ko,loadLayoutEditorBundle:yf,renderTemplate:fs,updateTemplateData:hs},Symbol.toStringTag,{value:"Module"}));Xt.DataSourceManager=Sa,Xt.LayoutLoader=sf,Xt.TemplateApp=_o,Xt.TemplateEngine=Oo,Xt.default=Oo,Xt.destroyTemplate=Sc,Xt.getActionDispatcher=Sy,Xt.getState=er,Xt.initTemplateApp=mf,Xt.initTemplateEngine=ko,Xt.loadLayoutEditorBundle=yf,Xt.renderTemplate=fs,Xt.updateTemplateData=hs,Object.defineProperties(Xt,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})})(this.G7Core=this.G7Core||{}); + `)(a)}catch(r){B.warn(`Expression evaluation failed: ${e}`,r);return}}async executeInitActions(e,a={}){const{getActionDispatcher:r}=await Promise.resolve().then(()=>ja),i=r();if(!i){B.warn("ActionDispatcher not available for init_actions");return}B.log("Executing init_actions:",e.map(d=>d.handler)),B.log("Init actions dataContext:",a),B.log("Init actions dataContext._global:",a._global);const l=e.map(d=>d.handler).filter(d=>d.includes("."));l.length>0&&await this.waitForHandlers(i,l);let u={...a};for(const d of e)try{const f={type:"click",handler:d.handler,target:d.target,params:d.params,resultTo:d.resultTo,onSuccess:d.onSuccess,onError:d.onError,if:d.if,conditions:d.conditions,auth_mode:d.auth_mode,auth_required:d.auth_required},g=i.createHandler(f,u),m=new Event("init");await g(m),B.log(`Init action executed: ${d.handler}`);const b=this.globalState;u={...u,_global:{...b},_local:b._local||u._local||{}},B.log(`Data context refreshed after ${d.handler}:`,{cartKey:u._global?.cartKey})}catch(f){B.error(`Failed to execute init action: ${d.handler}`,f)}}async waitForHandlers(e,a,r=5e3){const i=Date.now(),l=50,u=()=>a.every(f=>e.customHandlers?.has(f));if(u()){B.log("All module handlers already registered");return}const d=ff().getFailedJsAssets();if(d.length>0){const f=d.some(b=>b==="module"||b==="plugin"),g=a.filter(b=>!e.customHandlers?.has(b)),m=g.length>0&&g.every(b=>d.some(v=>b.startsWith(`${v}.`)));if(f||m){B.warn("Extension asset load failed — not waiting for handlers that will never register:",{pending:g,failedAssets:d});return}}return B.log("Waiting for module handlers:",a),new Promise(f=>{const g=()=>{if(u()){B.log("All module handlers now registered"),f();return}if(Date.now()-i>=r){const m=a.filter(b=>!e.customHandlers?.has(b));B.warn("Timeout waiting for handlers:",m),f();return}setTimeout(g,l)};g()})}};$(Ca,"LOCALE_STORAGE_KEY","g7_locale"),$(Ca,"CACHE_VERSION_STORAGE_KEY","g7_cache_version");let So=Ca;function gf(o){const e=new So(o);return o.websocket&&(hc.configure(o.websocket),B.log("WebSocket 설정 완료")),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{e.init(),window.__templateApp=e}):(e.init(),window.__templateApp=e),e}typeof window<"u"&&(window.G7Core=window.G7Core||{},window.G7Core.initTemplateApp=gf,window.G7Core.dataSource={refetch:async(o,e)=>{const a=window.__templateApp;if(!a){B.warn("TemplateApp not initialized (G7Core.dataSource.refetch)");return}return a.refetchDataSource(o,e)},get:o=>{const e=window.__templateApp;if(!e){B.warn("TemplateApp not initialized (G7Core.dataSource.get)");return}return e.getDataSource(o)}});function W_(o={}){const{checked:e,value:a,name:r,type:i="checkbox"}=o,u=a!==void 0?a:i==="checkbox"||i==="radio"?e??!1:String(e??""),d={checked:e??!1,value:u,name:r??"",type:i};return{target:d,currentTarget:d,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("change"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"change",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{}}}function Y_(o={}){const{button:e=0,clientX:a=0,clientY:r=0}=o;return{button:e,clientX:a,clientY:r,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new MouseEvent("click"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"click",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div")}}function X_(){return{preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new Event("submit"),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:"submit",isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("form"),currentTarget:document.createElement("form")}}function J_(o,e="keydown"){return{key:o,code:o.length===1?`Key${o.toUpperCase()}`:o,preventDefault:()=>{},stopPropagation:()=>{},nativeEvent:new KeyboardEvent(e,{key:o}),bubbles:!0,cancelable:!0,defaultPrevented:!1,eventPhase:Event.AT_TARGET,isTrusted:!1,timeStamp:Date.now(),type:e,isDefaultPrevented:()=>!1,isPropagationStopped:()=>!1,persist:()=>{},target:document.createElement("div"),currentTarget:document.createElement("div"),altKey:!1,ctrlKey:!1,metaKey:!1,shiftKey:!1,repeat:!1}}const Q_={justify:/^justify-(start|end|center|between|around|evenly)$/,items:/^items-(start|end|center|baseline|stretch)$/,content:/^content-(start|end|center|between|around|evenly|stretch)$/,self:/^self-(auto|start|end|center|stretch|baseline)$/,flex:/^flex-(row|row-reverse|col|col-reverse|wrap|wrap-reverse|nowrap|1|auto|initial|none)$/,grow:/^(grow|grow-0)$/,shrink:/^(shrink|shrink-0)$/,basis:/^basis-/,order:/^(order-|-)order-/,gap:/^gap(-x|-y)?-/,gridCols:/^grid-cols-/,gridRows:/^grid-rows-/,colSpan:/^col-(span-|start-|end-)/,rowSpan:/^row-(span-|start-|end-)/,display:/^(block|inline-block|inline|flex|inline-flex|table|inline-table|table-caption|table-cell|table-column|table-column-group|table-footer-group|table-header-group|table-row-group|table-row|flow-root|grid|inline-grid|contents|list-item|hidden)$/,position:/^(static|fixed|absolute|relative|sticky)$/,inset:/^(inset|top|right|bottom|left)-/,zIndex:/^z-/,width:/^w-/,minWidth:/^min-w-/,maxWidth:/^max-w-/,height:/^h-/,minHeight:/^min-h-/,maxHeight:/^max-h-/,padding:/^p[xytblr]?-/,margin:/^-?m[xytblr]?-/,space:/^space-(x|y)-/,fontSize:/^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/,fontWeight:/^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/,fontStyle:/^(italic|not-italic)$/,fontFamily:/^font-(sans|serif|mono)/,textAlign:/^text-(left|center|right|justify|start|end)$/,textColor:/^text-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,textDecoration:/^(underline|overline|line-through|no-underline)$/,textTransform:/^(uppercase|lowercase|capitalize|normal-case)$/,lineHeight:/^leading-/,letterSpacing:/^tracking-/,textOverflow:/^(truncate|text-ellipsis|text-clip)$/,whitespace:/^whitespace-/,wordBreak:/^break-/,bgColor:/^bg-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,bgGradient:/^bg-gradient-/,bgSize:/^bg-(auto|cover|contain)$/,bgPosition:/^bg-(bottom|center|left|left-bottom|left-top|right|right-bottom|right-top|top)$/,bgRepeat:/^bg-(repeat|no-repeat|repeat-x|repeat-y|repeat-round|repeat-space)$/,bgAttachment:/^bg-(fixed|local|scroll)$/,bgClip:/^bg-clip-/,bgOrigin:/^bg-origin-/,borderWidth:/^border(-[xytblr])?(-0|-2|-4|-8)?$/,borderColor:/^border-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,borderStyle:/^border-(solid|dashed|dotted|double|hidden|none)$/,borderRadius:/^rounded(-[tblrse]{1,2})?(-none|-sm|-md|-lg|-xl|-2xl|-3xl|-full)?$/,ringWidth:/^ring(-0|-1|-2|-4|-8|-inset)?$/,ringColor:/^ring-(inherit|current|transparent|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-/,ringOffset:/^ring-offset-/,shadow:/^shadow(-sm|-md|-lg|-xl|-2xl|-inner|-none)?$/,opacity:/^opacity-/,mixBlend:/^mix-blend-/,bgBlend:/^bg-blend-/,blur:/^blur(-none|-sm|-md|-lg|-xl|-2xl|-3xl)?$/,brightness:/^brightness-/,contrast:/^contrast-/,grayscale:/^grayscale(-0)?$/,hueRotate:/^-?hue-rotate-/,invert:/^invert(-0)?$/,saturate:/^saturate-/,sepia:/^sepia(-0)?$/,backdropBlur:/^backdrop-blur-/,backdropBrightness:/^backdrop-brightness-/,backdropContrast:/^backdrop-contrast-/,backdropGrayscale:/^backdrop-grayscale-/,backdropHueRotate:/^backdrop-hue-rotate-/,backdropInvert:/^backdrop-invert-/,backdropOpacity:/^backdrop-opacity-/,backdropSaturate:/^backdrop-saturate-/,backdropSepia:/^backdrop-sepia-/,scale:/^scale(-x|-y)?-/,rotate:/^-?rotate-/,translate:/^-?translate-[xy]-/,skew:/^-?skew-[xy]-/,transformOrigin:/^origin-/,transition:/^transition(-none|-all|-colors|-opacity|-shadow|-transform)?$/,duration:/^duration-/,ease:/^ease-(linear|in|out|in-out)$/,delay:/^delay-/,animate:/^animate-/,cursor:/^cursor-/,userSelect:/^select-/,pointerEvents:/^pointer-events-/,resize:/^resize(-none|-x|-y)?$/,scrollBehavior:/^scroll-(auto|smooth)$/,touchAction:/^touch-/,overflow:/^overflow(-x|-y)?-(auto|hidden|clip|visible|scroll)$/,overscroll:/^overscroll(-x|-y)?-(auto|contain|none)$/,visibility:/^(visible|invisible|collapse)$/,aspectRatio:/^aspect-/,columns:/^columns-/,breakAfter:/^break-after-/,breakBefore:/^break-before-/,breakInside:/^break-inside-/,boxDecorationBreak:/^box-decoration-/,boxSizing:/^box-(border|content)$/,float:/^float-(right|left|none)$/,clear:/^clear-(left|right|both|none)$/,isolation:/^(isolate|isolation-auto)$/,objectFit:/^object-(contain|cover|fill|none|scale-down)$/,objectPosition:/^object-/};function ly(o){const e=o.replace(/^(dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+/,"");for(const[a,r]of Object.entries(Q_))if(r.test(e))return a;return null}function cy(o){const e=o.match(/^((dark:|hover:|focus:|active:|disabled:|group-hover:|sm:|md:|lg:|xl:|2xl:)+)/);return e?e[1]:""}function Z_(o,e){if(!e||e.trim()==="")return o;if(!o||o.trim()==="")return e;const a=o.split(/\s+/).filter(Boolean),r=e.split(/\s+/).filter(Boolean),i=new Map;for(const u of r){const d=ly(u);if(d){const f=cy(u),g=`${d}:${f}`;i.set(g,u)}}return[...a.filter(u=>{const d=ly(u);if(!d)return!0;const f=cy(u),g=`${d}:${f}`;return!i.has(g)}),...r].join(" ")}function eA(o){return Object.entries(o).filter(([,e])=>e).map(([e])=>e).join(" ")}function tA(...o){return o.filter(e=>typeof e=="string"&&e.length>0).join(" ")}const we=ct("G7CoreGlobals");function nA(){window.React=We,window.ReactDOM={...Qu,createPortal:Ar.createPortal,unstable_batchedUpdates:o=>o()},window.ReactJSXRuntime=J0,we.log("전역 객체 window.React, window.ReactDOM, window.ReactJSXRuntime에 노출됨")}function aA(o){const e=new Map,a=Tr.getInstance();o.componentEvent={on:(r,i)=>(e.has(r)||e.set(r,new Set),e.get(r).add(i),a.trackEventSubscribe(r),()=>{e.get(r)?.delete(i),a.trackEventUnsubscribe(r)}),emit:async(r,i)=>{const l=e.get(r),u=l?.size??0;if(!l||l.size===0)return a.trackEventEmit(r,i,0),[];let d,f=[];try{f=await Promise.all(Array.from(l).map(async g=>{try{return await g(i)}catch(m){throw we.error(`componentEvent: Error in listener for "${r}":`,m),m}}))}catch(g){d=g instanceof Error?g:new Error(String(g))}if(a.trackEventEmit(r,i,u,f,d),d)throw d;return f},off:r=>{e.delete(r),a.trackEventOff(r)},clear:()=>{e.clear(),a.trackEventClear()}},we.log("전역 객체 window.G7Core.componentEvent에 노출됨")}function rA(o){o.__runtime||(o.__runtime={DynamicRenderer:za,ComponentRegistry:da,TranslationEngine:Er,DataSourceManager:va,dataSourceManager:H_,DataBindingEngine:wn,dataBindingEngine:XS,ActionDispatcher:lo,TranslationReactContext:af,TranslationProvider:uc,useTranslation:Km,ResponsiveContext:$d,ResponsiveProvider:Jl,useResponsive:Nd,responsiveManager:ui,BREAKPOINT_PRESETS:Dl,AuthManager:ha,createLogger:ct,G7DevToolsCore:Tr},we.log("전역 객체 window.G7Core.__runtime(코어 런타임 표면)에 노출됨"))}function iA(o){if(o.layoutEditor&&o.layoutEditor.__isStub!==!0||o.layoutEditor&&o.layoutEditor.__isStub===!0)return;const e=[],a=[];o.layoutEditor={__isStub:!0,__queue:e,__readyCallbacks:a,registerWidget:(r,i)=>e.push(["widget",r,i]),registerNodeEditor:(r,i)=>e.push(["nodeEditor",r,i]),registerCanvasOverlay:(r,i)=>e.push(["canvasOverlay",r,i]),onReady:r=>{typeof r=="function"&&a.push(r)}},we.log("전역 객체 window.G7Core.layoutEditor 예약 접수함(stub) 노출됨")}function sA(o,e){o.useTranslation=Km,o.t=(a,r)=>{const i=e.getState();if(!i.translationEngine||!i.translationContext)return a;if(r){const l="|"+Object.entries(r).map(([u,d])=>`${u}=${d}`).join("|");return i.translationEngine.translate(a,i.translationContext,l)}return i.translationEngine.translate(a,i.translationContext)},we.log("전역 객체 window.G7Core.useTranslation에 노출됨")}function oA(o,e){o.createChangeEvent=W_,o.createClickEvent=Y_,o.createSubmitEvent=X_,o.createKeyboardEvent=J_,o.uuid=()=>typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,a=>{const r=Math.random()*16|0;return(a==="x"?r:r&3|8).toString(16)}),o.createLogger=ct,we.log("전역 객체 window.G7Core.createLogger에 노출됨"),o.renderItemChildren=(a,r,i,l,u)=>{const d=e.getState(),m={_global:window.__templateApp?.getGlobalState?.()||{},_local:r._local||{},_computed:r._computed||{},...r},b=new Set,v=(E,A)=>{const T=m._global?._remountKeys;return E&&T?.[E]&&!b.has(E)?(b.add(E),`${E}-remount-${T[E]}`):E||A},S={translationContext:d.translationContext,translationEngine:d.translationEngine,bindingEngine:d.bindingEngine,actionDispatcher:d.actionDispatcher,getRemountKey:v,...u};return Zs(a,m,i,l,S)},o.getComponentMap=()=>da.getInstance().getComponentMap(),o.renderComponentLayout=(a,r,i)=>{if(!a||!Array.isArray(a)||a.length===0)return null;const l=da.getInstance().getComponentMap(),u=e.getState(),f=window.__templateApp?.getGlobalState?.()||{},g={_global:f._global||{},_local:f._local||{},_computed:f._computed||{},...r},m=new Set,b=(v,S)=>{const E=g._global?._remountKeys;return v&&E?.[v]&&!m.has(v)?(m.add(v),`${v}-remount-${E[v]}`):v||S};return Zs(a,g,l,i,{translationContext:u.translationContext,translationEngine:u.translationEngine,bindingEngine:u.bindingEngine,actionDispatcher:u.actionDispatcher,getRemountKey:b})},o.renderExpandContent=a=>{const{children:r,row:i,expandContext:l,componentContext:u,keyPrefix:d}=a;if(!r||!Array.isArray(r)||r.length===0)return null;const f=a.componentMap||da.getInstance().getComponentMap(),g=e.getState(),m=o.state?.get?.()||{},b=u?.stateRef?.current??u?.state??{},v={...m._local||{},...b},S=u?.computedRef?.current??m._computed??{},E=g.bindingEngine??o.getDataBindingEngine?.(),A={...m,_local:v,_computed:S,row:i,item:i,$item:i};let T={};if(l&&typeof l=="object"&&E)try{for(const[D,U]of Object.entries(l))if(typeof U=="string"){const j=U.match(/^\{\{([^}]+)\}\}$/);if(j){const P=j[1].trim();try{T[D]=E.evaluateExpression(P,A,{skipCache:!0})}catch{T[D]=void 0}}else T[D]=E.resolveBindings(U,A,{skipCache:!0})}else T[D]=U}catch(D){we.warn("renderExpandContent: expandContext 평가 오류:",D)}const L={row:i,item:i,$item:i,_local:v,_global:m._global||{},_computed:m._computed||{},...T},M=new Set,R=(D,U)=>{const j=L._global?._remountKeys;return D&&j?.[D]&&!M.has(D)?(M.add(D),`${D}-remount-${j[D]}`):D||U};return Zs(r,L,f,d,{translationContext:g.translationContext,translationEngine:g.translationEngine??void 0,bindingEngine:g.bindingEngine??void 0,actionDispatcher:g.actionDispatcher,componentContext:u,getRemountKey:R})},o.$get=function(r,i,l=void 0){if(r==null)return l;const u=Array.isArray(i)?i:[i];if(u.length===0)return r;let d=r;for(const f of u){if(d==null||f==null)return l;d=d[f]}return d??l},we.log("전역 객체 window.G7Core.renderItemChildren에 노출됨"),we.log("전역 객체 window.G7Core.renderComponentLayout에 노출됨"),we.log("전역 객체 window.G7Core.renderExpandContent에 노출됨"),we.log("전역 객체 window.G7Core.$get에 노출됨")}function lA(o){o.dispatch=async(e,a)=>{const r=window.__templateApp;if(!r)return we.warn("G7Core.dispatch: TemplateApp이 초기화되지 않았습니다."),{success:!1,error:new Error("TemplateApp이 초기화되지 않았습니다.")};const i=r.getActionDispatcher?.();if(!i)return we.warn("G7Core.dispatch: ActionDispatcher를 찾을 수 없습니다."),{success:!1,error:new Error("ActionDispatcher를 찾을 수 없습니다.")};const l=r.getRouter?.(),u=r.getGlobalState?.(),d=r.setGlobalState?.bind(r),g=a?.componentContext||window.__g7ActionContext,m=window.__g7PendingLocalState;let b=g?.state??u;m&&g&&(b=m,we.log("[dispatch] Using __g7PendingLocalState for context.state:",m));let v=g?.data??u;m&&g?.data?._local&&(v={...g.data,_local:m},we.log("[dispatch] Updated context.data._local with pendingLocalState"));const S={navigate:l?(E,A)=>l.navigate(E,A):void 0,setState:g?.setState??d,state:b,data:v,_isDispatchFallbackContext:!g?.setState};if(e.debounce){const E=e.debounceKey||`dispatch-${e.handler}`,A=typeof e.debounce=="number"?e.debounce:e.debounce.delay;return i.debouncedCall(E,A,()=>{const{debounce:T,debounceKey:L,...M}=e;i.dispatchAction(M,S)}),{success:!0,debounced:!0}}try{return await i.dispatchAction(e,S)}catch(E){return we.error("G7Core.dispatch: 액션 실행 오류:",E),{success:!1,error:E instanceof Error?E:new Error(String(E))}}},we.log("전역 객체 window.G7Core.dispatch에 노출됨")}function wo(o){const e={};for(const[a,r]of Object.entries(o))if(a.includes(".")){const i=a.split(".");let l=e;for(let u=0;u0&&e.every(a=>/^\d+$/.test(a))}function dy(o,e){const a={...o};for(const r of Object.keys(e))r in a?a[r]!==null&&typeof a[r]=="object"&&!Array.isArray(a[r])&&e[r]!==null&&typeof e[r]=="object"&&!Array.isArray(e[r])&&(a[r]=dy(a[r],e[r])):a[r]=e[r];return a}function Zn(o,e){if(Array.isArray(o)&&!Array.isArray(e)&&cA(e)){const r=Object.keys(e).map(l=>parseInt(l,10));if(!((r.length>0?Math.max(...r):0)>=o.length+r.length+10)){const l=[...o];for(const[u,d]of Object.entries(e)){const f=parseInt(u,10);f>=0&&f=l.length&&(l[f]=d)}return l}}const a={...o};for(const[r,i]of Object.entries(e))i!==null&&typeof i=="object"&&!Array.isArray(i)?a[r]!==null&&typeof a[r]=="object"?a[r]=Zn(a[r],i):a[r]={...i}:a[r]=i;return a}function uA(o){o.state={get:()=>window.__templateApp?.getGlobalState?.()||{},set:(e,a)=>{const r=window.__templateApp;if(r?.setGlobalState&&r?.getGlobalState){const i=a?.merge||"deep",l=wo(e);let u;if(i==="replace")u=l;else if(i==="shallow")u={...r.getGlobalState(),...l};else{const d=r.getGlobalState();u=Zn(d,l)}r.setGlobalState(u,{render:a?.render})}else we.warn("G7Core.state.set: TemplateApp이 초기화되지 않았습니다.")},setLocal:(e,a)=>{const r=window.__templateApp;if(a?.render===!1&&!a?.selfManaged){const T=window.__g7AutoBindingPaths;T&&T.size>0&&uy(wo(e)).some(M=>T.has(M))&&(we.log("[setLocal] render:false + 자동바인딩 경로 겹침 감지 → render:true 자동 승격 (engine-v1.43.0)"),a={...a,render:!0})}if(a?.debounce){const T=r?.getActionDispatcher?.();if(T){const L=a.debounceKey||`setLocal-${Object.keys(e).join(",")}`,{debounce:M,debounceKey:R,...D}=a;T.debouncedCall(L,a.debounce,()=>{o.state.setLocal(e,D)});return}}const i=a?.scope??"current",l=a?.merge||"deep",u=window.__g7ActionContext,d=wo(e);if(i==="parent"||i==="root"){const T=window.__g7LayoutContextStack||[];if(T.length>0){const L=i==="parent"?T[T.length-1]:T[0];if(L?.setState){L.setState(M=>l==="replace"?d:l==="shallow"?{...M||{},...d}:Zn(M||{},d)),we.log(`[setLocal] scope=${i}: 타겟 컨텍스트에 상태 업데이트 (mergeMode=${l})`,d);return}}we.warn(`[setLocal] scope=${i}: 레이아웃 컨텍스트 스택이 비어있습니다. current로 폴백합니다.`)}const f=window.__g7PendingLocalState,g=r?.getGlobalState?.()?._local||{},v=!((window.__g7LayoutContextStack||[]).length>0)&&u?.state?u.state:void 0,S=v?dy(g,v):g,E=f||S;let A;if(l==="replace"?A=d:l==="shallow"?A={...E,...d}:A=Zn(E,d),r?.setGlobalState&&(r.setGlobalState({_local:A},{render:a?.render}),we.log(`[setLocal] globalLocal updated via setGlobalState (mergeMode=${l}, render=${a?.render??!0}):`,d)),window.__g7PendingLocalState=A,we.log("[setLocal] __g7PendingLocalState updated:",A),window.__g7SequenceLocalSync=A,window.__g7LastSetLocalSnapshot=A,l==="replace")window.__g7ForcedLocalFields=d;else{const T=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=Zn(T,d)}if(we.log("[setLocal] __g7ForcedLocalFields updated:",d),l==="replace")window.__g7SetLocalOverrideKeys=d;else{const T=window.__g7SetLocalOverrideKeys||{};window.__g7SetLocalOverrideKeys=Zn(T,d)}we.log("[setLocal] __g7SetLocalOverrideKeys updated:",d),u?.setState&&u.setState(T=>l==="replace"?d:l==="shallow"?{...T||{},...d}:Zn(T||{},d)),r?.setGlobalState||we.warn("G7Core.state.setLocal: TemplateApp이 없습니다.")},getLocal:()=>{const r=(window.__templateApp?.getGlobalState?.()||{})._local||{},i=window.__g7PendingLocalState;if(i)return Zn(r,i);const l=window.__g7LastSetLocalSnapshot;return l&&l!==r?Zn(r,l):r},update:e=>{const a=window.__templateApp;if(a?.getGlobalState&&a?.setGlobalState){const r=a.getGlobalState(),i=e(r);a.setGlobalState(i)}else we.warn("G7Core.state.update: TemplateApp이 초기화되지 않았습니다.")},subscribe:e=>{const a=window.__templateApp;return a?.onGlobalStateChange?a.onGlobalStateChange(e):(we.warn("G7Core.state.subscribe: TemplateApp이 초기화되지 않았습니다."),()=>{})},getDataSource:e=>window.__templateApp?.getDataSource?.(e),getIsolated:e=>e?window.__g7IsolatedStates?.[e]?.state??null:window.__g7ActionContext?.isolatedContext?.state??null,setIsolated:(e,a,r)=>{let i,l,u;typeof e=="string"?(i=e,l=a||{},u=r):(l=e,u=a&&typeof a=="object"&&"merge"in a?a:r);const d=u?.merge||"deep",f=wo(l);if(i){const b=window.__g7IsolatedStates?.[i];b?.mergeState?b.mergeState(f,d):we.warn(`G7Core.state.setIsolated: scopeId '${i}'를 찾을 수 없습니다.`);return}const g=window.__g7ActionContext;g?.isolatedContext?.mergeState?g.isolatedContext.mergeState(f,d):we.warn("G7Core.state.setIsolated: 액션 컨텍스트에 isolatedContext가 없습니다.")},getParent:()=>{const e=window.__g7LayoutContextStack||[];if(e.length===0)return we.log("[getParent] 레이아웃 컨텍스트 스택이 비어있습니다."),null;const a=e[e.length-1];if(!a)return null;const r=a.dataContext||{};return{_local:a.state||r._local||{},_global:r._global||o.state.get()||{},setState:a.setState}},setParentLocal:(e,a,r)=>{const i=window.__g7LayoutContextStack||[];if(i.length===0){we.warn("[setParentLocal] 레이아웃 컨텍스트 스택이 비어있습니다.");return}const l=i[i.length-1];if(!l?.setState){we.warn("[setParentLocal] 부모 컨텍스트에 setState가 없습니다.");return}let u,d;typeof e=="string"?(u={[e]:a},d=r):(u=e,d=a&&typeof a=="object"&&"merge"in a?a:r);const f=d?.merge||"deep",g=wo(u),m=l.state?._local!==void 0,b=m?l.state._local:l.state||{};let v;f==="replace"?v=g:f==="shallow"?v={...b,...g}:v=Zn(b,g),window.__g7PendingLocalState=v;const S=window.__templateApp;if(S?.setGlobalState&&S.setGlobalState({_local:v}),l.setState(v),m?l.state._local=v:l.state=v,l.dataContext&&(l.dataContext._local=v),f==="replace")window.__g7ForcedLocalFields=g;else{const E=window.__g7ForcedLocalFields||{};window.__g7ForcedLocalFields=Zn(E,g)}Od()},setParentGlobal:(e,a)=>{let r;typeof e=="string"?r={[e]:a}:r=e,o.state.set(r),we.log("[setParentGlobal] 전역 상태 업데이트:",r)}},window.__g7IsolatedStates||(window.__g7IsolatedStates={}),o.state.getGlobal=o.state.get,o.state.setGlobal=o.state.set,we.log("전역 객체 window.G7Core.state에 노출됨")}function dA(o){o.dataSource={get:e=>window.__templateApp?.getDataSource?.(e),set:(e,a,r)=>{const i=window.__templateApp;i?.setDataSource?i.setDataSource(e,a,r):we.warn("G7Core.dataSource.set: TemplateApp이 초기화되지 않았습니다.")},refetch:async(e,a)=>{const r=window.__templateApp;if(r?.refetchDataSource)return r.refetchDataSource(e,a);we.warn("G7Core.dataSource.refetch: TemplateApp이 초기화되지 않았습니다.")},updateItem:(e,a,r,i,l)=>{const u=window.__templateApp;return u?.updateDataSourceItem?u.updateDataSourceItem(e,a,r,i,l):(we.warn("G7Core.dataSource.updateItem: TemplateApp이 초기화되지 않았습니다."),!1)},updateData:(e,a,r,i="append")=>{const l=window.__templateApp;if(!l?.getDataSource||!l?.setDataSource)return we.warn("G7Core.dataSource.updateData: TemplateApp이 초기화되지 않았습니다."),!1;const u=l.getDataSource(e);if(!u)return we.warn(`G7Core.dataSource.updateData: 데이터 소스 '${e}'를 찾을 수 없습니다.`),!1;let d,f=u,g=null;if(a){const b=a.split(".");g=b.pop();for(const v of b)if(f&&typeof f=="object"&&v in f)f=f[v];else return we.warn(`G7Core.dataSource.updateData: 경로 '${a}'를 찾을 수 없습니다.`),!1;d=f[g]}else d=u;if(!Array.isArray(d))return we.warn("G7Core.dataSource.updateData: 대상 경로의 데이터가 배열이 아닙니다."),!1;if(!Array.isArray(r))return we.warn("G7Core.dataSource.updateData: newData가 배열이 아닙니다."),!1;const m=i==="prepend"?[...r,...d]:[...d,...r];return a&&g?(f[g]=m,l.setDataSource(e,u,{merge:!1})):l.setDataSource(e,m,{merge:!1}),we.log(`G7Core.dataSource.updateData: '${e}'에 ${r.length}개 항목 ${i==="prepend"?"앞에":"뒤에"} 추가됨`),!0}},we.log("전역 객체 window.G7Core.dataSource에 노출됨")}function fA(o,e){o.locale={current:()=>window.__templateApp?.getLocale?.()||"ko",supported:()=>{const r=window.__templateApp?.globalState?.appConfig?.supportedLocales;return Array.isArray(r)&&r.length>0?r:e.getState().templateMetadata?.locales||["ko","en"]},change:async a=>{const r=window.__templateApp;r?.changeLocale?await r.changeLocale(a):we.warn("G7Core.locale.change: TemplateApp이 초기화되지 않았습니다.")}},we.log("전역 객체 window.G7Core.locale에 노출됨")}function hA(o){o.toast={show:(e,a)=>{o.dispatch({handler:"toast",params:{message:e,type:a?.type||"info",...a?.duration&&{duration:a.duration}}})},success:(e,a)=>{o.toast.show(e,{type:"success",duration:a})},error:(e,a)=>{o.toast.show(e,{type:"error",duration:a})},warning:(e,a)=>{o.toast.show(e,{type:"warning",duration:a})},info:(e,a)=>{o.toast.show(e,{type:"info",duration:a})}},we.log("전역 객체 window.G7Core.toast에 노출됨")}function gA(o){o.modal={open:e=>{o.dispatch({handler:"openModal",target:e})},close:e=>{o.dispatch({handler:"closeModal",...e&&{target:e}})},closeAll:()=>{o.dispatch({handler:"closeAllModals"})},isOpen:e=>{const a=o.state.get();return a._global?.activeModal===e||a._global?.modalStack?.includes(e)||!1},getStack:()=>o.state.get()._global?.modalStack||[]},we.log("전역 객체 window.G7Core.modal에 노출됨")}function pA(o){o.style={mergeClasses:Z_,conditionalClass:eA,joinClasses:tA},we.log("전역 객체 window.G7Core.style에 노출됨")}function mA(o,e){const{webSocketManager:a}=e;o.websocket={manager:a,subscribe:(r,i,l,u)=>a.subscribe(r,i,l,u),unsubscribe:r=>a.unsubscribe(r),leaveChannel:r=>a.leaveChannel(r),disconnect:()=>a.disconnect(),isInitialized:()=>a.isInitialized(),getSubscriptionCount:()=>a.getSubscriptionCount()},we.log("전역 객체 window.G7Core.websocket에 노출됨")}function yA(o,e){const{transitionManager:a}=e;o.navigation={isPending:()=>a?.getIsPending?.()||!1,onComplete:r=>{if(!a)return r(),()=>{};let i=a.getIsPending();const l=a.subscribe(u=>{u?i=!0:i&&(r(),l())});return l}},we.log("전역 객체 window.G7Core.navigation에 노출됨")}function bA(o){o.plugin={getSettings:e=>window.G7Config?.plugins?.[e],get:(e,a,r)=>window.G7Config?.plugins?.[e]?.[a]??r,getAll:()=>window.G7Config?.plugins??{}},we.log("전역 객체 window.G7Core.plugin에 노출됨")}function vA(o){o.module={getSettings:e=>window.G7Config?.modules?.[e],get:(e,a,r)=>window.G7Config?.modules?.[e]?.[a]??r,getAll:()=>window.G7Config?.modules??{}},we.log("전역 객체 window.G7Core.module에 노출됨")}function SA(o,e){const{transitionManager:a,responsiveManager:r}=e;o.AuthManager=ha,o.api=Na(),o.TransitionManager=a,o.useTransitionState=am,o.ResponsiveManager=r,o.useResponsive=Nd,o.useControllableState=cC,o.shallowArrayEqual=uC,o.shallowObjectEqual=im,o.updateQueryParams=async(i,l)=>{const u=window.__templateApp;if(u?.updateQueryParams)return u.updateQueryParams(i,l);we.warn("G7Core.updateQueryParams: TemplateApp이 초기화되지 않았습니다.")},we.log("전역 객체 window.G7Core.AuthManager에 노출됨"),we.log("전역 객체 window.G7Core.api에 노출됨"),we.log("전역 객체 window.G7Core.ResponsiveManager에 노출됨"),we.log("전역 객체 window.G7Core.useResponsive에 노출됨"),we.log("전역 객체 window.G7Core.useControllableState에 노출됨"),we.log("전역 객체 window.G7Core.updateQueryParams에 노출됨")}function wA(o,e){o.getSlotContext=()=>window.__slotContextValue??null,o.getDynamicRenderer=()=>window.__DynamicRenderer??null,o.getComponentRegistry=()=>e.getComponentRegistry?.()??da.getInstance(),o.getDataBindingEngine=()=>e.getDataBindingEngine?.()??e.getState().bindingEngine,o.getTranslationEngine=()=>e.getTranslationEngine?.()??e.getState().translationEngine,o.getActionDispatcher=()=>e.getActionDispatcher?.()??e.getState().actionDispatcher,we.log("전역 객체 window.G7Core 슬롯 API에 노출됨 (getSlotContext, getDynamicRenderer 등)")}function CA(o){o.identity={setLauncher:St.setLauncher.bind(St),handle:St.handle.bind(St),isIdentityRequired:St.isIdentityRequired.bind(St),hasLauncher:St.hasLauncher.bind(St),markDomainNoticeShown:St.markDomainNoticeShown.bind(St),redirectExternally:St.redirectExternally.bind(St),createDeferred:St.createDeferred.bind(St),resolveDeferred:St.resolveDeferred.bind(St),reset:St.reset.bind(St),redirectStashKey:Ad},we.log("전역 객체 window.G7Core 본인인증(IDV) API에 노출됨 (identity)")}function EA(o){if(typeof window>"u")return;nA(),window.G7Core||(window.G7Core={});const e=window.G7Core;rA(e),aA(e),iA(e),SA(e,o),sA(e,o),oA(e,o),lA(e),uA(e),dA(e),fA(e,o),hA(e),gA(e),pA(e),mA(e,o),yA(e,o),bA(e),vA(e),wA(e,o),CA(e),we.log("G7Core 전역 객체 초기화 완료")}function _A(){const o=window.G7Core;if(!o){we.warn("G7Core가 초기화되지 않았습니다.");return}const e=()=>Tr.getInstance(),a={isEnabled:()=>{try{return e().isEnabled()}catch{return!1}},trackRender:r=>{try{e().isEnabled()&&e().trackRender(r)}catch{}},trackIteration:(r,i,l,u,d)=>{try{e().isEnabled()&&e().trackIteration(r,{source:i,itemVar:l,indexVar:u,sourceLength:d})}catch{}},trackIfCondition:(r,i,l,u)=>{try{e().isEnabled()&&e().trackIfCondition(r,i,l)}catch{}},trackMount:(r,i)=>{try{e().isEnabled()&&e().trackMount(r,{...i,id:r})}catch{}},trackUnmount:r=>{try{e().isEnabled()&&e().trackUnmount(r)}catch{}},trackExpressionEval:r=>{try{e().isEnabled()&&e().trackExpressionEval(r)}catch{}},trackBindingEval:r=>{try{e().isEnabled()&&e().trackBindingEval(r)}catch{}},recordCacheHit:()=>{try{e().isEnabled()&&e().recordCacheHit()}catch{}},recordCacheMiss:()=>{try{e().isEnabled()&&e().recordCacheMiss()}catch{}},trackHandlerRegistration:(r,i,l,u)=>{try{e().isEnabled()&&e().trackHandlerRegistration(r,i,l,u)}catch{}},trackHandlerUnregistration:r=>{try{e().isEnabled()&&e().trackHandlerUnregistration(r)}catch{}},logAction:r=>{try{e().isEnabled()&&e().logAction(r)}catch{}},trackRequest:(r,i)=>{try{return e().isEnabled()?e().trackRequest(r,i):""}catch{return""}},completeRequest:(r,i,l)=>{try{e().isEnabled()&&e().completeRequest(r,i,l)}catch{}},failRequest:(r,i)=>{try{e().isEnabled()&&e().failRequest(r,i)}catch{}},trackDataSourceDefinition:r=>{try{e().isEnabled()&&e().trackDataSourceDefinition(r)}catch{}},trackDataSourceLoading:r=>{try{e().isEnabled()&&e().trackDataSourceLoading(r)}catch{}},trackDataSourceLoaded:(r,i,l)=>{try{e().isEnabled()&&e().trackDataSourceLoaded(r,i,l)}catch{}},trackDataSourceError:(r,i)=>{try{e().isEnabled()&&e().trackDataSourceError(r,i)}catch{}},trackForm:(r,i,l)=>{try{e().isEnabled()&&e().trackForm(r,i,l)}catch{}},untrackForm:r=>{try{e().isEnabled()&&e().untrackForm(r)}catch{}},startStateChange:(r,i,l,u)=>{try{return e().isEnabled()?e().startStateChange(r,i,l,u):""}catch{return""}},completeStateChange:r=>{try{e().isEnabled()&&e().completeStateChange(r)}catch{}},trackComponentRender:(r,i,l,u,d,f)=>{try{e().isEnabled()&&e().trackComponentRender(r,i,l,u,d,f)}catch{}},updateLocalState:r=>{try{e().isEnabled()&&e().updateLocalState(r)}catch{}},updateComputedState:r=>{try{e().isEnabled()&&e().updateComputedState(r)}catch{}},updateParentContext:r=>{try{e().isEnabled()&&e().updateParentContext(r)}catch{}},trackComponentStateSource:(r,i,l,u)=>{try{e().isEnabled()&&e().trackComponentStateSource(r,i,l,u)}catch{}},trackDynamicState:(r,i)=>{try{e().isEnabled()&&e().trackDynamicState(r,i)}catch{}},trackContextFlow:(r,i,l,u,d,f)=>{try{e().isEnabled()&&e().trackContextFlow(r,i,l,u,d,f)}catch{}},trackComponentStyle:(r,i,l,u)=>{try{e().isEnabled()&&e().trackComponentStyle(r,i,l,u)}catch{}},trackAuthEvent:(r,i,l,u)=>{try{e().isEnabled()&&e().trackAuthEvent(r,i,l,u)}catch{}},trackAuthHeader:(r,i,l,u,d)=>{try{e().isEnabled()&&e().trackAuthHeader(r,i,l,u,d)}catch{}},trackLog:(r,i,l)=>{try{e().isEnabled()&&e().trackLog(r,i,l)}catch{}},trackAction:r=>{try{e().isEnabled()&&e().trackAction?.(r)}catch{}},trackDataSourceUpdate:r=>{try{e().isEnabled()&&e().trackDataSourceUpdate?.(r)}catch{}},startSequenceExecution:r=>{try{return e().isEnabled()?e().startSequenceExecution(r):""}catch{return""}},captureSequenceActionBefore:(r,i,l,u,d)=>{try{e().isEnabled()&&e().captureSequenceActionBefore(r,i,l,u,d)}catch{}},captureSequenceActionAfter:(r,i,l,u,d,f)=>{try{e().isEnabled()&&e().captureSequenceActionAfter(r,i,l,u,d,f)}catch{}},endSequenceExecution:(r,i)=>{try{e().isEnabled()&&e().endSequenceExecution(r,i)}catch{}},registerStateCaptureForHandler:(r,i,l)=>{try{e().isEnabled()&&e().registerStateCaptureForHandler(r,i,l)}catch{}},detectStaleClosure:(r,i,l,u,d)=>{try{return e().isEnabled()?e().detectStaleClosure(r,i,l,u,d):[]}catch{return[]}},trackStaleClosureWarning:r=>{try{e().isEnabled()&&e().trackStaleClosureWarning(r)}catch{}},trackModalOpen:r=>{try{e().isEnabled()&&e().trackModalOpen(r)}catch{}},trackModalClose:(r,i)=>{try{e().isEnabled()&&e().trackModalClose(r,i)}catch{}},trackModalStateChange:r=>{try{e().isEnabled()&&e().trackModalStateChange(r)}catch{}},trackNestedContext:r=>{try{return e().isEnabled()?e().trackNestedContext(r):""}catch{return""}},trackNestedContextAccess:(r,i)=>{try{e().isEnabled()&&e().trackNestedContextAccess(r,i)}catch{}},trackComputedProperty:(r,i,l,u,d,f,g)=>{try{e().isEnabled()&&e().trackComputedProperty(r,i,l,u,d,f,g)}catch{}},trackComputedRecalc:(r,i,l,u,d,f,g)=>{try{e().isEnabled()&&e().trackComputedRecalc(r,i,l,u,d,f,g)}catch{}},setNamedActionDefinitions:r=>{try{e().isEnabled()&&e().setNamedActionDefinitions(r)}catch{}},trackNamedActionRef:r=>{try{e().isEnabled()&&e().trackNamedActionRef(r)}catch{}}};o.devTools=a,we.log("G7Core.devTools 인터페이스 초기화 완료")}let Co=null;function AA(){const o=window.G7Core;return o?.__devtools?Promise.resolve(o.__devtools):Co||(Co=new Promise((e,a)=>{const r=window.G7Config?.coreDevToolsAsset||"/build/core/devtools.min.js",i="g7-devtools-bundle",l=()=>{const f=window.G7Core?.__devtools;f?e(f):a(new Error("DevTools 번들 로드됨 — 그러나 __devtools 미노출"))};window.G7Core=window.G7Core||{},window.G7Core.__onDevToolsReady=l;const u=document.getElementById(i);if(u){u.addEventListener("load",l,{once:!0}),u.addEventListener("error",()=>a(new Error(`DevTools 번들 로드 실패: ${r}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=r,d.async=!1,d.addEventListener("load",l,{once:!0}),d.addEventListener("error",()=>{Co=null,a(new Error(`DevTools 번들 로드 실패: ${r}`))},{once:!0}),document.head.appendChild(d)}),Co)}function xA(){const o=window.G7Core;if(!o){we.warn("G7Core가 초기화되지 않았습니다.");return}const e=Tr.getInstance();if(e.initialize(),!e.isEnabled()){window.G7DevTools={isEnabled:()=>!1,enable:()=>{we.log("환경설정 > 고급 설정 > 디버그 모드를 켜세요")}},ed(),we.log("G7DevTools 비활성화됨 (디버그 모드 꺼짐)");return}TA(o,e)}async function TA(o,e){let a;try{a=await AA()}catch(g){we.warn("DevTools 번들 로드 실패 — 최소 API 로 폴백:",g),window.G7DevTools={isEnabled:()=>!1,enable:()=>{}},ed();return}const{DiagnosticEngine:r,getServerConnector:i,getStyleTracker:l}=a;_A(),ed();try{l().enable(),we.log("StyleTracker 활성화됨")}catch(g){we.warn("StyleTracker 활성화 실패:",g)}const u=new r,d=i(),f={isEnabled:()=>e.isEnabled(),state:{get:()=>e.getState(),getHistory:()=>e.getStateHistory(),watch:(g,m)=>e.watchState(g,m)},actions:{getHistory:()=>e.getActionHistory(),watch:g=>e.watchActions(g),getMetrics:()=>e.getActionMetrics()},binding:{evaluate:g=>{const m=o.getDataBindingEngine?.();if(!m){we.warn("DataBindingEngine이 없습니다.");return}return m.evaluateExpression(g,e.getState())},getCacheStats:()=>e.getCacheStats(),clearStats:()=>{e.resetCacheStats(),we.log("캐시 통계 초기화됨")}},diagnose:{analyze:g=>u.analyze(g),suggestFix:g=>u.suggestFix(g),getCommonIssues:()=>u.getCommonIssues(),getRulesByCategory:g=>u.getRulesByCategory(g)},server:{dumpState:g=>d.dumpState(g),sendLog:g=>d.sendLog(g),sendError:(g,m)=>d.sendError(g,m),isConnected:()=>d.isConnected(),testConnection:()=>d.testConnection()},config:{isDebugMode:()=>e.isEnabled(),setLogLevel:g=>e.setLogLevel(g),setMaxHistory:g=>e.setMaxHistory(g)},lifecycle:{getMountedComponents:()=>e.getLifecycleInfo().mountedComponents,getOrphanedListeners:()=>e.getLifecycleInfo().orphanedListeners},performance:{getRenderCount:()=>e.getPerformanceInfo().renderCounts,getBindingEvalCount:()=>e.getPerformanceInfo().bindingEvalCount,getMemoryWarnings:()=>e.getPerformanceInfo().memoryWarnings,startProfiling:()=>e.startProfiling(),stopProfiling:()=>e.stopProfiling()},network:{getActiveRequests:()=>e.getNetworkInfo().activeRequests,getRequestHistory:()=>e.getNetworkInfo().requestHistory,getPendingDataSources:()=>e.getNetworkInfo().pendingDataSources},conditional:{getIfConditions:()=>e.getConditionalInfo().ifConditions,getIterations:()=>e.getConditionalInfo().iterations},form:{getForms:()=>e.getFormInfo()},websocket:{getInfo:()=>e.getWebSocketInfo()}};window.G7DevTools=f,o.devtools=f,we.log("G7DevTools 전역 객체 초기화 완료 (window.G7DevTools)"),RA(a.DevToolsPanel)}function RA(o){if(document.getElementById("g7-devtools-root")){we.log("DevToolsPanel 이미 렌더링됨");return}const e=document.createElement("div");e.id="g7-devtools-root",document.body.appendChild(e);try{Qu.createRoot(e).render(We.createElement(o)),we.log("DevToolsPanel UI 렌더링 완료")}catch(a){we.error("DevToolsPanel UI 렌더링 실패:",a)}}const Ze=ct("TemplateEngine"),se={templateId:null,locale:"ko",isInitialized:!1,reactRoot:null,containerId:null,currentLayoutJson:null,currentDataContext:{},translationContext:{templateId:"",locale:"ko"},registry:null,bindingEngine:null,translationEngine:null,actionDispatcher:null,templateMetadata:null};let Eo=!1;const _o=[];async function kA(o){try{Ze.log("템플릿 메타데이터 로드 중...",o);const e=o.split("-").map(r=>r.split("_").map(i=>i.charAt(0).toUpperCase()+i.slice(1)).join("")).join(""),a=window[e];return a?.templateMetadata?(Ze.log("템플릿 메타데이터 로드 완료 (전역 변수)",a.templateMetadata),a.templateMetadata):(Ze.warn(`템플릿 번들에 메타데이터가 없습니다. 전역 변수: ${e}`),{identifier:o,locales:["ko","en"],name:{ko:o,en:o},description:{ko:"",en:""},version:"1.0.0",type:"admin"})}catch(e){return Ze.error("템플릿 메타데이터 로드 실패",e),{identifier:o,locales:["ko","en"],name:{ko:o,en:o},description:{ko:"",en:""},version:"1.0.0",type:"admin"}}}function fy(){const e=(typeof window<"u"?window.__templateApp:void 0)?.globalState?.appConfig?.supportedLocales,a=se.templateMetadata?.locales;return{$locale:se.locale,$locales:Array.isArray(e)&&e.length>0?e:a||["ko","en"],$templateLocales:a||["ko","en"],$templateId:se.templateId}}async function Ao(o){try{if(Ze.log("템플릿 엔진 초기화 시작",o),se.isInitialized)throw new Error("템플릿 엔진이 이미 초기화되었습니다. destroyTemplate()을 먼저 호출하세요.");if(!o.templateId)throw new j_;Eo=o.debug??!1,typeof window<"u"&&(window.G7Config||(window.G7Config={}),window.G7Config.debug=Eo),xA(),se.templateId=o.templateId,se.locale=o.locale||"ko",Ze.log("엔진 인스턴스 생성 중..."),se.registry=da.getInstance(),se.bindingEngine=new wn,se.translationEngine=Er.getInstance(),o.cacheVersion!==void 0&&o.cacheVersion>0&&se.translationEngine.setCacheVersion(o.cacheVersion),se.translationContext={templateId:o.templateId,locale:se.locale},se.actionDispatcher=new lo({},se.translationEngine,se.translationContext),tC(se.actionDispatcher),Ze.log("다국어 파일 로드 중...",o.templateId,se.locale);const e="en",a=[se.translationEngine.loadTranslations(o.templateId,se.locale).then(()=>{Ze.log(`다국어 파일 로드 완료: ${se.locale}`)}).catch(r=>{Ze.warn(`다국어 파일 로드 실패 (${se.locale}):`,r instanceof Error?r.message:r)})];se.locale!==e&&a.push(se.translationEngine.loadTranslations(o.templateId,e).then(()=>{Ze.log(`폴백 다국어 파일 로드 완료: ${e}`)}).catch(r=>{Ze.warn(`폴백 다국어 파일 로드 실패 (${e}):`,r instanceof Error?r.message:r)})),await Promise.allSettled(a),Ze.log("템플릿 메타데이터 로드 중...",o.templateId),se.templateMetadata=await kA(o.templateId),se.isInitialized=!0,Ze.log("템플릿 엔진 초기화 완료 (ComponentRegistry는 별도 로드)")}catch(e){throw Ze.error("템플릿 엔진 초기화 실패",e),se.isInitialized=!1,se.registry=null,se.bindingEngine=null,se.translationEngine=null,se.actionDispatcher=null,e}}let xo=null;function pf(){const o=window.G7Core;return o?.__LayoutEditorChrome?Promise.resolve(o.__LayoutEditorChrome):xo||(xo=new Promise((e,a)=>{const r=window.G7Config?.coreEditorAsset||"/build/core/layout-editor.min.js",i="g7-layout-editor-bundle",l=document.getElementById(i),u=()=>{const f=window.G7Core?.__LayoutEditorChrome;f?e(f):a(new Error("편집기 번들 로드됨 — 그러나 __LayoutEditorChrome 미노출"))};if(window.G7Core=window.G7Core||{},window.G7Core.__onChromeReady=u,l){l.addEventListener("load",u,{once:!0}),l.addEventListener("error",()=>a(new Error(`편집기 번들 로드 실패: ${r}`)),{once:!0});return}const d=document.createElement("script");d.id=i,d.src=r,d.async=!1,d.addEventListener("load",u,{once:!0}),d.addEventListener("error",()=>{xo=null,a(new Error(`편집기 번들 로드 실패: ${r}`))},{once:!0}),document.head.appendChild(d)}),xo)}async function cs(o){try{if(Ze.log("템플릿 렌더링 시작",o),!se.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다. initTemplateEngine()을 먼저 호출하세요.");if(!o.containerId)throw new Error("containerId는 필수입니다.");if(!o.layoutJson)throw new Error("layoutJson은 필수입니다.");const e=document.getElementById(o.containerId);if(!e)throw new Error(`컨테이너를 찾을 수 없습니다: #${o.containerId}`);const a=fy();if(se.containerId=o.containerId,se.currentLayoutJson=o.layoutJson,se.currentDataContext={...a,...o.dataContext||{}},se.translationContext=o.translationContext||{templateId:se.templateId||"",locale:se.locale},se.reactRoot||(Ze.log("React Root 생성"),se.reactRoot=Qu.createRoot(e)),typeof window<"u"){const l=sy(window.location.pathname);if(l){Ze.log("레이아웃 편집기 모드 진입",l);let u;try{u=await pf()}catch(f){Ze.error("레이아웃 편집기 번들 로드 실패",f),uf.render(o.containerId,{title:"레이아웃 편집기 로드 실패",message:f instanceof Error?f.message:"편집기 번들을 불러오지 못했습니다.",icon:"fas fa-triangle-exclamation",showStack:!1,showReloadButton:!0,debug:Eo});return}const d=We.createElement(u,{templateIdentifier:l.templateIdentifier,initialLocale:se.locale});se.reactRoot.render(We.createElement(uc,{translationEngine:se.translationEngine,translationContext:se.translationContext,children:We.createElement(Ld,{children:We.createElement(Jl,{children:We.createElement(rf,{children:d})})})}));return}}const r=o.layoutJson.components||[];if(r.length===0){Ze.warn("렌더링할 컴포넌트가 없습니다.");return}const i=o.layoutJson.modals||[];if(Ze.log("modals 배열:",i),Ze.log("modals 개수:",i.length),Ze.log("DynamicRenderer로 렌더링 시작"),se.reactRoot.render(We.createElement(uc,{translationEngine:se.translationEngine,translationContext:se.translationContext},We.createElement(Ld,null,We.createElement(Jl,null,We.createElement(rf,null,[...r.map((l,u)=>{const d=se.currentLayoutJson?.layout_name||"";return We.createElement(za,{key:d&&!l._fromBase?`${l.id}_${d}`:l.id,componentDef:l,dataContext:se.currentDataContext,translationContext:se.translationContext,registry:se.registry,bindingEngine:se.bindingEngine,translationEngine:se.translationEngine,actionDispatcher:se.actionDispatcher,isRootRenderer:u===0,layoutKey:d})}),We.createElement(Qp,{key:"__modal_parent_context_provider"},i.map(l=>{const u=se.currentDataContext._global?.modalStack||[],f=u.includes(l.id)||se.currentDataContext._global?.activeModal===l.id,g=u.indexOf(l.id),m=g>=0?50+g:50,b=window.__g7LayoutContextStack||[],S=b[b.length-1]?.dataContext,E=We.createElement(za,{key:`modal_${l.id}_renderer`,componentDef:{...l,props:{...l.props,isOpen:f,style:{...l.props?.style,zIndex:m},onClose:se.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},se.currentDataContext)}},dataContext:se.currentDataContext,translationContext:se.translationContext,registry:se.registry,bindingEngine:se.bindingEngine,translationEngine:se.translationEngine,actionDispatcher:se.actionDispatcher,parentDataContext:S});return l.data_sources&&l.data_sources.length>0?We.createElement(ry,{key:`modal_${l.id}`,isOpen:f,modalId:l.id,dataSources:l.data_sources,dataContext:se.currentDataContext,globalStateUpdater:se.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:se.bindingEngine,debug:Eo,children:E}):E}))]))))),Ze.log("템플릿 렌더링 완료"),_o.length>0){Ze.log(`대기 중인 데이터 업데이트 ${_o.length}건 적용`);const l=[..._o];_o.length=0;for(const{data:u,options:d}of l)us(u,d)}}catch(e){throw Ze.error("템플릿 렌더링 실패",e),e}}function us(o,e){try{if(Ze.log("템플릿 데이터 업데이트 시작",Object.keys(o)),!se.isInitialized)throw new Error("템플릿 엔진이 초기화되지 않았습니다.");if(!se.reactRoot||!se.currentLayoutJson){Ze.log("React 미준비 - 데이터 업데이트 큐잉:",Object.keys(o)),_o.push({data:o,options:e});return}const a=fy(),r={...se.currentDataContext._global||{},...o._global||{}},i=oC(se.currentDataContext._localInit,o._localInit);se.currentDataContext={...a,...se.currentDataContext,...o,_global:r,_localInit:i},r._local!==void 0&&(se.currentDataContext._local=r._local),r._computed!==void 0&&(se.currentDataContext._computed=r._computed);const l=se.currentLayoutJson.components||[],u=se.currentLayoutJson.modals||[];Ze.log("updateTemplateData - modals 배열:",u),Ze.log("updateTemplateData - modals 개수:",u.length),Ze.log("updateTemplateData - activeModal:",se.currentDataContext._global?.activeModal);const d=()=>{se.reactRoot.render(We.createElement(uc,{translationEngine:se.translationEngine,translationContext:se.translationContext},We.createElement(Ld,null,We.createElement(Jl,null,We.createElement(rf,null,[...l.map((f,g)=>{const m=se.currentLayoutJson?.layout_name||"";return We.createElement(za,{key:m&&!f._fromBase?`${f.id}_${m}`:f.id,componentDef:f,dataContext:se.currentDataContext,translationContext:se.translationContext,registry:se.registry,bindingEngine:se.bindingEngine,translationEngine:se.translationEngine,actionDispatcher:se.actionDispatcher,isRootRenderer:g===0,layoutKey:m})}),We.createElement(Qp,{key:"__modal_parent_context_provider_update"},u.map(f=>{const g=se.currentDataContext._global?.modalStack||[],b=g.includes(f.id)||se.currentDataContext._global?.activeModal===f.id,v=g.indexOf(f.id),S=v>=0?50+v:50,E=window.__g7LayoutContextStack||[],T=E[E.length-1]?.dataContext,L=We.createElement(za,{key:`modal_${f.id}_renderer`,componentDef:{...f,props:{...f.props,isOpen:b,style:{...f.props?.style,zIndex:S},onClose:se.actionDispatcher?.createHandler({type:"click",handler:"closeModal"},se.currentDataContext)}},dataContext:se.currentDataContext,translationContext:se.translationContext,registry:se.registry,bindingEngine:se.bindingEngine,translationEngine:se.translationEngine,actionDispatcher:se.actionDispatcher,parentDataContext:T});return f.data_sources&&f.data_sources.length>0?We.createElement(ry,{key:`modal_${f.id}`,isOpen:b,modalId:f.id,dataSources:f.data_sources,dataContext:se.currentDataContext,globalStateUpdater:se.actionDispatcher?.getGlobalStateUpdater(),bindingEngine:se.bindingEngine,debug:Eo,children:L}):L}))])))))};e?.sync?(Ze.log("재렌더링 시작 (sync mode - 즉시 렌더링)"),d()):(Ze.log("재렌더링 시작 (with startTransition)"),N.startTransition(()=>{d()})),Ze.log("템플릿 데이터 업데이트 완료")}catch(a){throw Ze.error("템플릿 데이터 업데이트 실패",a),a}}function yc(){try{Ze.log("템플릿 정리 시작"),se.reactRoot&&(Ze.log("React Root 언마운트"),se.reactRoot.unmount(),se.reactRoot=null),se.templateId=null,se.locale="ko",se.isInitialized=!1,se.containerId=null,se.currentLayoutJson=null,se.currentDataContext={},se.translationContext={templateId:"",locale:"ko"},se.registry=null,se.bindingEngine=null,se.translationEngine=null,se.actionDispatcher=null,se.templateMetadata=null,Ze.log("템플릿 정리 완료")}catch(o){throw Ze.error("템플릿 정리 실패",o),o}}function er(){return Object.freeze({...se})}function hy(){return se.actionDispatcher}const To={initTemplateEngine:Ao,renderTemplate:cs,updateTemplateData:us,destroyTemplate:yc,getState:er};typeof window<"u"&&(window.G7Core||(window.G7Core={}),window.G7Core.TemplateEngine=To,EA({getState:()=>({translationEngine:se.translationEngine,translationContext:se.translationContext,bindingEngine:se.bindingEngine,actionDispatcher:se.actionDispatcher,templateMetadata:se.templateMetadata}),transitionManager:Ia,responsiveManager:ui,webSocketManager:hc}),Ze.log("전역 객체 window.G7Core.TemplateEngine에 노출됨"));const ja=Object.freeze(Object.defineProperty({__proto__:null,DataSourceManager:va,LayoutLoader:nf,TemplateApp:So,TemplateEngine:To,default:To,destroyTemplate:yc,getActionDispatcher:hy,getState:er,initTemplateApp:gf,initTemplateEngine:Ao,loadLayoutEditorBundle:pf,renderTemplate:cs,updateTemplateData:us},Symbol.toStringTag,{value:"Module"}));Ft.DataSourceManager=va,Ft.LayoutLoader=nf,Ft.TemplateApp=So,Ft.TemplateEngine=To,Ft.default=To,Ft.destroyTemplate=yc,Ft.getActionDispatcher=hy,Ft.getState=er,Ft.initTemplateApp=gf,Ft.initTemplateEngine=Ao,Ft.loadLayoutEditorBundle=pf,Ft.renderTemplate=cs,Ft.updateTemplateData=us,Object.defineProperties(Ft,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})})(this.G7Core=this.G7Core||{}); //# sourceMappingURL=template-engine.min.js.map diff --git a/resources/js/core/TemplateApp.ts b/resources/js/core/TemplateApp.ts index 540434d1..34cb1ce8 100644 --- a/resources/js/core/TemplateApp.ts +++ b/resources/js/core/TemplateApp.ts @@ -1128,7 +1128,10 @@ export class TemplateApp { route: { ...(route.params || {}), path: route.path }, query: queryObject, _global: { ...this.globalState }, // 나중에 갱신됨 - _globalSetState: (updates: Partial) => this.setGlobalState(updates), // Form dataKey="_global.xxx" 지원 + // Form dataKey="_global.xxx" 지원. + // @since engine-v1.54.5: 함수형 업데이트도 그대로 위임한다 — 소비부(DynamicRenderer 의 + // _localInit 동기화)가 렌더 시점 스냅샷 대신 쓰기 시점 prev 를 base 로 쓰기 위함. + _globalSetState: (updates: Partial | ((prev: GlobalState) => GlobalState)) => this.setGlobalState(updates), _dataSourceErrors: Object.keys(dataSourceErrors).length > 0 ? dataSourceErrors : undefined, // initLocal 옵션으로 초기화할 로컬 상태 (DynamicRenderer에서 처리) _localInit: Object.keys(localInit).length > 0 ? localInit : undefined, diff --git a/resources/js/core/template-engine/ActionDispatcher.ts b/resources/js/core/template-engine/ActionDispatcher.ts index 2d944ac1..5cdcc768 100644 --- a/resources/js/core/template-engine/ActionDispatcher.ts +++ b/resources/js/core/template-engine/ActionDispatcher.ts @@ -29,9 +29,44 @@ import { evaluateConditionBranches } from './helpers/ConditionEvaluator'; import { triggerModalParentUpdate } from './ParentContextProvider'; import type { GlobalHeaderRule } from './LayoutLoader'; import { IdentityGuardInterceptor } from '../identity/IdentityGuardInterceptor'; +import { isAbortError, isNetworkFailure } from './networkResilience'; const logger = createLogger('ActionDispatcher'); +/** + * 액션 실패 시 화면에 띄울 문구를 결정합니다. + * + * 우선순위는 (1) 서버가 준 메시지 → (2) 네트워크 실패 안내 → (3) 내부 식별 문구다. + * + * 네트워크 실패(`TypeError: Failed to fetch`)와 요청 취소(AbortError)는 **응답 자체가 없어** + * 서버 메시지가 존재하지 않는다. 그때 내부 식별 문구(`Failed to execute action: apiCall`)를 + * 그대로 토스트에 띄우면 운영자는 무슨 일이 일어났는지 알 수 없고 다국어도 적용되지 않는다. + * + * @param originalError 원래 발생한 에러 + * @param handler 실패한 핸들러명 + * @param serverMessage 서버 응답이 준 메시지 (없으면 undefined) + * @param fallbackMessage 위 둘이 없을 때 쓸 문구 (기본: 내부 식별 문구). + * `ActionError` 가 이미 고유 메시지를 들고 있으면 그것을 넘겨 보존한다. + * @return string 화면에 띄울 문구 ($t: 구문이면 호출부가 번역한다) + * @since engine-v1.54.6 + */ +export function resolveActionFailureMessage( + originalError: unknown, + handler: string, + serverMessage?: string, + fallbackMessage?: string +): string { + if (serverMessage) { + return serverMessage; + } + + if (isNetworkFailure(originalError) || isAbortError(originalError)) { + return '$t:core.errors.network_request_failed'; + } + + return fallbackMessage || `Failed to execute action: ${handler}`; +} + /** * 프리뷰 모드에서 억제되는 핸들러 목록 * @@ -2607,8 +2642,23 @@ export class ActionDispatcher { const errorStatus = (actionError.originalError as any)?.status || apiResponse.status || 500; // 에러 컨텍스트 생성 (ErrorHandlingResolver와 호환) - // API 응답의 message를 우선 사용하고, 없으면 ActionError 메시지 사용 - const errorMessage = responseData.message || actionError.message; + // API 응답의 message를 우선 사용하고, 응답이 아예 없었던 네트워크 실패에는 + // 내부 식별 문구 대신 다국어 안내를 쓴다 (engine-v1.54.6) + let errorMessage = resolveActionFailureMessage( + actionError.originalError ?? error, + action.handler, + responseData.message, + actionError.message + ); + + // 상태에 실려 텍스트로 그대로 렌더되는 경로(`{{error.message}}` → `_global.*Error`)가 있으므로 + // 여기서 번역해 둔다. 키 문자열이 화면에 노출되면 안 된다. + if (this.translationEngine && this.translationContext && errorMessage.startsWith('$t:')) { + errorMessage = this.translationEngine.resolveTranslations( + errorMessage, + this.translationContext + ); + } const errorContextData: ErrorContext = { status: errorStatus, message: errorMessage, diff --git a/resources/js/core/template-engine/CHANGELOG.md b/resources/js/core/template-engine/CHANGELOG.md index 6cb1c3a2..43659375 100644 --- a/resources/js/core/template-engine/CHANGELOG.md +++ b/resources/js/core/template-engine/CHANGELOG.md @@ -5,6 +5,27 @@ > > 형식: [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) +## [engine-v1.54.6] - 2026-08-01 + +### Fixed + +#### 통신이 끊겼을 때 내부 식별 문구가 사용자에게 노출되던 문제 + +- `template-engine/ActionDispatcher.ts` — 액션 실패 문구 결정을 `resolveActionFailureMessage()` 로 분리하고, **응답 자체가 없었던 실패**(`TypeError: Failed to fetch` / `AbortError`)에는 다국어 안내(`$t:core.errors.network_request_failed`)를 쓴다. 종전에는 서버 메시지가 없으므로 내부 식별 문구인 `Failed to execute action: apiCall` 이 그대로 토스트에 떴다 — 운영자가 무슨 일이 일어났는지 알 수 없고 다국어도 적용되지 않았다. +- 우선순위는 (1) 서버 메시지 → (2) 네트워크 안내 → (3) 기존 문구다. `ActionError` 가 이미 들고 있던 고유 메시지(예: 미등록 핸들러 안내)는 그대로 보존한다. +- `{{error.message}}` 가 상태에 실려 텍스트로 렌더되는 경로가 있어 `$t:` 키를 디스패처에서 미리 번역한다(키 문자열 노출 방지). +- 코어 다국어 키 `core.errors.network_request_failed` 추가 (ko/en + ja 번들). + +## [engine-v1.54.5] - 2026-07-31 + +### Fixed + +#### 데이터소스 `initLocal` 동기화가 라우트 진입 시드를 되돌리던 문제 + +- `template-engine/DynamicRenderer.tsx` — `_localInit` 을 전역 `_local`(저장소 B)에 동기화할 때, 병합 base 를 렌더 시점 스냅샷(`dataContext._global._local`)이 아니라 **쓰기 시점의 canonical 상태**(함수형 업데이트의 `prev._local`)로 바꿨다. `setGlobalState` 는 `{ _local: X }` 를 얕게 펼쳐 저장소를 통째로 교체하는데, 이 effect 는 렌더 커밋 뒤에 실행되므로 그 사이에 `init_actions` 가 URL query 로 시드한 값이 스냅샷 기반 교체에 통째로 되돌아갔다. +- 증상: 목록 화면에서 필터를 적용하고 상세로 들어갔다가 브라우저 뒤로가기로 복귀하면 URL·목록·총건수는 필터가 걸린 상태인데 **필터 컨트롤만 기본값**으로 표시됐다. 그 상태에서 다른 조건을 바꿔 검색하면 기존 필터가 조용히 사라진다. `initLocal` 데이터소스의 응답 시점과 시드 시점의 경합이라 같은 화면에서도 재현이 들쭉날쭉했다. +- `TemplateApp.ts` — `dataContext._globalSetState` 가 함수형 업데이트를 그대로 위임하도록 시그니처를 넓혔다(`setGlobalState` 는 이미 함수형을 지원). +- `_merge: "replace"` 계약(기존 base 무시)과 `_local` 이외 전역 키 보존은 종전과 동일하다. ## [engine-v1.54.4] - 2026-07-31 ### Fixed diff --git a/resources/js/core/template-engine/DynamicRenderer.tsx b/resources/js/core/template-engine/DynamicRenderer.tsx index 0bd9fedf..40f8360d 100644 --- a/resources/js/core/template-engine/DynamicRenderer.tsx +++ b/resources/js/core/template-engine/DynamicRenderer.tsx @@ -1371,16 +1371,26 @@ const DynamicRenderer: React.FC = memo( // 전역 상태도 함께 업데이트 (컴포넌트 로컬 상태와 전역 상태 동기화) // G7Core.state.get()._local에서도 데이터를 조회할 수 있도록 함 if (dataContext._globalSetState) { - const currentGlobalLocal = (dataContext._global as any)?._local || {}; - let globalLocalUpdate: Record; - if (mergeStrategy === 'replace') { - globalLocalUpdate = { ...initDataWithoutMeta, hasChanges: false }; - } else if (mergeStrategy === 'deep') { - globalLocalUpdate = deepMergeState(currentGlobalLocal, { ...initDataWithoutMeta, hasChanges: false }); - } else { - globalLocalUpdate = { ...currentGlobalLocal, ...initDataWithoutMeta, hasChanges: false }; - } - dataContext._globalSetState({ _local: globalLocalUpdate }); + // @since engine-v1.54.5: 병합 base 는 렌더 시점 스냅샷(dataContext._global._local)이 아니라 + // **쓰기 시점의 canonical _local(prev._local)** 이어야 한다. + // + // setGlobalState 는 updates 를 얕게 펼치므로 `{ _local: X }` 는 저장소 B 를 통째로 교체한다. + // 이 effect 는 마운트 후(렌더 커밋 뒤)에 실행되는데, 그 사이에 init_actions 가 URL query 로 + // _local 을 시드해 두었다면(목록 화면의 필터·정렬 컨트롤) 렌더 시점 스냅샷을 base 로 쓴 교체가 + // 그 시드를 통째로 되돌린다 — 뒤로가기 복귀 시 URL·목록은 필터가 걸린 상태인데 필터 컨트롤만 + // 기본값으로 표시되는 결함(#492 D-20). 함수형 업데이트로 prev 를 읽어 그 역전을 제거한다. + dataContext._globalSetState((prev: Record) => { + const currentGlobalLocal = (prev as any)?._local || {}; + let globalLocalUpdate: Record; + if (mergeStrategy === 'replace') { + globalLocalUpdate = { ...initDataWithoutMeta, hasChanges: false }; + } else if (mergeStrategy === 'deep') { + globalLocalUpdate = deepMergeState(currentGlobalLocal, { ...initDataWithoutMeta, hasChanges: false }); + } else { + globalLocalUpdate = { ...currentGlobalLocal, ...initDataWithoutMeta, hasChanges: false }; + } + return { ...prev, _local: globalLocalUpdate }; + }); } // _localInit 데이터가 변경되면 DataBindingEngine 캐시 무효화 diff --git a/resources/js/core/template-engine/__tests__/DynamicRenderer.localInitGlobalSync.test.tsx b/resources/js/core/template-engine/__tests__/DynamicRenderer.localInitGlobalSync.test.tsx new file mode 100644 index 00000000..70c10426 --- /dev/null +++ b/resources/js/core/template-engine/__tests__/DynamicRenderer.localInitGlobalSync.test.tsx @@ -0,0 +1,173 @@ +/** + * _localInit → 전역 `_local` 동기화가 **쓰기 시점의 canonical 상태**를 base 로 병합하는지 검증 + * + * 회귀 배경 (#492 D-20) + * + * 목록 화면은 URL query 로 필터/정렬을 표현하고, 라우트 진입 시 `init_actions` 가 그 query 를 + * `_local` 에 시드해 필터 컨트롤을 복원한다. 그런데 `_localInit`(데이터소스 initLocal) 동기화가 + * 렌더 시점 스냅샷(`dataContext._global._local`)을 병합 base 로 쓰고, `setGlobalState` 는 + * `{ _local: X }` 를 얕게 펼쳐 저장소를 **통째로 교체**한다. 이 effect 는 렌더 커밋 뒤에 실행되므로 + * 시드가 그 사이에 일어나면 스냅샷 기반 교체가 시드를 되돌린다 — + * 상세 진입 후 뒤로가기로 목록에 복귀하면 URL·목록·총건수는 필터가 걸린 상태인데 + * 필터 라디오만 `전체` 로 표시되는 결함. + * + * 아래 테스트는 그 순서를 그대로 재현한다: 스냅샷(stale) ≠ canonical(seeded). + */ + +import React from 'react'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { render } from '@testing-library/react'; +import DynamicRenderer, { ComponentDefinition } from '../DynamicRenderer'; +import { ComponentRegistry } from '../ComponentRegistry'; +import { DataBindingEngine } from '../DataBindingEngine'; +import { TranslationEngine, TranslationContext } from '../TranslationEngine'; +import { ActionDispatcher } from '../ActionDispatcher'; +import { resetLocalInitTracking } from '../localInitSlot'; + +const TestDiv: React.FC> = (props) => { + const { children, ...rest } = props as { children?: React.ReactNode }; + return
)}>{children}
; +}; + +describe('_localInit 전역 동기화 — canonical base 병합', () => { + let registry: ComponentRegistry; + let bindingEngine: DataBindingEngine; + let translationEngine: TranslationEngine; + let actionDispatcher: ActionDispatcher; + let translationContext: TranslationContext; + + beforeEach(() => { + resetLocalInitTracking(); + registry = ComponentRegistry.getInstance(); + (registry as any).registry = { + Div: { component: TestDiv, metadata: { name: 'Div', type: 'basic' } }, + }; + bindingEngine = new DataBindingEngine(); + translationEngine = new TranslationEngine(); + actionDispatcher = new ActionDispatcher({ navigate: vi.fn() }); + translationContext = { templateId: 'test-template', locale: 'ko' }; + }); + + const componentDef: ComponentDefinition = { + id: 'list_root', + type: 'basic', + name: 'Div', + props: { 'data-testid': 'root' }, + children: [], + } as unknown as ComponentDefinition; + + /** + * TemplateApp.setGlobalState 와 동일한 계약을 갖는 테스트 대역. + * - 객체 업데이트: 얕은 펼침 (`{ _local: X }` 는 저장소를 통째로 교체) + * - 함수형 업데이트: `prev` 를 인자로 받아 반환값으로 교체 + */ + const createStore = (initial: Record) => { + let state = initial; + return { + get current() { + return state; + }, + setGlobalState: ( + updates: Record | ((prev: Record) => Record) + ) => { + state = typeof updates === 'function' ? updates(state) : { ...state, ...updates }; + }, + }; + }; + + it('init_actions 시드가 렌더 뒤 _localInit 동기화로 되돌아가지 않아야 한다', () => { + // canonical(저장소 B): init_actions 가 URL query 로 시드한 최신 값 + const store = createStore({ + _local: { + filter: { issueStatus: 'issuing', targetType: 'all' }, + sortBy: 'name_asc', + }, + }); + + // 렌더 시점 스냅샷: 시드 이전 값(레이아웃 기본값)이 담긴 stale 컨텍스트 + const staleGlobalSnapshot = { + _local: { + filter: { issueStatus: 'all', targetType: 'all' }, + sortBy: 'created_at_desc', + }, + }; + + render( + + ); + + // _localInit payload 는 반영되어야 한다 + expect(store.current._local.rows).toEqual([{ id: 1 }]); + // 그리고 시드된 필터/정렬은 살아 있어야 한다 (수정 전에는 'all' / 'created_at_desc' 로 역전) + expect(store.current._local.filter.issueStatus).toBe('issuing'); + expect(store.current._local.sortBy).toBe('name_asc'); + }); + + it('_merge:"replace" 는 기존 계약대로 base 를 무시하고 payload 로 교체한다', () => { + const store = createStore({ + _local: { filter: { issueStatus: 'issuing' }, stale: 'keep-out' }, + }); + + render( + + ); + + expect(store.current._local.form).toEqual({ name: 'A' }); + expect(store.current._local.stale).toBeUndefined(); + expect(store.current._local.filter).toBeUndefined(); + }); + + it('_global 이외의 전역 키는 동기화로 소실되지 않아야 한다', () => { + const store = createStore({ + _local: { filter: { issueStatus: 'issuing' } }, + cartKey: 'ck-1', + toasts: [], + }); + + render( + + ); + + expect(store.current.cartKey).toBe('ck-1'); + expect(store.current.toasts).toEqual([]); + }); +}); diff --git a/resources/js/core/template-engine/__tests__/actionDispatcherNetworkFailureMessage.test.ts b/resources/js/core/template-engine/__tests__/actionDispatcherNetworkFailureMessage.test.ts new file mode 100644 index 00000000..7e75bb56 --- /dev/null +++ b/resources/js/core/template-engine/__tests__/actionDispatcherNetworkFailureMessage.test.ts @@ -0,0 +1,69 @@ +import { describe, it, expect } from 'vitest'; +import { resolveActionFailureMessage } from '../ActionDispatcher'; + +/** + * 액션 실패 문구가 **운영자에게 의미 있는 말**인지 고정한다. + * + * 네트워크 실패(`TypeError: Failed to fetch`)는 응답 자체가 없으므로 서버 메시지가 없다. + * 그때 내부 식별자(`Failed to execute action: apiCall`)를 그대로 토스트에 띄우면 + * 운영자는 무슨 일이 일어났는지 알 수 없고 다국어도 적용되지 않는다. + * + * 확장 업데이트 모달에서 통신이 끊겼을 때 실제로 이 문구가 노출되는 것을 브라우저 실측으로 + * 확인했다(#492 검색 인덱스 실측 세션). + * + * @since engine-v1.54.6 + */ +describe('resolveActionFailureMessage', () => { + it('네트워크 실패에는 다국어 키를 돌려준다', () => { + const message = resolveActionFailureMessage( + new TypeError('Failed to fetch'), + 'apiCall', + undefined + ); + + expect(message).toBe('$t:core.errors.network_request_failed'); + }); + + it('요청 취소도 네트워크 계열로 안내한다', () => { + const abort = new Error('The user aborted a request.'); + abort.name = 'AbortError'; + + expect(resolveActionFailureMessage(abort, 'apiCall', undefined)).toBe( + '$t:core.errors.network_request_failed' + ); + }); + + it('서버가 메시지를 주면 그대로 쓴다', () => { + expect( + resolveActionFailureMessage(new Error('boom'), 'apiCall', '유효하지 않은 레이아웃 전략입니다.') + ).toBe('유효하지 않은 레이아웃 전략입니다.'); + }); + + it('서버 메시지도 네트워크 실패도 아니면 기존 식별 문구를 유지한다', () => { + expect(resolveActionFailureMessage(new Error('boom'), 'setState', undefined)).toBe( + 'Failed to execute action: setState' + ); + }); + + it('ActionError 가 이미 들고 있던 고유 메시지는 덮어쓰지 않는다', () => { + expect( + resolveActionFailureMessage( + new Error('boom'), + 'ecommerce.initPreferredCurrency', + undefined, + 'Unknown action handler: ecommerce.initPreferredCurrency' + ) + ).toBe('Unknown action handler: ecommerce.initPreferredCurrency'); + }); + + it('네트워크 실패는 고유 메시지보다 우선한다 (내부 문구가 사용자에게 보이지 않도록)', () => { + expect( + resolveActionFailureMessage( + new TypeError('Failed to fetch'), + 'apiCall', + undefined, + 'Failed to execute action: apiCall' + ) + ).toBe('$t:core.errors.network_request_failed'); + }); +}); diff --git a/resources/js/core/template-engine/__tests__/troubleshooting-state-global.test.ts b/resources/js/core/template-engine/__tests__/troubleshooting-state-global.test.ts index 85386826..5019a7b8 100644 --- a/resources/js/core/template-engine/__tests__/troubleshooting-state-global.test.ts +++ b/resources/js/core/template-engine/__tests__/troubleshooting-state-global.test.ts @@ -721,6 +721,55 @@ describe('트러블슈팅 회귀 테스트 - 데이터소스 업데이트 및 // DynamicRenderer 의 trackingKey 계산과 동일한 형태 — 값·타임스탬프 둘 다 달라 재적용된다 expect(after).not.toBe(before); + describe('[사례 8] initLocal 동기화가 init_actions 의 query 시드를 되돌림 (engine-v1.54.5)', () => { + /** + * 증상: 목록에서 필터 적용 → 상세 진입 → 뒤로가기 복귀 시 URL·목록은 필터 상태인데 + * 필터 컨트롤만 기본값으로 표시된다 (#492 D-20). + * 원인: _localInit → 전역 _local 동기화가 **렌더 시점 스냅샷**을 병합 base 로 썼고, + * setGlobalState 는 `{ _local: X }` 를 얕게 펼쳐 저장소를 통째로 교체한다. + * effect 는 렌더 커밋 뒤에 실행되므로 그 사이의 init_actions query 시드가 되돌아갔다. + * 해결: 함수형 업데이트로 **쓰기 시점 prev._local** 을 base 로 병합. + * + * 렌더 통합 검증은 DynamicRenderer.localInitGlobalSync.test.tsx 가 담당한다. + */ + const applySync = ( + store: Record, + snapshotBase: Record, + initData: Record, + useCanonicalBase: boolean + ) => { + const base = useCanonicalBase ? store._local || {} : snapshotBase; + // setGlobalState 의 얕은 펼침 계약 — `_local` 은 통째로 교체된다 + return { ...store, _local: { ...base, ...initData, hasChanges: false } }; + }; + + it('스냅샷 base 는 시드를 되돌린다 (수정 전 동작)', () => { + const store = { _local: { filter: { issueStatus: 'issuing' }, sortBy: 'name_asc' } }; + const staleSnapshot = { filter: { issueStatus: 'all' }, sortBy: 'created_at_desc' }; + + const next = applySync(store, staleSnapshot, { rows: [1] }, false); + + expect(next._local.filter.issueStatus).toBe('all'); + expect(next._local.sortBy).toBe('created_at_desc'); + }); + + it('canonical base 는 시드를 보존한다 (수정 후 동작)', () => { + const store = { _local: { filter: { issueStatus: 'issuing' }, sortBy: 'name_asc' } }; + const staleSnapshot = { filter: { issueStatus: 'all' }, sortBy: 'created_at_desc' }; + + const next = applySync(store, staleSnapshot, { rows: [1] }, true); + + expect(next._local.filter.issueStatus).toBe('issuing'); + expect(next._local.sortBy).toBe('name_asc'); + expect(next._local.rows).toEqual([1]); + }); + + it('_local 이외의 전역 키는 동기화로 소실되지 않아야 함', () => { + const store = { _local: { filter: { issueStatus: 'issuing' } }, cartKey: 'ck-1' }; + + const next = applySync(store, {}, { rows: [] }, true); + + expect(next.cartKey).toBe('ck-1'); }); }); }); diff --git a/resources/views/dev-dashboard.blade.php b/resources/views/dev-dashboard.blade.php index 3a58b2f6..8e3d56c1 100644 --- a/resources/views/dev-dashboard.blade.php +++ b/resources/views/dev-dashboard.blade.php @@ -81,11 +81,16 @@ if (isset($_GET['ajax_action'])) { $cleanOutput = $exitCode === 0 ? '명령이 성공적으로 실행되었습니다.' : '명령 실행 중 오류가 발생했습니다.'; } - // 성공/실패 여부 표시 + // 종료 코드 표시 + // + // 비-0 종료가 곧 "명령 실패" 는 아니다. 점검 계열 커맨드(예: search:index)는 + // 점검 자체는 정상 수행하고 "이상을 발견했다" 는 뜻으로 비-0 을 돌려준다. + // 이것을 "실행 실패" 로 적으면 운영자가 도구가 고장난 것으로 읽고 + // 정작 발견된 이상(색인 누락 등)을 놓친다. if ($exitCode === 0) { $cleanOutput = "✅ 완료\n\n" . $cleanOutput; } else { - $cleanOutput = "❌ 실패 (exitCode: {$exitCode})\n\n" . $cleanOutput; + $cleanOutput = "⚠️ 종료 코드 {$exitCode} — 아래 출력을 확인하세요\n\n" . $cleanOutput; } echo json_encode([ @@ -860,6 +865,14 @@ if (isset($_GET['ajax_action'])) { 사이트맵 생성 (seo:generate-sitemap) + + @@ -1943,7 +1956,7 @@ if (isset($_GET['ajax_action'])) { : 'mt-2 p-2 bg-red-900/30 border border-red-700/50 rounded text-red-300 text-[11px]'; finalEl.textContent = result.success ? `🎉 ${command} 실행 완료!` - : `❌ ${command} 실행 실패`; + : `⚠️ ${command} — 종료 코드 ${result.code ?? 1} (출력 확인 필요)`; content.appendChild(finalEl); } diff --git a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md index 9c5b445d..94bc8101 100644 --- a/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md +++ b/templates/_bundled/sirsoft-admin_basic/CHANGELOG.md @@ -47,6 +47,11 @@ - 오류 안내가 뜨기는 하지만 내용이 비어 있던 문제를 수정했습니다. 서버가 알려 준 실패 사유가 그대로 표시됩니다. - 모듈·플러그인·템플릿·언어팩 목록과 일정 관리 화면의 아이콘이 의도한 크기보다 크거나 작게 보이던 문제를 수정했습니다. - 다크 모드에서 일부 버튼·알림 점·아이콘·탭 뱃지의 색 대비가 낮아 잘 보이지 않던 문제를 수정했습니다. +- 목록의 열 제목을 눌렀을 때 지금 보고 있는 페이지의 줄만 순서가 바뀌면서 전체가 정렬된 것처럼 보이던 문제를 수정했습니다. 페이지를 나눠 불러오는 목록에서 열 제목 정렬을 지원하지 않는 화면은 이제 정렬 표시와 클릭 효과를 보이지 않으므로, 정렬 결과를 잘못 읽을 일이 없습니다. +- 목록의 열 제목을 눌러 정렬할 때, 이미 다른 열로 정렬 중이었다면 화살표는 새로 누른 열에 표시되면서 실제로는 이전 열 기준으로 정렬되던 문제를 수정했습니다. 표시와 실제 정렬 기준이 어긋나 결과를 신뢰할 수 없었으며, 이제 누른 열 기준으로 한 번에 정렬됩니다. +- 사용자 관리 목록에서 뒤쪽 페이지를 보던 중 정렬을 바꾸면 이전 페이지 번호에 그대로 머물러 엉뚱한 구간이 보이던 문제를 수정했습니다. 이제 정렬을 바꾸면 첫 페이지부터 보여줍니다. +- 사용자 관리 목록의 "페이지당 개수"가 10개로 표시되는데 실제로는 15개가 나오던 문제를 수정했습니다. 일정 관리 목록에서 20개 목록을 10개로 잘못 안내하던 것도 함께 바로잡았습니다. +- 다크 모드에서 일부 버튼·알림 점·아이콘의 색 대비가 낮아 잘 보이지 않던 문제를 수정했습니다. - 템플릿 목록 화면의 탭 영역이 본문 여백과 어긋나 가로로 밀려 보일 수 있던 문제를 수정했습니다. - 메일 템플릿·알림 정의 목록에서 각 행이 같은 식별자를 공유해 화면 읽기 도구가 항목을 구분하지 못하던 문제를 수정했습니다. - 관리자 목록에서 검색·필터·정렬을 바꿀 때 표가 다시 그려지는 동안 로딩 표시가 나오지 않던 문제를 수정했습니다. 응답이 늦으면 아무 반응이 없는 것처럼 보여 같은 조작을 반복하게 되던 상황이 사라집니다. diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-list-pagination-binding.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-list-pagination-binding.test.tsx new file mode 100644 index 00000000..0176236f --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-list-pagination-binding.test.tsx @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest'; +import moduleList from '../../layouts/admin_module_list.json'; +import pluginList from '../../layouts/admin_plugin_list.json'; + +/** + * 모듈/플러그인 목록의 페이지네이션 바인딩이 **응답의 실제 경로**를 보는지 고정한다. + * + * `/api/admin/{modules,plugins}` 의 응답 봉투는 + * `data: { data: [...], pagination: { total, current_page, last_page, per_page }, ... }` 다. + * `data.total` / `data.last_page` 를 읽으면 항상 `undefined` 라서 + * - 총건수가 "총 0개 중 1-0개 표시" 로 나오고 + * - `serverTotalPages` 가 1 로 고정돼 2페이지 이후 확장이 화면에서 사라진다. + * + * 확장이 12개(기본 per_page)를 넘는 사이트에서만 드러나므로 데이터가 적을 때는 + * 눈으로 확인되지 않는다. 그래서 경로를 정적으로 고정한다. + */ + +const WRONG_PATHS = ['?.data?.total', '?.data?.last_page', '?.data?.current_page', '?.data?.per_page']; + +describe('확장 목록 — 페이지네이션 바인딩 경로', () => { + const cases = [ + { name: '모듈', layout: moduleList, source: 'modules' }, + { name: '플러그인', layout: pluginList, source: 'plugins' }, + ]; + + it.each(cases)('$name 목록은 pagination 하위 경로를 읽는다', ({ layout, source }) => { + const json = JSON.stringify(layout); + + WRONG_PATHS.forEach((suffix) => { + expect( + json.includes(`${source}${suffix}`), + `${source}${suffix} 는 응답에 없는 경로입니다 — ${source}?.data?.pagination${suffix.replace('?.data', '')} 를 써야 합니다`, + ).toBe(false); + }); + + expect(json).toContain(`${source}?.data?.pagination?.total`); + expect(json).toContain(`${source}?.data?.pagination?.last_page`); + expect(json).toContain(`${source}?.data?.pagination?.current_page`); + }); + + it.each(cases)('$name 목록은 목록 배열 경로는 그대로 유지한다', ({ layout, source }) => { + expect(JSON.stringify(layout)).toContain(`${source}?.data?.data ?? []`); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-update-rebuild-optin.test.tsx b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-update-rebuild-optin.test.tsx new file mode 100644 index 00000000..4bc24792 --- /dev/null +++ b/templates/_bundled/sirsoft-admin_basic/__tests__/layouts/admin-extension-update-rebuild-optin.test.tsx @@ -0,0 +1,75 @@ +import { describe, it, expect } from 'vitest'; +import moduleList from '../../layouts/admin_module_list.json'; +import pluginList from '../../layouts/admin_plugin_list.json'; +import moduleUpdateModal from '../../layouts/partials/admin_module_list/_modal_update.json'; +import pluginUpdateModal from '../../layouts/partials/admin_plugin_list/_modal_update.json'; + +/** + * 확장 업데이트 모달의 「검색 인덱스 재생성」 체크가 **모달을 열 때마다 해제**되는지 고정한다. + * + * 재생성은 인덱스 잠금·전체 재색인을 유발하므로 운영자가 그 모달에서 직접 선택했을 때만 + * 수행되어야 한다. 체크 상태를 전역에 남겨 두면 한 번 체크한 운영자가 다음 확장을 + * 업데이트할 때 아무것도 누르지 않았는데 재생성이 다시 수행된다 (브라우저 실측으로 확인). + * + * 모달을 여는 setState 시드에 `RebuildSearchIndex: false` 가 없으면 여기서 red 가 난다. + */ + +type Json = Record; + +/** 레이아웃 트리를 훑어 조건을 만족하는 setState params 를 모은다. */ +function collectSetStateParams(node: unknown, out: Json[] = []): Json[] { + if (Array.isArray(node)) { + node.forEach((child) => collectSetStateParams(child, out)); + return out; + } + if (node && typeof node === 'object') { + const obj = node as Json; + if (obj.handler === 'setState' && obj.params && typeof obj.params === 'object') { + out.push(obj.params as Json); + } + Object.values(obj).forEach((value) => collectSetStateParams(value, out)); + } + return out; +} + +describe('확장 업데이트 모달 — 검색 인덱스 재생성 옵트인', () => { + const cases = [ + { name: '모듈', layout: moduleList, modal: moduleUpdateModal, strategyKey: 'moduleLayoutStrategy', rebuildKey: 'moduleRebuildSearchIndex' }, + { name: '플러그인', layout: pluginList, modal: pluginUpdateModal, strategyKey: 'pluginLayoutStrategy', rebuildKey: 'pluginRebuildSearchIndex' }, + ]; + + it.each(cases)('$name 목록: 업데이트 모달을 여는 시드가 체크를 해제한다', ({ layout, strategyKey, rebuildKey }) => { + const seeds = collectSetStateParams(layout).filter( + (params) => params[strategyKey] === 'overwrite' && 'selectedModule' in params === ('selectedModule' in params), + ); + const modalSeeds = seeds.filter((params) => params[strategyKey] === 'overwrite'); + + expect(modalSeeds.length).toBeGreaterThan(0); + modalSeeds.forEach((params) => { + expect(params[rebuildKey], `${rebuildKey} 가 모달 진입 시드에서 해제되지 않습니다`).toBe(false); + }); + }); + + it.each(cases)('$name 모달: 업데이트 성공 후 상태를 되돌릴 때도 체크를 해제한다', ({ modal, strategyKey, rebuildKey }) => { + const resets = collectSetStateParams(modal).filter( + (params) => params[strategyKey] === 'overwrite' && params.selectedModule === null, + ); + + // 모듈 모달은 selectedModule, 플러그인 모달은 selectedPlugin 을 비운다 + const resetParams = resets.length + ? resets + : collectSetStateParams(modal).filter( + (params) => params[strategyKey] === 'overwrite' && params.selectedPlugin === null, + ); + + expect(resetParams.length).toBeGreaterThan(0); + resetParams.forEach((params) => { + expect(params[rebuildKey], `${rebuildKey} 가 제출 후 초기화되지 않습니다`).toBe(false); + }); + }); + + it.each(cases)('$name 모달: 체크박스는 전역 상태 true 일 때만 체크된다', ({ modal, rebuildKey }) => { + const json = JSON.stringify(modal); + expect(json).toContain(`{{_global.${rebuildKey} === true}}`); + }); +}); diff --git a/templates/_bundled/sirsoft-admin_basic/dist/css/components.css b/templates/_bundled/sirsoft-admin_basic/dist/css/components.css index 38d052a8..26d39150 100644 --- a/templates/_bundled/sirsoft-admin_basic/dist/css/components.css +++ b/templates/_bundled/sirsoft-admin_basic/dist/css/components.css @@ -1 +1 @@ -@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-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-pan-x:initial;--tw-pan-y:initial;--tw-pinch-zoom:initial;--tw-scroll-snap-strictness:proximity;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--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-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction: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-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Pretendard", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-yellow-950:oklch(28.6% .066 53.813);--color-lime-50:oklch(98.6% .031 120.757);--color-lime-100:oklch(96.7% .067 122.328);--color-lime-200:oklch(93.8% .127 124.321);--color-lime-300:oklch(89.7% .196 126.665);--color-lime-400:oklch(84.1% .238 128.85);--color-lime-500:oklch(76.8% .233 130.85);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-700:oklch(53.2% .157 131.589);--color-lime-800:oklch(45.3% .124 130.933);--color-lime-900:oklch(40.5% .101 131.063);--color-lime-950:oklch(27.4% .072 132.109);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-emerald-950:oklch(26.2% .051 172.552);--color-teal-50:oklch(98.4% .014 180.72);--color-teal-100:oklch(95.3% .051 180.801);--color-teal-200:oklch(91% .096 180.426);--color-teal-300:oklch(85.5% .138 181.071);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-teal-800:oklch(43.7% .078 188.216);--color-teal-900:oklch(38.6% .063 188.416);--color-teal-950:oklch(27.7% .046 192.524);--color-cyan-50:oklch(98.4% .019 200.873);--color-cyan-100:oklch(95.6% .045 203.388);--color-cyan-200:oklch(91.7% .08 205.041);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-800:oklch(45% .085 224.283);--color-cyan-900:oklch(39.8% .07 227.392);--color-cyan-950:oklch(30.2% .056 229.695);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-800:oklch(44.3% .11 240.79);--color-sky-900:oklch(39.1% .09 240.876);--color-sky-950:oklch(29.3% .066 243.157);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-indigo-950:oklch(25.7% .09 281.288);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-violet-950:oklch(28.3% .141 291.089);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-purple-950:oklch(29.1% .149 302.717);--color-fuchsia-50:oklch(97.7% .017 320.058);--color-fuchsia-100:oklch(95.2% .037 318.852);--color-fuchsia-200:oklch(90.3% .076 319.62);--color-fuchsia-300:oklch(83.3% .145 321.434);--color-fuchsia-400:oklch(74% .238 322.16);--color-fuchsia-500:oklch(66.7% .295 322.15);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-fuchsia-700:oklch(51.8% .253 323.949);--color-fuchsia-800:oklch(45.2% .211 324.591);--color-fuchsia-900:oklch(40.1% .17 325.612);--color-fuchsia-950:oklch(29.3% .136 325.661);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-100:oklch(94.8% .028 342.258);--color-pink-200:oklch(89.9% .061 343.231);--color-pink-300:oklch(82.3% .12 346.018);--color-pink-400:oklch(71.8% .202 349.761);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-600:oklch(59.2% .249 .584);--color-pink-700:oklch(52.5% .223 3.958);--color-pink-800:oklch(45.9% .187 3.815);--color-pink-900:oklch(40.8% .153 2.432);--color-pink-950:oklch(28.4% .109 3.907);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-rose-950:oklch(27.1% .105 12.094);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-zinc-50:oklch(98.5% 0 none);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-neutral-50:oklch(98.5% 0 none);--color-neutral-100:oklch(97% 0 none);--color-neutral-200:oklch(92.2% 0 none);--color-neutral-300:oklch(87% 0 none);--color-neutral-400:oklch(70.8% 0 none);--color-neutral-500:oklch(55.6% 0 none);--color-neutral-600:oklch(43.9% 0 none);--color-neutral-700:oklch(37.1% 0 none);--color-neutral-800:oklch(26.9% 0 none);--color-neutral-900:oklch(20.5% 0 none);--color-neutral-950:oklch(14.5% 0 none);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-stone-950:oklch(14.7% .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--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: 1.5 ;--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: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--aspect-video:16 / 9;--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,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{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:where(:not(iframe)){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}body{color:var(--color-gray-900);font-size:.875rem;line-height:1.5}.dark :is(body){color:var(--color-gray-100)}}@layer components{.admin_layout_root{background-color:var(--color-gray-50);height:100vh;display:flex;overflow:hidden}.dark .admin_layout_root{background-color:var(--color-gray-900)}.mobile_overlay{z-index:40;background-color:#00000080;position:fixed;inset:0}.left_sidebar_area{z-index:50;width:calc(var(--spacing) * 64);background-color:var(--color-white);inset-block:0;flex-direction:column;display:flex;position:relative;left:0}.dark .left_sidebar_area{background-color:var(--color-gray-800)}.left_sidebar_area{transition:width .3s}.left_sidebar_area_collapsed{width:0;overflow:hidden}.left_sidebar_area_portable{z-index:50;width:calc(var(--spacing) * 64);background-color:var(--color-white);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;flex-direction:column;transition-duration:.3s;display:flex;position:fixed;left:0}.dark .left_sidebar_area_portable{background-color:var(--color-gray-800)}.left_sidebar_area_portable{top:4rem;bottom:0;transform:translate(-100%)}.left_sidebar_area_portable_open{transform:translate(0)}.sidebar_header{height:calc(var(--spacing) * 16);padding-inline:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.sidebar_logo{height:calc(var(--spacing) * 10);align-items:center;gap:calc(var(--spacing) * 2);flex:1;min-width:0;display:flex}.logo_image{max-height:calc(var(--spacing) * 10);object-fit:contain;width:auto}.admin_sidebar_menu{padding-top:calc(var(--spacing) * 4);box-shadow:inset 3px 2px 2px var(--tw-shadow-color,#0000000a);flex:1;overflow-y:auto}.admin_sidebar_menu_portable{padding-top:calc(var(--spacing) * 4);flex:1;overflow-y:auto}.admin_sidebar_empty{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 8);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-400)}.dark .admin_sidebar_empty{color:var(--color-gray-500)}.admin_sidebar_empty_icon{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));color:var(--color-gray-300)}.dark .admin_sidebar_empty_icon{color:var(--color-gray-600)}.sidebar_footer{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100);padding:calc(var(--spacing) * 3);flex-shrink:0}.dark .sidebar_footer{border-color:var(--color-gray-700)}.sidebar_view_site_link{height:calc(var(--spacing) * 9);align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-600);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));text-decoration-line:none;display:flex}@media(hover:hover){.sidebar_view_site_link:hover{background-color:var(--color-slate-100);color:var(--color-slate-900)}}.dark .sidebar_view_site_link{color:var(--color-slate-300)}@media(hover:hover){.dark .sidebar_view_site_link:hover{background-color:var(--color-slate-700);color:var(--color-white)}}.sidebar_view_site_icon{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));flex-shrink:0}.sidebar_view_site_label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.right_content_wrapper{flex-direction:column;flex:1;min-width:0;display:flex}.right_content_area{flex:1;min-width:0;min-height:0;overflow:auto}.main_header{z-index:50;height:calc(var(--spacing) * 16);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 8);flex-shrink:0;justify-content:space-between;align-items:center;display:flex;position:relative}.dark .main_header{background-color:var(--color-gray-800)}.main_header{box-shadow:0 1px 3px #00000014}.header_left{flex:1;align-items:center;display:flex}.header_right{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.menu_toggle_btn{margin-left:calc(var(--spacing) * -2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);color:var(--color-slate-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.menu_toggle_btn:hover{background-color:var(--color-slate-100);color:var(--color-slate-900)}}.dark .menu_toggle_btn{color:var(--color-slate-400)}@media(hover:hover){.dark .menu_toggle_btn:hover{background-color:var(--color-slate-700);color:var(--color-white)}}.menu_toggle_btn_icon{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5)}.user_avatar_section{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 3);display:flex;position:relative}.user_avatar{height:calc(var(--spacing) * 9);width:calc(var(--spacing) * 9);background-color:var(--color-gray-200);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex;overflow:hidden}.dark .user_avatar{background-color:var(--color-gray-600)}.user_avatar_img{object-fit:cover;width:100%;height:100%}.user_info{display:none}@media(min-width:48rem){.user_info{display:block}}.mobile_header_portable{z-index:50;height:calc(var(--spacing) * 16);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex;position:relative}.dark .mobile_header_portable{background-color:var(--color-gray-800)}.mobile_header_portable{box-shadow:0 1px 3px #00000014}.mobile_header_left{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.mobile_header_right{align-items:center;gap:calc(var(--spacing) * 2);flex-shrink:0;display:flex}.mobile_logo{align-items:center;gap:calc(var(--spacing) * 2);flex:1;min-width:0;display:flex}.mobile_logo_image{height:calc(var(--spacing) * 8);object-fit:contain;width:auto;max-width:100%}.main_content{min-height:fit-content}.layout_warnings{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2)}.text-primary{color:var(--color-gray-900)}.dark .text-primary{color:var(--color-white)}.text-secondary{color:var(--color-gray-600)}.dark .text-secondary{color:var(--color-gray-400)}.text-muted,.dark .text-muted{color:var(--color-gray-500)}.text-tertiary{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .text-tertiary{color:var(--color-gray-400)}.text-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .text-label{color:var(--color-gray-400)}.text-label-subtle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .text-label-subtle{color:var(--color-gray-400)}.text-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .text-heading{color:var(--color-white)}.section-heading{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-heading{color:var(--color-white)}.section-title{margin-bottom:var(--spacing);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-title{color:var(--color-white)}.section-description{margin-bottom:calc(var(--spacing) * 6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .section-description{color:var(--color-gray-400)}.card-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .card-title{color:var(--color-white)}.card-title-aside{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);display:flex}.card-description{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .card-description{color:var(--color-gray-400)}.admin-card>.card-title{margin-bottom:calc(var(--spacing) * 4)}.admin-card>.card-title:has(+.card-description){margin-bottom:0}.admin-card>.card-description{margin-bottom:calc(var(--spacing) * 4)}.card-header{align-items:center;gap:calc(var(--spacing) * 2);--tw-leading:1;margin-bottom:0;line-height:1;display:flex}.card-header>*{--tw-leading:1;line-height:1}.text-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}.dark .text-body{color:var(--color-gray-300)}.icon-muted{color:var(--color-gray-400)}.dark .icon-muted{color:var(--color-gray-500)}.bg-success{background-color:var(--color-green-100)}.dark .bg-success{background-color:var(--color-green-900)}.bg-success-light{background-color:var(--color-green-50)}.dark .bg-success-light{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .bg-success-light{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.bg-success-solid{background-color:var(--color-green-600)}.dark .bg-success-solid{background-color:var(--color-green-500)}.bg-error{background-color:var(--color-red-100)}.dark .bg-error{background-color:var(--color-red-900)}.bg-error-light{background-color:var(--color-red-50)}.dark .bg-error-light{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .bg-error-light{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.bg-error-solid{background-color:var(--color-red-600)}.dark .bg-error-solid{background-color:var(--color-red-500)}.bg-warning{background-color:var(--color-yellow-100)}.dark .bg-warning{background-color:var(--color-yellow-900)}.bg-warning-light{background-color:var(--color-yellow-50)}.dark .bg-warning-light{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .bg-warning-light{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.bg-warning-solid{background-color:var(--color-yellow-600)}.dark .bg-warning-solid{background-color:var(--color-yellow-500)}.bg-info{background-color:var(--color-blue-100)}.dark .bg-info{background-color:var(--color-blue-900)}.bg-info-light{background-color:var(--color-blue-50)}.dark .bg-info-light{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .bg-info-light{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.bg-info-solid{background-color:var(--color-blue-600)}.dark .bg-info-solid{background-color:var(--color-blue-500)}.bg-ready{background-color:var(--color-cyan-100)}.dark .bg-ready{background-color:var(--color-cyan-900)}.bg-ready-light{background-color:var(--color-cyan-50)}.dark .bg-ready-light{background-color:#104e6433}@supports (color:color-mix(in lab,red,red)){.dark .bg-ready-light{background-color:color-mix(in oklab,var(--color-cyan-900) 20%,transparent)}}.bg-ready-solid{background-color:var(--color-cyan-600)}.dark .bg-ready-solid{background-color:var(--color-cyan-400)}.bg-progress{background-color:var(--color-indigo-100)}.dark .bg-progress{background-color:var(--color-indigo-900)}.bg-progress-light{background-color:var(--color-indigo-50)}.dark .bg-progress-light{background-color:#312c8533}@supports (color:color-mix(in lab,red,red)){.dark .bg-progress-light{background-color:color-mix(in oklab,var(--color-indigo-900) 20%,transparent)}}.bg-progress-solid{background-color:var(--color-indigo-600)}.dark .bg-progress-solid{background-color:var(--color-indigo-400)}.bg-complete{background-color:var(--color-pink-100)}.dark .bg-complete{background-color:var(--color-pink-900)}.bg-complete-light{background-color:var(--color-pink-50)}.dark .bg-complete-light{background-color:#86104333}@supports (color:color-mix(in lab,red,red)){.dark .bg-complete-light{background-color:color-mix(in oklab,var(--color-pink-900) 20%,transparent)}}.bg-complete-solid{background-color:var(--color-pink-600)}.dark .bg-complete-solid{background-color:var(--color-pink-400)}.bg-hold{background-color:var(--color-gray-100)}.dark .bg-hold{background-color:var(--color-gray-800)}.bg-hold-light{background-color:var(--color-gray-50)}.dark .bg-hold-light{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .bg-hold-light{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-hold-solid{background-color:var(--color-gray-500)}.dark .bg-hold-solid{background-color:var(--color-gray-400)}.text-success{color:var(--color-green-600)}.dark .text-success{color:var(--color-green-400)}.text-error{color:var(--color-red-600)}.dark .text-error{color:var(--color-red-400)}.text-warning{color:var(--color-yellow-600)}.dark .text-warning{color:var(--color-yellow-400)}.text-info{color:var(--color-blue-600)}.dark .text-info{color:var(--color-blue-400)}.text-ready{color:var(--color-cyan-600)}.dark .text-ready{color:var(--color-cyan-400)}.text-progress{color:var(--color-indigo-600)}.dark .text-progress{color:var(--color-indigo-400)}.text-complete{color:var(--color-pink-600)}.dark .text-complete{color:var(--color-pink-400)}.text-hold{color:var(--color-gray-500)}.dark .text-hold{color:var(--color-gray-400)}.text-success-light{color:var(--color-white)}.dark .text-success-light{color:var(--color-green-900)}.text-error-light{color:var(--color-white)}.dark .text-error-light{color:var(--color-red-900)}.text-warning-light{color:var(--color-white)}.dark .text-warning-light{color:var(--color-yellow-900)}.text-info-light{color:var(--color-white)}.dark .text-info-light{color:var(--color-blue-900)}.text-ready-light{color:var(--color-white)}.dark .text-ready-light{color:var(--color-cyan-900)}.text-progress-light{color:var(--color-white)}.dark .text-progress-light{color:var(--color-indigo-900)}.text-complete-light{color:var(--color-white)}.dark .text-complete-light{color:var(--color-pink-900)}.text-hold-light{color:var(--color-white)}.dark .text-hold-light{color:var(--color-gray-900)}.border-success{border-color:var(--color-green-200)}.dark .border-success{border-color:var(--color-green-800)}.border-error{border-color:var(--color-red-200)}.dark .border-error{border-color:var(--color-red-800)}.border-warning{border-color:var(--color-yellow-200)}.dark .border-warning{border-color:var(--color-yellow-800)}.border-info{border-color:var(--color-blue-200)}.dark .border-info{border-color:var(--color-blue-800)}.border-ready{border-color:var(--color-cyan-200)}.dark .border-ready{border-color:var(--color-cyan-800)}.border-progress{border-color:var(--color-indigo-200)}.dark .border-progress{border-color:var(--color-indigo-800)}.border-complete{border-color:var(--color-pink-200)}.dark .border-complete{border-color:var(--color-pink-800)}.border-hold{border-color:var(--color-gray-300)}.dark .border-hold{border-color:var(--color-gray-600)}.btn-success{background-color:var(--color-green-600);color:var(--color-white)}@media(hover:hover){.btn-success:hover{background-color:var(--color-green-700)}}.dark .btn-success{background-color:var(--color-green-500)}@media(hover:hover){.dark .btn-success:hover{background-color:var(--color-green-600)}}.btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media(hover:hover){.btn-danger:hover{background-color:var(--color-red-700)}}.dark .btn-danger{background-color:var(--color-red-500)}@media(hover:hover){.dark .btn-danger:hover{background-color:var(--color-red-600)}}.btn-warning{background-color:var(--color-yellow-600);color:var(--color-white)}@media(hover:hover){.btn-warning:hover{background-color:var(--color-yellow-700)}}.dark .btn-warning{background-color:var(--color-yellow-500)}@media(hover:hover){.dark .btn-warning:hover{background-color:var(--color-yellow-600)}}.btn-info{background-color:var(--color-blue-600);color:var(--color-white)}@media(hover:hover){.btn-info:hover{background-color:var(--color-blue-700)}}.dark .btn-info{background-color:var(--color-blue-500)}@media(hover:hover){.dark .btn-info:hover{background-color:var(--color-blue-600)}}.btn-neutral{background-color:var(--color-gray-600);color:var(--color-white)}@media(hover:hover){.btn-neutral:hover{background-color:var(--color-gray-700)}}.dark .btn-neutral{background-color:var(--color-gray-500)}@media(hover:hover){.dark .btn-neutral:hover{background-color:var(--color-gray-600)}}.btn-sm{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5)}.button{cursor:pointer;justify-content:center;align-items:center;display:inline-flex}.button:disabled{cursor:not-allowed}.btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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:.2s;transition-duration:.2s;display:inline-flex}.btn:disabled{cursor:not-allowed;opacity:.5}.btn-primary{background-color:var(--color-gray-900);color:var(--color-white)}@media(hover:hover){.btn-primary:hover{background-color:var(--color-gray-800)}}.dark .btn-primary{background-color:var(--color-white);color:var(--color-gray-900)}@media(hover:hover){.dark .btn-primary:hover{background-color:var(--color-gray-100)}}.btn-secondary{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-gray-700)}@media(hover:hover){.btn-secondary:hover{background-color:var(--color-gray-50)}}.dark .btn-secondary{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-300)}@media(hover:hover){.dark .btn-secondary:hover{background-color:var(--color-gray-700)}}.btn-outline{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);background-color:#0000}@media(hover:hover){.btn-outline:hover{background-color:var(--color-gray-50)}}.dark .btn-outline{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .btn-outline:hover{background-color:var(--color-gray-700)}}.btn-ghost{color:var(--color-gray-600);background-color:#0000}@media(hover:hover){.btn-ghost:hover{background-color:var(--color-gray-100)}}.dark .btn-ghost{color:var(--color-gray-400)}@media(hover:hover){.dark .btn-ghost:hover{background-color:var(--color-gray-700)}}.btn-icon{border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);color:var(--color-gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.btn-icon:hover{background-color:var(--color-gray-100)}}.dark .btn-icon{color:var(--color-gray-400)}@media(hover:hover){.dark .btn-icon:hover{background-color:var(--color-gray-700)}}.btn-preset{background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);border-radius:3.40282e38px}.dark .btn-preset{background-color:var(--color-gray-700);color:var(--color-gray-300)}.btn-date{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .btn-date{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-date.active{border-color:var(--color-gray-900);background-color:var(--color-gray-900);color:var(--color-white)}.dark .btn-date.active{border-color:var(--color-white);background-color:var(--color-white);color:var(--color-gray-900)}.btn-badge{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);border-radius:3.40282e38px}.dark .btn-badge{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-badge.active{border-color:var(--color-gray-900);color:var(--color-gray-900)}.dark .btn-badge.active{border-color:var(--color-white);color:var(--color-blue-400)}.btn-group{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);display:inline-flex;overflow:hidden}.dark .btn-group{border-color:var(--color-gray-700)}.btn-group-item{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.btn-group-item:hover{background-color:var(--color-gray-50)}}.dark .btn-group-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}@media(hover:hover){.dark .btn-group-item:hover{background-color:var(--color-gray-700)}}.btn-group-item:last-child{border-right-style:var(--tw-border-style);border-right-width:0}.btn-group-item.active{background-color:var(--color-gray-900);color:var(--color-white)}.dark .btn-group-item.active{background-color:var(--color-white);color:var(--color-gray-900)}.btn-badge-green{border-color:var(--color-green-500);background-color:var(--color-green-50);color:var(--color-green-600)}.dark .btn-badge-green{border-color:var(--color-green-400);background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-green{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .btn-badge-green{color:var(--color-green-400)}.btn-badge-blue{border-color:var(--color-blue-500);background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .btn-badge-blue{border-color:var(--color-blue-400);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-blue{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .btn-badge-blue{color:var(--color-blue-400)}.btn-badge-cyan{border-color:var(--color-cyan-500);background-color:var(--color-cyan-50);color:var(--color-cyan-600)}.dark .btn-badge-cyan{border-color:var(--color-cyan-400);background-color:#104e6433}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-cyan{background-color:color-mix(in oklab,var(--color-cyan-900) 20%,transparent)}}.dark .btn-badge-cyan{color:var(--color-cyan-400)}.btn-badge-indigo{border-color:var(--color-indigo-500);background-color:var(--color-indigo-50);color:var(--color-indigo-600)}.dark .btn-badge-indigo{border-color:var(--color-indigo-400);background-color:#312c8533}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-indigo{background-color:color-mix(in oklab,var(--color-indigo-900) 20%,transparent)}}.dark .btn-badge-indigo{color:var(--color-indigo-400)}.btn-badge-pink{border-color:var(--color-pink-500);background-color:var(--color-pink-50);color:var(--color-pink-600)}.dark .btn-badge-pink{border-color:var(--color-pink-400);background-color:#86104333}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-pink{background-color:color-mix(in oklab,var(--color-pink-900) 20%,transparent)}}.dark .btn-badge-pink{color:var(--color-pink-400)}.btn-badge-red{border-color:var(--color-red-500);background-color:var(--color-red-50);color:var(--color-red-600)}.dark .btn-badge-red{border-color:var(--color-red-400);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-red{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .btn-badge-red{color:var(--color-red-400)}.btn-badge-gray{border-color:var(--color-gray-300);background-color:var(--color-gray-50);color:var(--color-gray-600)}.dark .btn-badge-gray{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-badge-yellow{border-color:var(--color-yellow-500);background-color:var(--color-yellow-50);color:var(--color-yellow-600)}.dark .btn-badge-yellow{border-color:var(--color-yellow-400);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-yellow{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .btn-badge-yellow{color:var(--color-yellow-400)}.btn-badge-orange{border-color:var(--color-orange-500);background-color:var(--color-orange-50);color:var(--color-orange-600)}.dark .btn-badge-orange{border-color:var(--color-orange-400);background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-orange{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .btn-badge-orange{color:var(--color-orange-400)}.input{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input::placeholder{color:var(--color-gray-400)}.input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .input{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .input::placeholder{color:var(--color-gray-500)}.input:disabled{cursor:not-allowed;background-color:var(--color-gray-100);color:var(--color-gray-400)}.dark .input:disabled{background-color:var(--color-gray-800);color:var(--color-gray-500)}.input-tag{border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input-tag:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .input-tag{background-color:var(--color-gray-700);color:var(--color-gray-100)}.input-tag{min-height:2.25rem}.textarea{border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.textarea::placeholder{color:var(--color-gray-400)}.textarea:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .textarea{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .textarea::placeholder{color:var(--color-gray-500)}.textarea:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .textarea:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.input-readonly{cursor:default;color:var(--color-gray-500)}.dark .input-readonly{color:var(--color-gray-400)}.input-w-xs{width:calc(var(--spacing) * 20)}.input-w-sm{width:calc(var(--spacing) * 32)}.input-w-md{width:calc(var(--spacing) * 48)}.input-w-lg{width:calc(var(--spacing) * 64)}.input-w-xl{width:calc(var(--spacing) * 80)}.input-w-auto{width:auto}.input-search-container{border-radius:var(--radius-lg);background-color:var(--color-gray-100);align-items:center;display:flex;overflow:hidden}.dark .input-search-container{background-color:var(--color-gray-700)}.input-search-container .input-search-icon{padding-left:calc(var(--spacing) * 3);color:var(--color-gray-400)}.dark :is(.input-search-container .input-search-icon){color:var(--color-gray-500)}.input-search-container input{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);background-color:#0000;flex:1}.input-search-container input::placeholder{color:var(--color-gray-400)}.input-search-container input:focus{--tw-outline-style:none;outline-style:none}.dark :is(.input-search-container input){color:var(--color-gray-100)}.dark :is(.input-search-container input)::placeholder{color:var(--color-gray-500)}.input-search-container .input-search-dropdown{cursor:pointer;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}@media(hover:hover){.input-search-container .input-search-dropdown:hover{background-color:var(--color-gray-50)}}.dark :is(.input-search-container .input-search-dropdown){border-color:var(--color-gray-700);color:var(--color-gray-400)}@media(hover:hover){.dark :is(.input-search-container .input-search-dropdown):hover{background-color:var(--color-gray-700)}}.select{height:calc(var(--spacing) * 9);cursor:pointer;appearance:none;border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select{background-color:var(--color-gray-700);color:var(--color-gray-100)}.select:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .select:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}.dark .select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")}.select-composite{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.select-composite:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select-composite{background-color:var(--color-gray-700);color:var(--color-gray-100)}.select-composite:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .select-composite:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.select-composite-sm{height:calc(var(--spacing) * 8);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.select-w-sm{width:calc(var(--spacing) * 32)}.select-w-md{width:calc(var(--spacing) * 48)}.select-w-lg{width:calc(var(--spacing) * 64)}.input-split{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.input-split input{border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input-split input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark :is(.input-split input){background-color:var(--color-gray-700);color:var(--color-gray-100)}.input-split .input-split-separator{color:var(--color-gray-400)}.dark :is(.input-split .input-split-separator){color:var(--color-gray-500)}.tag-input-container{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.tag{align-items:center;gap:var(--spacing);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);border-radius:3.40282e38px;display:inline-flex}.dark .tag{background-color:var(--color-gray-700);color:var(--color-gray-300)}.tag .tag-remove{cursor:pointer;color:var(--color-gray-400)}@media(hover:hover){.tag .tag-remove:hover{color:var(--color-gray-600)}}.dark :is(.tag .tag-remove){color:var(--color-gray-500)}@media(hover:hover){.dark :is(.tag .tag-remove):hover{color:var(--color-gray-300)}}:where(.form-group>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}.textarea-sm{min-height:5rem}.textarea-md{min-height:8rem}.textarea-lg{min-height:12rem}.input-error{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-500)}.input-error:focus{--tw-ring-color:var(--color-red-500)}.dark .input-error{border-color:var(--color-red-500)}.input-success{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-500)}.input-success:focus{--tw-ring-color:var(--color-green-500)}.dark .input-success{border-color:var(--color-green-500)}.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-500)}:is(.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea):focus{--tw-ring-color:var(--color-red-500)}.dark :is(.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea){border-color:var(--color-red-500)}.form-error{margin-top:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-500);display:block}.dark .form-error{color:var(--color-red-400)}.form-success{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-600)}.dark .form-success{color:var(--color-green-400)}.checkbox{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-gray-900);border-radius:.25rem}.checkbox:focus{--tw-ring-color:var(--color-gray-500)}.dark .checkbox{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.checkbox:checked{border-color:var(--color-gray-900);background-color:var(--color-gray-900)}.dark .checkbox:checked{border-color:var(--color-white);background-color:var(--color-white)}.radio{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-blue-600);flex-shrink:0}.radio:focus{--tw-ring-color:var(--color-blue-500)}.dark .radio{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.dark .radio:focus{--tw-ring-color:var(--color-blue-400)}.radio:checked{border-color:var(--color-blue-600)}.dark .radio:checked{border-color:var(--color-blue-400)}.toggle{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 11);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));border-radius:3.40282e38px;align-items:center;display:inline-flex;position:relative}.toggle-track{background-color:var(--color-gray-200);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));border-radius:3.40282e38px;position:absolute;inset:0}.dark .toggle-track{background-color:var(--color-gray-700)}.toggle.active .toggle-track{background-color:var(--color-blue-600)}.toggle-handle{left:calc(var(--spacing) * .5);height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);background-color:var(--color-white);--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);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.toggle.active .toggle-handle{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.badge{align-items:center;gap:var(--spacing);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}.badge-green{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-200);background-color:var(--color-green-100);color:var(--color-green-700)}.dark .badge-green{border-color:var(--color-green-800);background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-green{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .badge-green{color:var(--color-green-400)}.badge-blue{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-200);background-color:var(--color-blue-100);color:var(--color-blue-700)}.dark .badge-blue{border-color:var(--color-blue-800);background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-blue{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .badge-blue{color:var(--color-blue-400)}.badge-cyan{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-cyan-200);background-color:var(--color-cyan-100);color:var(--color-cyan-700)}.dark .badge-cyan{border-color:var(--color-cyan-800);background-color:#104e644d}@supports (color:color-mix(in lab,red,red)){.dark .badge-cyan{background-color:color-mix(in oklab,var(--color-cyan-900) 30%,transparent)}}.dark .badge-cyan{color:var(--color-cyan-400)}.badge-indigo{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-indigo-200);background-color:var(--color-indigo-100);color:var(--color-indigo-700)}.dark .badge-indigo{border-color:var(--color-indigo-800);background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .badge-indigo{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .badge-indigo{color:var(--color-indigo-400)}.badge-pink{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-pink-200);background-color:var(--color-pink-100);color:var(--color-pink-700)}.dark .badge-pink{border-color:var(--color-pink-800);background-color:#8610434d}@supports (color:color-mix(in lab,red,red)){.dark .badge-pink{background-color:color-mix(in oklab,var(--color-pink-900) 30%,transparent)}}.dark .badge-pink{color:var(--color-pink-400)}.badge-red{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-100);color:var(--color-red-700)}.dark .badge-red{border-color:var(--color-red-800);background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-red{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .badge-red{color:var(--color-red-400)}.badge-gray{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);color:var(--color-gray-600)}.dark .badge-gray{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.badge-yellow{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-100);color:var(--color-yellow-700)}.dark .badge-yellow{border-color:var(--color-yellow-800);background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-yellow{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .badge-yellow{color:var(--color-yellow-400)}.badge-status{align-items:center;gap:calc(var(--spacing) * 1.5);padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;display:inline-flex}.badge-status-success{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .badge-status-success{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-success{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .badge-status-success{color:var(--color-green-400)}.badge-status-processing{background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .badge-status-processing{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-processing{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .badge-status-processing{color:var(--color-blue-400)}.badge-status-error{background-color:var(--color-red-50);color:var(--color-red-600)}.dark .badge-status-error{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-error{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .badge-status-error{color:var(--color-red-400)}.badge-status-expired{background-color:var(--color-gray-50);color:var(--color-gray-500)}.dark .badge-status-expired{background-color:var(--color-gray-800);color:var(--color-gray-400)}.search-bar{gap:calc(var(--spacing) * 3);flex-direction:column;display:flex}@media(min-width:48rem){.search-bar{flex-flow:wrap;align-items:center}}.search-bar-select{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.search-bar-select::placeholder{color:var(--color-gray-400)}.search-bar-select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .search-bar-select{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .search-bar-select::placeholder{color:var(--color-gray-500)}@media(min-width:48rem){.search-bar-select{width:calc(var(--spacing) * 32);flex-shrink:0}}.search-bar-select-custom{width:100%}@media(min-width:48rem){.search-bar-select-custom{width:calc(var(--spacing) * 32);flex-shrink:0}}.search-bar-input{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.search-bar-input::placeholder{color:var(--color-gray-400)}.search-bar-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .search-bar-input{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .search-bar-input::placeholder{color:var(--color-gray-500)}@media(min-width:48rem){.search-bar-input{min-width:calc(var(--spacing) * 64);flex:1}}.search-bar-reset{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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:.2s;border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);background-color:#0000;transition-duration:.2s;display:inline-flex}@media(hover:hover){.search-bar-reset:hover{background-color:var(--color-gray-50)}}.dark .search-bar-reset{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .search-bar-reset:hover{background-color:var(--color-gray-700)}}.search-bar-reset{gap:calc(var(--spacing) * 1.5);flex-shrink:0;width:100%}@media(min-width:48rem){.search-bar-reset{width:auto}}.filter-grid{gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}.filter-row{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}@media(min-width:48rem){.filter-row{flex-direction:row;align-items:center}}.filter-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);display:block}.dark .filter-label{color:var(--color-gray-400)}@media(min-width:48rem){.filter-label{width:calc(var(--spacing) * 14);flex-shrink:0;align-items:center;margin-bottom:0;display:flex}}.filter-buttons{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.dropdown{display:inline-block;position:relative}.dropdown-trigger{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--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)}@media(hover:hover){.dropdown-trigger:hover{background-color:var(--color-gray-50)}}.dropdown-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dropdown-trigger{color:var(--color-gray-900)}.dark .dropdown-trigger{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-white)}@media(hover:hover){.dark .dropdown-trigger:hover{background-color:var(--color-gray-700)}}.dark .dropdown-trigger:focus{--tw-ring-color:var(--color-blue-400)}.dropdown-trigger-text{cursor:pointer;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900);align-items:center;display:inline-flex}@media(hover:hover){.dropdown-trigger-text:hover{color:var(--color-blue-600)}}.dark .dropdown-trigger-text{color:var(--color-white)}@media(hover:hover){.dark .dropdown-trigger-text:hover{color:var(--color-blue-400)}}.dropdown-menu{z-index:50;width:calc(var(--spacing) * 56);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--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);position:absolute}.dark .dropdown-menu{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.dropdown-menu-bottom-left{margin-top:calc(var(--spacing) * 2);top:100%;left:0}.dropdown-menu-bottom-right{margin-top:calc(var(--spacing) * 2);top:100%;right:0}.dropdown-menu-top-left{margin-bottom:calc(var(--spacing) * 2);bottom:100%;left:0}.dropdown-menu-top-right{margin-bottom:calc(var(--spacing) * 2);bottom:100%;right:0}.dropdown-item{cursor:pointer;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}@media(hover:hover){.dropdown-item:hover{background-color:var(--color-gray-100)}}.dark .dropdown-item{color:var(--color-gray-300)}@media(hover:hover){.dark .dropdown-item:hover{background-color:var(--color-gray-700)}}.dropdown-item-icon{color:var(--color-gray-400)}.dark .dropdown-item-icon{color:var(--color-gray-500)}.dropdown-item-disabled{cursor:not-allowed;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-400)}.dark .dropdown-item-disabled{color:var(--color-gray-500)}.dropdown-item-focused{background-color:var(--color-gray-100)}.dark .dropdown-item-focused{background-color:var(--color-gray-700)}.dropdown-divider{margin-block:var(--spacing);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100)}.dark .dropdown-divider{border-color:var(--color-gray-700)}.pagination{justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.pagination-btn{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-gray-700);border-radius:.25rem}@media(hover:hover){.pagination-btn:hover{background-color:var(--color-gray-100)}}.pagination-btn:disabled{cursor:not-allowed;opacity:.5}.dark .pagination-btn{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-300)}@media(hover:hover){.dark .pagination-btn:hover{background-color:var(--color-gray-700)}}.pagination-btn-active{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:1px;border-color:var(--color-blue-500);background-color:var(--color-blue-500);color:var(--color-white);border-radius:.25rem}.dark .pagination-btn-active{border-color:var(--color-blue-600);background-color:var(--color-blue-600)}.pagination-ellipsis{padding-inline:calc(var(--spacing) * 3);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .pagination-ellipsis{color:var(--color-gray-400)}.dialog-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.dialog-title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .dialog-title{color:var(--color-white)}.dialog-close{color:var(--color-gray-400)}@media(hover:hover){.dialog-close:hover{color:var(--color-gray-600)}}.dialog-close:focus{--tw-outline-style:none;outline-style:none}.dark .dialog-close{color:var(--color-gray-500)}@media(hover:hover){.dark .dialog-close:hover{color:var(--color-gray-300)}}.dialog-body{margin-bottom:calc(var(--spacing) * 6);color:var(--color-gray-700)}.dark .dialog-body{color:var(--color-gray-300)}.dialog-footer{justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.dialog-btn-primary{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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))}.dialog-btn-primary:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-primary:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-primary{background-color:var(--color-blue-600);color:var(--color-white)}@media(hover:hover){.dialog-btn-primary:hover{background-color:var(--color-blue-700)}}.dialog-btn-primary:focus{--tw-ring-color:var(--color-blue-500)}.dialog-btn-secondary{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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))}.dialog-btn-secondary:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-secondary:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-secondary{background-color:var(--color-gray-200);color:var(--color-gray-800)}@media(hover:hover){.dialog-btn-secondary:hover{background-color:var(--color-gray-300)}}.dialog-btn-secondary:focus{--tw-ring-color:var(--color-gray-500)}.dark .dialog-btn-secondary{background-color:var(--color-gray-700);color:var(--color-gray-200)}@media(hover:hover){.dark .dialog-btn-secondary:hover{background-color:var(--color-gray-600)}}.dialog-btn-danger{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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))}.dialog-btn-danger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-danger:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media(hover:hover){.dialog-btn-danger:hover{background-color:var(--color-red-700)}}.dialog-btn-danger:focus{--tw-ring-color:var(--color-red-500)}.dialog-btn-ghost{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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))}.dialog-btn-ghost:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-ghost:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-ghost{color:var(--color-gray-700);background-color:#0000}@media(hover:hover){.dialog-btn-ghost:hover{background-color:var(--color-gray-100)}}.dialog-btn-ghost:focus{--tw-ring-color:var(--color-gray-500)}.dark .dialog-btn-ghost{color:var(--color-gray-300)}@media(hover:hover){.dark .dialog-btn-ghost:hover{background-color:var(--color-gray-700)}}.alert-error{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-900);display:flex}.dark .alert-error{border-color:var(--color-red-800);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-error{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .alert-error{color:var(--color-red-100)}.alert-icon-info{margin-top:calc(var(--spacing) * .5);color:var(--color-blue-600);flex-shrink:0}.dark .alert-icon-info{color:var(--color-blue-400)}.alert-icon-success{margin-top:calc(var(--spacing) * .5);color:var(--color-green-600);flex-shrink:0}.dark .alert-icon-success{color:var(--color-green-400)}.alert-icon-warning{margin-top:calc(var(--spacing) * .5);color:var(--color-yellow-600);flex-shrink:0}.dark .alert-icon-warning{color:var(--color-yellow-400)}.alert-icon-error{margin-top:calc(var(--spacing) * .5);color:var(--color-red-600);flex-shrink:0}.dark .alert-icon-error{color:var(--color-red-400)}.alert-text-info{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-800);flex:1}.dark .alert-text-info{color:var(--color-blue-200)}.alert-text-success{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-800);flex:1}.dark .alert-text-success{color:var(--color-green-200)}.alert-text-warning{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-800);flex:1}.dark .alert-text-warning{color:var(--color-yellow-200)}.alert-text-error{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-800);flex:1}.dark .alert-text-error{color:var(--color-red-200)}.alert-dismiss{cursor:pointer;border-style:var(--tw-border-style);background-color:#0000;border-width:0;flex-shrink:0;padding:0}@media(hover:hover){.alert-dismiss:hover{opacity:.7}}.table-container{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);overflow:hidden}.dark .table-container{border-color:var(--color-gray-700)}.table{width:100%}.table thead{background-color:var(--color-gray-50)}.dark :is(.table thead){background-color:var(--color-gray-800)}.table th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600)}.dark :is(.table th){border-color:var(--color-gray-700);color:var(--color-gray-400)}.table th.sortable{cursor:pointer}@media(hover:hover){.table th.sortable:hover{background-color:var(--color-gray-100)}.dark :is(.table th.sortable):hover{background-color:var(--color-gray-700)}}.table tbody tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);background-color:var(--color-white)}.dark :is(.table tbody tr){border-color:var(--color-gray-800);background-color:var(--color-gray-900)}.table tbody tr:hover{background-color:var(--color-gray-50)}.dark :is(.table tbody tr:hover){background-color:var(--color-gray-800)}.table tbody tr.selected{background-color:var(--color-blue-50)}.dark :is(.table tbody tr.selected){background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark :is(.table tbody tr.selected){background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.table td{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark :is(.table td){color:var(--color-gray-100)}.table-actions{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;display:flex}.dark .table-actions{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.log-table{width:100%}.log-table th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500)}.dark :is(.log-table th){border-color:var(--color-gray-700);color:var(--color-gray-400)}.log-table td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark :is(.log-table td){border-color:var(--color-gray-800);color:var(--color-gray-100)}.log-table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.log-table-actor{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);align-items:center;display:inline-flex}.dark .log-table-actor{border-color:var(--color-gray-600);background-color:var(--color-gray-700);color:var(--color-gray-300)}.log-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .log-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.log-card-header{margin-bottom:calc(var(--spacing) * 2);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);display:flex}.log-card-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .log-card-body{color:var(--color-gray-100)}.modal-overlay{z-index:50;background-color:#00000080;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}@supports (color:color-mix(in lab,red,red)){.modal-overlay{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.modal-wrapper{justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal-backdrop{background-color:#00000080;position:absolute;inset:0}@supports (color:color-mix(in lab,red,red)){.modal-backdrop{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.modal-container{border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);max-width:calc(100vw - 2rem);max-height:90vh;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:relative;overflow:hidden}.dark .modal-container{background-color:var(--color-gray-800)}.modal-header-row{padding:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.modal-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .modal-title{color:var(--color-white)}.modal-close-btn{color:var(--color-gray-400);margin-left:auto}@media(hover:hover){.modal-close-btn:hover{color:var(--color-gray-600)}}.modal-close-btn:focus{--tw-outline-style:none;outline-style:none}.dark .modal-close-btn{color:var(--color-gray-500)}@media(hover:hover){.dark .modal-close-btn:hover{color:var(--color-gray-300)}}.modal-body-content{max-height:calc(90vh - 140px);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4);overflow-y:auto}.modal{margin-inline:calc(var(--spacing) * 4);width:100%;max-width:var(--container-md);border-radius:var(--radius-xl);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.dark .modal{background-color:var(--color-gray-800)}.modal-lg{max-width:var(--container-lg)}.modal-xl{max-width:var(--container-xl)}.modal-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;display:flex}.modal-close{cursor:pointer;border-radius:var(--radius-lg);padding:var(--spacing);color:var(--color-gray-400)}@media(hover:hover){.modal-close:hover{background-color:var(--color-gray-100);color:var(--color-gray-600)}}.dark .modal-close{color:var(--color-gray-500)}@media(hover:hover){.dark .modal-close:hover{background-color:var(--color-gray-700);color:var(--color-gray-300)}}.modal-body{padding-block:calc(var(--spacing) * 4)}.modal-footer{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.danger-confirm-icon{margin-inline:auto;margin-bottom:calc(var(--spacing) * 4);height:calc(var(--spacing) * 12);width:calc(var(--spacing) * 12);background-color:var(--color-red-100);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex}.dark .danger-confirm-icon{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .danger-confirm-icon{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.danger-confirm-icon svg,.danger-confirm-icon .icon{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6);color:var(--color-red-600)}.dark :is(.danger-confirm-icon svg,.danger-confirm-icon .icon){color:var(--color-red-400)}.danger-confirm-title{margin-bottom:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .danger-confirm-title{color:var(--color-white)}.danger-confirm-message{margin-bottom:calc(var(--spacing) * 4);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .danger-confirm-message{color:var(--color-gray-400)}.danger-confirm-warning{margin-bottom:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-50);padding:calc(var(--spacing) * 4)}.dark .danger-confirm-warning{border-color:var(--color-yellow-700);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .danger-confirm-warning{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.danger-confirm-warning-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-yellow-800)}.dark .danger-confirm-warning-title{color:var(--color-yellow-300)}.danger-confirm-warning-list{list-style-type:disc;list-style-position:inside}:where(.danger-confirm-warning-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}.danger-confirm-warning-list{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-700)}.dark .danger-confirm-warning-list{color:var(--color-yellow-400)}.danger-confirm-alert{text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-600)}.dark .danger-confirm-alert{color:var(--color-red-400)}.danger-confirm-footer{margin-top:calc(var(--spacing) * 6);justify-content:center;gap:calc(var(--spacing) * 3);display:flex}.card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);width:100%;padding:calc(var(--spacing) * 6)}.dark .card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card{margin-bottom:calc(var(--spacing) * 6);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .admin-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card{flex-direction:column;display:flex}.admin-card-clickable{margin-bottom:calc(var(--spacing) * 2);cursor:pointer;border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5)}@media(hover:hover){.admin-card-clickable:hover{border-color:var(--color-gray-300)}}.dark .admin-card-clickable{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}@media(hover:hover){.dark .admin-card-clickable:hover{border-color:var(--color-gray-600)}}.admin-card-selected{margin-bottom:calc(var(--spacing) * 2);cursor:pointer;border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-500);background-color:var(--color-blue-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5)}.dark .admin-card-selected{border-color:var(--color-blue-500);background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .admin-card-selected{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.card-clickable{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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);transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));overflow:hidden}@media(hover:hover){.card-clickable:hover{--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)}}.dark .card-clickable{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.card-image{height:calc(var(--spacing) * 48);object-fit:cover;width:100%}.card-content{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));white-space:pre-line;color:var(--color-gray-600)}.dark .card-content{color:var(--color-gray-300)}.card-section-title{margin-inline:calc(var(--spacing) * -6);margin-bottom:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 6);padding-bottom:calc(var(--spacing) * 4);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .card-section-title{border-color:var(--color-gray-700);color:var(--color-white)}.card-account{border-radius:var(--radius-lg);background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4);position:relative}.dark .card-account{background-color:var(--color-gray-900)}.card-account-default{top:calc(var(--spacing) * 3);left:calc(var(--spacing) * 3);position:absolute}.card-account-delete{top:calc(var(--spacing) * 3);right:calc(var(--spacing) * 3);position:absolute}.admin-card-accordion{margin-bottom:calc(var(--spacing) * 6);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .admin-card-accordion{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card-accordion{flex-direction:column;display:flex}.admin-card-accordion>.card-header{cursor:pointer}.admin-card-accordion-icon{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;margin-left:auto;transition-duration:.2s}.dark .admin-card-accordion-icon{color:var(--color-gray-500)}.admin-card-accordion>.row-stack{margin-top:calc(var(--spacing) * 6)}.admin-card-accordion-icon.open{rotate:180deg}.tabs{border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding:var(--spacing);display:inline-flex}.dark .tabs{background-color:var(--color-gray-800)}.tab{cursor:pointer;border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.tab:hover{color:var(--color-gray-700)}}.dark .tab{color:var(--color-gray-400)}@media(hover:hover){.dark .tab:hover{color:var(--color-gray-300)}}.tab.active{background-color:var(--color-white);color:var(--color-gray-900);--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)}.dark .tab.active{background-color:var(--color-gray-700);color:var(--color-white)}.toast{right:calc(var(--spacing) * 4);bottom:calc(var(--spacing) * 4);z-index:50;position:fixed}.toast-item{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);background-color:var(--color-gray-900);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);color:var(--color-white);--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);display:flex}.dark .toast-item{background-color:var(--color-white);color:var(--color-gray-900)}.toast-icon{flex-shrink:0}.drag-item{align-items:center;gap:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4);display:flex}@media(hover:hover){.drag-item:hover{--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)}}.dark .drag-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.drag-handle{cursor:grab;color:var(--color-gray-400)}.dark .drag-handle{color:var(--color-gray-500)}.drag-handle:active{cursor:grabbing}.table-mobile-cards{display:none}.excel-card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .excel-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.excel-card-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-bottom:calc(var(--spacing) * 3);display:flex}.dark .excel-card-header{border-color:var(--color-gray-700)}.excel-card-title{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .excel-card-title{color:var(--color-white)}:where(.excel-card-body>: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)))}.excel-card-row{padding-block:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));justify-content:space-between;align-items:center;display:flex}.excel-card-label{color:var(--color-gray-500);flex-shrink:0}.dark .excel-card-label{color:var(--color-gray-400)}.excel-card-value{text-align:right;color:var(--color-gray-900)}.dark .excel-card-value{color:var(--color-white)}.excel-card-footer{margin-top:calc(var(--spacing) * 4);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100);padding-top:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;display:flex}.dark .excel-card-footer{border-color:var(--color-gray-700)}.excel-card-creator{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}.dark .excel-card-creator{color:var(--color-gray-300)}.excel-card-creator-label{margin-right:calc(var(--spacing) * 2);color:var(--color-gray-500)}.dark .excel-card-creator-label{color:var(--color-gray-400)}.select-custom{display:inline-block;position:relative}.select-trigger{height:calc(var(--spacing) * 9);border-radius:var(--radius-xl);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);cursor:pointer;justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;display:flex}.select-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select-trigger{background-color:var(--color-gray-700);color:var(--color-gray-200)}.select-trigger:disabled{cursor:not-allowed;opacity:.5}.select-trigger span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.select-trigger-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;transition-duration:.2s}.dark .select-trigger-icon{color:var(--color-gray-500)}.select-trigger-icon.open{rotate:180deg}.select-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100%;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute;overflow:hidden}.dark .select-dropdown{border-color:var(--color-gray-600);background-color:var(--color-gray-800)}.select-dropdown.hidden{display:none}.select-dropdown-inner{max-height:calc(var(--spacing) * 60);padding-block:calc(var(--spacing) * 2);overflow:auto}.select-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);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));align-items:center;display:flex}@media(hover:hover){.select-option:hover{background-color:var(--color-gray-100)}}.dark .select-option{color:var(--color-gray-200)}@media(hover:hover){.dark .select-option:hover{background-color:var(--color-gray-700)}}.select-option.selected{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-600)}.dark .select-option.selected{color:var(--color-blue-400)}.select-option:disabled{cursor:not-allowed;opacity:.5}.select-option-check{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-blue-600);flex-shrink:0;margin-left:auto}.dark .select-option-check{color:var(--color-blue-400)}.multi-select{display:inline-block;position:relative}.multi-select-trigger{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);cursor:pointer;background-color:#0000;display:inline-flex}@media(hover:hover){.multi-select-trigger:hover{background-color:var(--color-gray-50)}}.dark .multi-select-trigger{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .multi-select-trigger:hover{background-color:var(--color-gray-700)}}.multi-select-count{color:var(--color-gray-500)}.dark .multi-select-count{color:var(--color-gray-400)}.multi-select-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);min-width:calc(var(--spacing) * 64);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--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);position:absolute}.dark .multi-select-dropdown{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.multi-select-dropdown.hidden{display:none}.multi-select-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .multi-select-header{border-color:var(--color-gray-700);color:var(--color-white)}.multi-select-hint{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--color-gray-500)}.dark .multi-select-hint{color:var(--color-gray-400)}.multi-select-items{max-height:calc(var(--spacing) * 80);padding-block:calc(var(--spacing) * 2);overflow:auto}.multi-select-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);display:flex}@media(hover:hover){.multi-select-item:hover{background-color:var(--color-gray-50)}}.dark .multi-select-item{color:var(--color-gray-300)}@media(hover:hover){.dark .multi-select-item:hover{background-color:var(--color-gray-700)}}.multi-select-item.disabled{cursor:not-allowed;opacity:.5}.multi-select-item.disabled:hover,.dark .multi-select-item.disabled:hover{background-color:#0000}.select-ghost{display:inline-block;position:relative}.select-ghost-trigger{align-items:center;gap:var(--spacing);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);cursor:pointer;border-radius:var(--radius-lg);color:var(--color-gray-700);background-color:#0000;display:inline-flex}@media(hover:hover){.select-ghost-trigger:hover{background-color:var(--color-gray-100)}}.dark .select-ghost-trigger{color:var(--color-gray-300)}@media(hover:hover){.dark .select-ghost-trigger:hover{background-color:var(--color-gray-700)}}.select-ghost-dropdown{z-index:50;margin-top:var(--spacing);min-width:calc(var(--spacing) * 32);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-block:var(--spacing);--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);position:absolute}.dark .select-ghost-dropdown{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.select-ghost-dropdown.hidden{display:none}.select-ghost-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);justify-content:space-between;align-items:center;display:flex}@media(hover:hover){.select-ghost-option:hover{background-color:var(--color-gray-50)}}.dark .select-ghost-option{color:var(--color-gray-300)}@media(hover:hover){.dark .select-ghost-option:hover{background-color:var(--color-gray-700)}}.select-ghost-option.selected{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-600)}.dark .select-ghost-option.selected{color:var(--color-blue-400)}.select-ghost-check{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-blue-600)}.dark .select-ghost-check{color:var(--color-blue-400)}.searchable-dropdown{display:inline-block;position:relative}.searchable-dropdown-trigger{border-radius:var(--radius-xl);background-color:var(--color-gray-100);min-width:200px;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);cursor:pointer;justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;display:flex}.searchable-dropdown-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .searchable-dropdown-trigger{background-color:var(--color-gray-700);color:var(--color-gray-200)}.searchable-dropdown-trigger:disabled{cursor:not-allowed;opacity:.5}.searchable-dropdown-trigger-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;transition-duration:.2s}.dark .searchable-dropdown-trigger-icon{color:var(--color-gray-500)}.searchable-dropdown-trigger-icon.open{rotate:180deg}.searchable-dropdown-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100%;min-width:280px;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute;overflow:hidden}.dark .searchable-dropdown-dropdown{border-color:var(--color-gray-600);background-color:var(--color-gray-800)}.searchable-dropdown-dropdown.hidden{display:none}.searchable-dropdown-search{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding:calc(var(--spacing) * 3)}.dark .searchable-dropdown-search{border-color:var(--color-gray-700)}.searchable-dropdown-search-input{border-radius:var(--radius-lg);background-color:var(--color-gray-50);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.searchable-dropdown-search-input::placeholder{color:var(--color-gray-400)}.searchable-dropdown-search-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;border-color:#0000;outline-style:none}.dark .searchable-dropdown-search-input{border-color:var(--color-gray-600);background-color:var(--color-gray-700);color:var(--color-white)}.dark .searchable-dropdown-search-input::placeholder{color:var(--color-gray-500)}.searchable-dropdown-items{max-height:calc(var(--spacing) * 80);padding-block:calc(var(--spacing) * 2);overflow:auto}.searchable-dropdown-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;align-items:flex-start;gap:calc(var(--spacing) * 3);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));display:flex}@media(hover:hover){.searchable-dropdown-option:hover{background-color:var(--color-gray-100)}.dark .searchable-dropdown-option:hover{background-color:var(--color-gray-700)}}.searchable-dropdown-option:disabled{cursor:not-allowed;opacity:.5}.searchable-dropdown-check-area{margin-top:calc(var(--spacing) * .5);height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);flex-shrink:0}.searchable-dropdown-check{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-blue-600)}.dark .searchable-dropdown-check{color:var(--color-blue-400)}.searchable-dropdown-option-content{flex:1;min-width:0}.searchable-dropdown-option-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .searchable-dropdown-option-label{color:var(--color-blue-400)}.searchable-dropdown-option.selected .searchable-dropdown-option-label{color:var(--color-blue-600)}.dark :is(.searchable-dropdown-option.selected .searchable-dropdown-option-label){color:var(--color-blue-400)}.searchable-dropdown-option-description{margin-top:calc(var(--spacing) * .5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .searchable-dropdown-option-description{color:var(--color-gray-400)}.searchable-dropdown-message{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 6);text-align:center;color:var(--color-gray-500)}.dark .searchable-dropdown-message{color:var(--color-gray-400)}.stat-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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);transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.stat-card:hover{--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)}}.dark .stat-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.stats-icon{border-radius:var(--radius-lg);padding:calc(var(--spacing) * 3)}.stats-icon-blue{background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .stats-icon-blue{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-blue{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .stats-icon-blue{color:var(--color-blue-400)}.stats-icon-green{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .stats-icon-green{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-green{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .stats-icon-green{color:var(--color-green-400)}.stats-icon-purple{background-color:var(--color-purple-50);color:var(--color-purple-600)}.dark .stats-icon-purple{background-color:#59168b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-purple{background-color:color-mix(in oklab,var(--color-purple-900) 20%,transparent)}}.dark .stats-icon-purple{color:var(--color-purple-400)}.stats-icon-orange{background-color:var(--color-orange-50);color:var(--color-orange-600)}.dark .stats-icon-orange{background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-orange{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .stats-icon-orange{color:var(--color-orange-400)}.stats-change{align-items:center;gap:var(--spacing);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);display:flex}.stats-trend-up{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .stats-trend-up{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-trend-up{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .stats-trend-up{color:var(--color-green-400)}.stats-trend-down{background-color:var(--color-red-50);color:var(--color-red-600)}.dark .stats-trend-down{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .stats-trend-down{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .stats-trend-down{color:var(--color-red-400)}.stats-trend-neutral{background-color:var(--color-gray-50);color:var(--color-gray-600)}.dark .stats-trend-neutral{background-color:var(--color-gray-700);color:var(--color-gray-400)}.stats-value-lg{margin-bottom:var(--spacing);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-gray-900)}.dark .stats-value-lg{color:var(--color-white)}.stats-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .stats-label{color:var(--color-gray-400)}.stats-sublabel{align-items:center;gap:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:flex}.dark .stats-sublabel{color:var(--color-gray-400)}:where(.tree>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}.tree-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);display:flex}@media(hover:hover){.tree-item:hover{background-color:var(--color-gray-50)}.dark .tree-item:hover{background-color:var(--color-gray-800)}}.tree-item.selected{background-color:var(--color-blue-50)}.dark .tree-item.selected{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tree-item.selected{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.tree-item-content{align-items:center;gap:calc(var(--spacing) * 2);flex:1;min-width:0;display:flex}.tree-item-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);flex-shrink:0}.dark .tree-item-icon{color:var(--color-gray-500)}.tree-item-label{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);overflow:hidden}.dark .tree-item-label{color:var(--color-gray-300)}.tree-expand-btn{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6);color:var(--color-gray-400);border-radius:.25rem;justify-content:center;align-items:center;display:flex}@media(hover:hover){.tree-expand-btn:hover{background-color:var(--color-gray-100)}}.dark .tree-expand-btn{color:var(--color-gray-500)}@media(hover:hover){.dark .tree-expand-btn:hover{background-color:var(--color-gray-700)}}.tree-expand-btn.hidden{visibility:hidden}.tree-children{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-children{border-color:var(--color-gray-700)}.tree-level-1{border-left-style:var(--tw-border-style);border-left-width:0;margin-left:0;padding-left:0}.tree-level-2{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-2{border-color:var(--color-gray-700)}.tree-level-3{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-3{border-color:var(--color-gray-700)}.tree-level-4{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-4{border-color:var(--color-gray-700)}.category-item{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);justify-content:space-between;align-items:center;display:flex}.dark .category-item{color:var(--color-white)}@media(hover:hover){.category-item:hover{background-color:var(--color-gray-50)}.dark .category-item:hover{background-color:var(--color-gray-700)}}.category-item[data-selected=true]{background-color:var(--color-blue-600);color:var(--color-white)}.category-item[data-selected=true]:hover{background-color:var(--color-blue-700)}.category-item-mobile{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .category-item-mobile{color:var(--color-white)}.category-item-mobile[data-selected=true]{background-color:var(--color-blue-50)}.dark .category-item-mobile[data-selected=true]{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .category-item-mobile[data-selected=true]{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.stats-card{height:calc(var(--spacing) * 36);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-100);background-color:var(--color-white);padding:calc(var(--spacing) * 5);flex-direction:column;justify-content:space-between;display:flex}.dark .stats-card{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}.dashboard-panel{border-radius:var(--radius-3xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-100);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--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)}.dark .dashboard-panel{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}.stats-value{margin-bottom:calc(var(--spacing) * 2);--tw-leading:1;--tw-font-weight:var(--font-weight-bold);font-size:2rem;line-height:1;font-weight:var(--font-weight-bold);color:var(--color-slate-900)}.dark .stats-value{color:var(--color-white)}.stats-trend{align-items:center;gap:var(--spacing);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);color:var(--color-emerald-500);display:flex}.status-active{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-green-100);color:var(--color-green-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-active{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .status-active{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .status-active{color:var(--color-green-400)}.status-inactive{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-gray-100);color:var(--color-gray-600);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-inactive{background-color:var(--color-gray-700);color:var(--color-gray-400)}.status-pending{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-yellow-100);color:var(--color-yellow-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-pending{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .status-pending{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .status-pending{color:var(--color-yellow-400)}.status-error{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-red-100);color:var(--color-red-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-error{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .status-error{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .status-error{color:var(--color-red-400)}.list-item{align-items:center;gap:calc(var(--spacing) * 3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-block:calc(var(--spacing) * 3);display:flex}.dark .list-item{border-color:var(--color-gray-700)}.list-item:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.list-item-hover{margin-inline:calc(var(--spacing) * -3);align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);display:flex}@media(hover:hover){.list-item-hover:hover{background-color:var(--color-gray-50)}.dark .list-item-hover:hover{background-color:var(--color-gray-800)}}.empty-state{padding-block:calc(var(--spacing) * 12);text-align:center;flex-direction:column;justify-content:center;align-items:center;display:flex}.empty-state-icon{margin-bottom:calc(var(--spacing) * 4);height:calc(var(--spacing) * 16);width:calc(var(--spacing) * 16);color:var(--color-gray-300)}.dark .empty-state-icon{color:var(--color-gray-600)}.empty-state-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .empty-state-title{color:var(--color-white)}.empty-state-description{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .empty-state-description{color:var(--color-gray-400)}.page-header{margin-bottom:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.page-header-padded{margin-bottom:calc(var(--spacing) * 6);padding-inline:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.page-header-fullbleed{margin-inline:calc(var(--spacing) * -6);margin-bottom:calc(var(--spacing) * 6);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.dark .page-header-fullbleed{background-color:var(--color-gray-900)}.page-actions{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.section-card{margin-top:calc(var(--spacing) * 6);scroll-margin-top:calc(var(--spacing) * 32);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6)}.dark .section-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.section-header{margin-bottom:calc(var(--spacing) * 6);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-header{color:var(--color-white)}.info-row{margin-bottom:calc(var(--spacing) * 4);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.action-buttons{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.form-label{margin-bottom:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900);display:block}.dark .form-label{color:var(--color-white)}.form-label-muted{margin-bottom:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);display:block}.dark .form-label-muted{color:var(--color-gray-400)}.form-hint{margin-top:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .form-hint{color:var(--color-gray-400)}.text-value{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .text-value{color:var(--color-white)}.text-link{cursor:pointer;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-600)}@media(hover:hover){.text-link:hover{color:var(--color-blue-700)}}.dark .text-link{color:var(--color-blue-400)}@media(hover:hover){.dark .text-link:hover{color:var(--color-blue-300)}}.icon-xs{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3)}.icon-sm{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4)}.icon-md{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5)}.icon-lg{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6)}.icon-xl{height:calc(var(--spacing) * 8);width:calc(var(--spacing) * 8)}.icon-2xl{height:calc(var(--spacing) * 10);width:calc(var(--spacing) * 10)}.icon-action{cursor:pointer;color:var(--color-gray-400)}@media(hover:hover){.icon-action:hover{color:var(--color-gray-600)}}.dark .icon-action{color:var(--color-gray-500)}@media(hover:hover){.dark .icon-action:hover{color:var(--color-gray-300)}}.page-container{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 6)}.dark .page-container{background-color:var(--color-gray-900)}.content-area{max-width:var(--container-7xl);margin-inline:auto}.content-panel{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6)}.dark .content-panel{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.flex-row-center{align-items:center;display:flex}.flex-row-between{justify-content:space-between;align-items:center;display:flex}.flex-row-end{justify-content:flex-end;align-items:center;display:flex}.flex-col-start{flex-direction:column;align-items:flex-start;display:flex}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-center{align-items:center;display:flex}.flex-end{justify-content:flex-end;align-items:center;display:flex}.flex-wrap-center{flex-wrap:wrap;align-items:center;display:flex}.inline-flex-center{align-items:center;display:inline-flex}.row-stack{flex-direction:column;display:flex}:where(.row-stack>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-color:var(--color-gray-200)}:where(.dark .row-stack>:not(:last-child)){border-color:var(--color-gray-700)}.row-stack>*{padding-block:calc(var(--spacing) * 2)}.row-stack>:first-child{padding-top:0}.row-stack>:last-child{padding-bottom:0}.stack-flush{flex-direction:column;gap:0;display:flex}.stack-tight{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.card-stack{margin-top:calc(var(--spacing) * 3);gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.card-stack-item{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .card-stack-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.card-stack-item{flex-direction:column;display:flex}.option-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);overflow:hidden}.dark .option-card{border-color:var(--color-gray-700);background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .option-card{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.option-card>*{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3)}.option-card>*+*{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200)}.dark :is(.option-card>*+*){border-color:var(--color-gray-700)}.alert-info{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-200);background-color:var(--color-blue-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-900);display:flex}.dark .alert-info{border-color:var(--color-blue-800);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .alert-info{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .alert-info{color:var(--color-blue-100)}.alert-warning{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-900);display:flex}.dark .alert-warning{border-color:var(--color-yellow-800);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-warning{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .alert-warning{color:var(--color-yellow-100)}.alert-danger{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-900);display:flex}.dark .alert-danger{border-color:var(--color-red-800);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-danger{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .alert-danger{color:var(--color-red-100)}.alert-success{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-200);background-color:var(--color-green-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-900);display:flex}.dark .alert-success{border-color:var(--color-green-800);background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .alert-success{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .alert-success{color:var(--color-green-100)}.alert-info:has(+.row-stack),.alert-warning:has(+.row-stack),.alert-danger:has(+.row-stack),.alert-success:has(+.row-stack),.alert-error:has(+.row-stack),.alert-info:has(+.toggle-group),.alert-warning:has(+.toggle-group),.alert-danger:has(+.toggle-group),.alert-success:has(+.toggle-group),.alert-error:has(+.toggle-group),.alert-info:has(+.grid-2col-responsive),.alert-warning:has(+.grid-2col-responsive),.alert-danger:has(+.grid-2col-responsive),.alert-success:has(+.grid-2col-responsive),.alert-error:has(+.grid-2col-responsive){margin-bottom:calc(var(--spacing) * 4)}.row-stack>.alert-info,.row-stack>.alert-warning,.row-stack>.alert-danger,.row-stack>.alert-success,.row-stack>.alert-error{padding-top:calc(var(--spacing) * 3);padding-bottom:calc(var(--spacing) * 3)}.row-stack>*+.alert-info,.row-stack>*+.alert-warning,.row-stack>*+.alert-danger,.row-stack>*+.alert-success,.row-stack>*+.alert-error{margin-top:calc(var(--spacing) * 3)}.sub-tab-nav{gap:var(--spacing);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 6);display:flex}@media(min-width:40rem){.sub-tab-nav{padding-inline:calc(var(--spacing) * 8)}}@media(min-width:64rem){.sub-tab-nav{padding-inline:calc(var(--spacing) * 10)}}.dark .sub-tab-nav{background-color:var(--color-gray-800)}.section-header{margin-bottom:calc(var(--spacing) * 6)}.section-header>h3{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark :is(.section-header>h3){color:var(--color-white)}.section-header>p{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark :is(.section-header>p){color:var(--color-gray-400)}.toggle-group{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);overflow:hidden}.dark .toggle-group{border-color:var(--color-gray-700)}.section-header-row{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);background-color:var(--color-gray-50);display:flex}.dark .section-header-row{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .section-header-row{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.section-header-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200)}.dark .section-header-row{border-color:var(--color-gray-700)}.section-header-row{cursor:pointer;-webkit-user-select:none;user-select:none}.list-row-hover{background-color:var(--color-white)}.dark .list-row-hover{background-color:var(--color-gray-800)}@media(hover:hover){.list-row-hover:hover{background-color:var(--color-gray-50)}.dark .list-row-hover:hover{background-color:var(--color-gray-700)}}.list-row-hover{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);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))}.text-hint{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-400)}.dark .text-hint{color:var(--color-gray-500)}.text-info-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-blue-700)}.dark .text-info-soft{color:var(--color-blue-300)}.text-warning-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-yellow-700)}.dark .text-warning-soft{color:var(--color-yellow-300)}.text-danger-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-700)}.dark .text-danger-soft{color:var(--color-red-300)}.text-success-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-green-700)}.dark .text-success-soft{color:var(--color-green-300)}.checkbox-aligned{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-gray-900);border-radius:.25rem}.checkbox-aligned:focus{--tw-ring-color:var(--color-gray-500)}.dark .checkbox-aligned{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.checkbox-aligned{margin-top:calc(var(--spacing) * .5);flex-shrink:0}:where(.divider-list>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-color:var(--color-gray-200)}:where(.dark .divider-list>:not(:last-child)){border-color:var(--color-gray-700)}.modal-footer-buttons{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.spinner-sm{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);animation:var(--animate-spin)}.text-required{margin-left:var(--spacing);color:var(--color-red-500)}.dark .text-required{color:var(--color-red-400)}.form-label-block{margin-bottom:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);display:block}.dark .form-label-block{color:var(--color-gray-300)}.form-sub-label{margin-bottom:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:block}.dark .form-sub-label{color:var(--color-gray-400)}.text-field-hint{margin-top:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--color-gray-500)}.dark .text-field-hint{color:var(--color-gray-400)}.section-heading-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-heading-md{color:var(--color-white)}.grid-3col-responsive{align-items:flex-start;gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media(min-width:64rem){.grid-3col-responsive{grid-template-columns:repeat(3,minmax(0,1fr))}}.text-error-soft{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-700)}.dark .text-error-soft{color:var(--color-red-300)}.text-error-strong{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-red-800)}.dark .text-error-strong{color:var(--color-red-200)}.row-content-indent{padding-left:calc(var(--spacing) * 6)}.flex-start{align-items:flex-start;display:flex}.inline-clickable{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);display:inline-flex}.grid-2col{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.empty-state-cell{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 4);text-align:center}.form-error-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-500)}.dark .form-error-xs{color:var(--color-red-400)}.text-page-heading{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-gray-900)}.dark .text-page-heading{color:var(--color-white)}.text-table-header{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-gray-500);text-transform:uppercase}.dark .text-table-header{color:var(--color-gray-400)}.panel-header-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4)}.dark .panel-header-row{border-color:var(--color-gray-700)}.grid-2col-responsive{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media(min-width:48rem){.grid-2col-responsive{grid-template-columns:repeat(2,minmax(0,1fr))}}.panel-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-block:calc(var(--spacing) * 3)}.dark .panel-header{border-color:var(--color-gray-700)}.gap-sm{gap:calc(var(--spacing) * 2)}.gap-md{gap:calc(var(--spacing) * 4)}.gap-lg{gap:calc(var(--spacing) * 6)}.mb-sm{margin-bottom:calc(var(--spacing) * 2)}.mb-md{margin-bottom:calc(var(--spacing) * 4)}.mb-lg{margin-bottom:calc(var(--spacing) * 6)}.surface{background-color:var(--color-white)}.dark .surface{background-color:var(--color-gray-800)}.surface-alt{background-color:var(--color-gray-50)}.dark .surface-alt{background-color:var(--color-gray-900)}.card-border{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.dark .card-border{border-color:var(--color-gray-700)}.card-border-xl{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.dark .card-border-xl{border-color:var(--color-gray-700)}.border-light{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300)}.dark .border-light{border-color:var(--color-gray-600)}.disabled-state:disabled{cursor:not-allowed;opacity:.5}.focus-ring:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dark .focus-ring:focus{--tw-ring-offset-color:var(--color-gray-800)}.focus-ring-inset:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}@media(hover:hover){.hover-bg:hover{background-color:var(--color-gray-100)}.dark .hover-bg:hover{background-color:var(--color-gray-700)}.hover-bg-light:hover{background-color:var(--color-gray-50)}.dark .hover-bg-light:hover{background-color:var(--color-gray-800)}}.btn-base{border-radius:var(--radius-lg);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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:.2s;justify-content:center;align-items:center;transition-duration:.2s;display:inline-flex}.btn-base:disabled{cursor:not-allowed;opacity:.5}.btn-size-sm{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.btn-size-md{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.btn-size-lg{padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 3);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.badge-base{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-size-sm{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5)}.badge-size-md{padding-inline:calc(var(--spacing) * 2.5);padding-block:var(--spacing)}.shadow-card{--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-dropdown{--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)}@media(hover:hover){.shadow-hover:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}}.page-title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}@media(min-width:40rem){.page-title{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}}.dark .page-title{color:var(--color-white)}.page-description{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .page-description{color:var(--color-gray-400)}.page-action-primary{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));background-color:var(--color-blue-600);color:var(--color-white);display:flex}@media(hover:hover){.page-action-primary:hover{background-color:var(--color-blue-700)}}.page-action-secondary{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));background-color:var(--color-gray-200);color:var(--color-gray-900);display:flex}@media(hover:hover){.page-action-secondary:hover{background-color:var(--color-gray-300)}}.dark .page-action-secondary{background-color:var(--color-gray-700);color:var(--color-gray-200)}@media(hover:hover){.dark .page-action-secondary:hover{background-color:var(--color-gray-600)}}.page-action-danger{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));background-color:var(--color-red-600);color:var(--color-white);display:flex}@media(hover:hover){.page-action-danger:hover{background-color:var(--color-red-700)}}.page-action-ghost{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);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));color:var(--color-gray-500);display:flex}@media(hover:hover){.page-action-ghost:hover{background-color:var(--color-gray-100);color:var(--color-gray-700)}}.dark .page-action-ghost{color:var(--color-gray-400)}@media(hover:hover){.dark .page-action-ghost:hover{background-color:var(--color-gray-800);color:var(--color-gray-200)}}.tab-container{margin-bottom:calc(var(--spacing) * 6);gap:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);display:flex}.dark .tab-container{border-color:var(--color-gray-700)}.tab-btn{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px;margin-bottom:-1px;display:flex}@media(hover:hover){.tab-btn:hover{color:var(--color-gray-900)}}.dark .tab-btn{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn:hover{color:var(--color-gray-200)}}.tab-btn-active{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));border-bottom-width:2px;border-color:var(--color-blue-600);color:var(--color-blue-600);margin-bottom:-1px;display:flex}.dark .tab-btn-active{color:var(--color-blue-400)}.tab-badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);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);background-color:var(--color-gray-100);color:var(--color-gray-600);border-radius:3.40282e38px}.dark .tab-badge{background-color:var(--color-gray-700);color:var(--color-gray-400)}.tab-badge-active{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);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);background-color:var(--color-blue-100);color:var(--color-blue-600);border-radius:3.40282e38px}.dark .tab-badge-active{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tab-badge-active{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .tab-badge-active{color:var(--color-blue-400)}.sticky-tab-nav{z-index:40;margin-inline:calc(var(--spacing) * -6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 6);position:sticky;top:0}.dark .sticky-tab-nav{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-tab-nav-responsive{z-index:40;margin-inline:calc(var(--spacing) * -4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 4);position:sticky;top:0}@media(min-width:40rem){.sticky-tab-nav-responsive{margin-inline:calc(var(--spacing) * -6);padding-inline:calc(var(--spacing) * 6)}}@media(min-width:64rem){.sticky-tab-nav-responsive{margin-inline:calc(var(--spacing) * -8);padding-inline:calc(var(--spacing) * 8)}}.dark .sticky-tab-nav-responsive{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-section-header{z-index:40;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);position:sticky;top:0}.dark .sticky-section-header{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-footer-buttons{z-index:10;margin-top:calc(var(--spacing) * 6);justify-content:flex-end;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);display:flex;position:sticky;bottom:0}.dark .sticky-footer-buttons{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.sticky-footer-buttons-between{z-index:10;margin-top:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);display:flex;position:sticky;bottom:0}.dark .sticky-footer-buttons-between{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.footer-buttons-between{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);display:flex}.dark .footer-buttons-between{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.tab-content{padding-top:calc(var(--spacing) * 6)}.admin-page-content{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 6)}.dark .admin-page-content{background-color:var(--color-gray-900)}.admin-page-content-compact{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 4)}.dark .admin-page-content-compact{background-color:var(--color-gray-900)}.admin-page-content-responsive{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 4)}@media(min-width:40rem){.admin-page-content-responsive{padding:calc(var(--spacing) * 6)}}@media(min-width:64rem){.admin-page-content-responsive{padding:calc(var(--spacing) * 8)}}.dark .admin-page-content-responsive{background-color:var(--color-gray-900)}.admin-page-content-responsive-fluid{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4)}@media(min-width:40rem){.admin-page-content-responsive-fluid{padding:calc(var(--spacing) * 6)}}@media(min-width:64rem){.admin-page-content-responsive-fluid{padding:calc(var(--spacing) * 8)}}.dark .admin-page-content-responsive-fluid{background-color:var(--color-gray-900)}.admin-page-content-fluid{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 6)}.dark .admin-page-content-fluid{background-color:var(--color-gray-900)}.admin-page-content-fluid-compact{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4)}.dark .admin-page-content-fluid-compact{background-color:var(--color-gray-900)}.admin-page-content-viewport{background-color:var(--color-gray-50);height:calc(100vh - 64px);padding:calc(var(--spacing) * 6);overflow:hidden}.dark .admin-page-content-viewport{background-color:var(--color-gray-900)}.admin-page-content-viewport-compact{background-color:var(--color-gray-50);height:calc(100vh - 64px);padding:calc(var(--spacing) * 4);overflow:hidden}.dark .admin-page-content-viewport-compact{background-color:var(--color-gray-900)}.bulk-actions-bar{margin-bottom:calc(var(--spacing) * 4);align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);padding:calc(var(--spacing) * 3);display:flex}.dark .bulk-actions-bar{border-color:var(--color-gray-600);background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .bulk-actions-bar{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}:where(.admin-sidebar-menu-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}.admin-sidebar-menu-list{padding-inline:calc(var(--spacing) * 4);padding-bottom:calc(var(--spacing) * 4)}.admin-sidebar-item{border-radius:var(--radius-lg);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);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));justify-content:space-between;align-items:center;display:flex}.admin-sidebar-subitem{border-radius:var(--radius-lg);width:100%;padding-block:6px;padding-right:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium);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));justify-content:space-between;align-items:center;padding-left:30px;display:flex}.admin-sidebar-item-active{background-color:var(--color-slate-100);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.dark .admin-sidebar-item-active{background-color:var(--color-slate-700);color:var(--color-white)}.admin-sidebar-item-active-parent{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.dark .admin-sidebar-item-active-parent{color:var(--color-white)}.admin-sidebar-item-default{color:var(--color-slate-500)}@media(hover:hover){.admin-sidebar-item-default:hover{background-color:var(--color-slate-50);color:var(--color-slate-900)}}.dark .admin-sidebar-item-default{color:var(--color-slate-400)}@media(hover:hover){.dark .admin-sidebar-item-default:hover{background-color:var(--color-slate-800);color:var(--color-white)}}.admin-sidebar-item-disabled{cursor:default;color:var(--color-slate-400)}.dark .admin-sidebar-item-disabled{color:var(--color-slate-500)}.admin-sidebar-icon-box{flex-shrink:0;justify-content:center;align-items:center;width:18px;height:18px;display:flex}.admin-sidebar-chevron-trail{align-items:center;gap:var(--spacing);display:flex}.admin-sidebar-item-content{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.admin-sidebar-module-icon{margin-right:calc(var(--spacing) * 2);height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);color:var(--color-slate-400);opacity:.6}.dark .admin-sidebar-module-icon{color:var(--color-slate-500)}.admin-sidebar-tree-line-vertical{z-index:0;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-slate-300);position:absolute;top:0;bottom:0;left:12px}.dark .admin-sidebar-tree-line-vertical{border-color:var(--color-slate-600)}.admin-sidebar-tree-line-curve{z-index:10;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-left-style:var(--tw-border-style);width:14px;height:50%;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));border-left-width:1px;border-color:var(--color-slate-300);border-bottom-left-radius:10px;position:absolute;top:0;left:12px}@media(hover:hover){.admin-sidebar-tree-line-curve:is(:where(.group):hover *){border-color:var(--color-blue-400)}}.dark .admin-sidebar-tree-line-curve{border-color:var(--color-slate-600)}@media(hover:hover){.dark .admin-sidebar-tree-line-curve:is(:where(.group):hover *){border-color:var(--color-blue-500)}}.admin-sidebar-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.dark .admin-sidebar-header{border-color:var(--color-slate-700)}.admin-sidebar-collapse-btn{border-radius:var(--radius-md);padding:calc(var(--spacing) * 2);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.admin-sidebar-collapse-btn:hover{background-color:var(--color-slate-100)}.dark .admin-sidebar-collapse-btn:hover{background-color:var(--color-slate-700)}}.tab-nav-default{gap:calc(var(--spacing) * 2);display:flex}.tab-nav-underline{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);gap:0;display:flex}.dark .tab-nav-underline{border-color:var(--color-gray-700)}.tab-btn-base{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}.tab-btn-default{border-bottom-style:var(--tw-border-style);color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px}@media(hover:hover){.tab-btn-default:hover{background-color:var(--color-gray-50);color:var(--color-gray-900)}}.dark .tab-btn-default{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-default:hover{background-color:var(--color-gray-700);color:var(--color-gray-200)}}.tab-btn-default-active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-blue-600);background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .tab-btn-default-active{border-color:var(--color-blue-400);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tab-btn-default-active{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .tab-btn-default-active{color:var(--color-blue-400)}.tab-btn-underline{border-bottom-style:var(--tw-border-style);color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px}@media(hover:hover){.tab-btn-underline:hover{border-color:var(--color-gray-300);color:var(--color-gray-900)}}.dark .tab-btn-underline{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-underline:hover{border-color:var(--color-gray-600);color:var(--color-gray-200)}}.tab-btn-underline-active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-blue-600);color:var(--color-blue-600)}.dark .tab-btn-underline-active{border-color:var(--color-blue-400);color:var(--color-blue-400)}.tab-btn-pills{border-radius:var(--radius-lg);color:var(--color-gray-600)}@media(hover:hover){.tab-btn-pills:hover{background-color:var(--color-gray-100)}}.dark .tab-btn-pills{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-pills:hover{background-color:var(--color-gray-700)}}.tab-btn-pills-active{border-radius:var(--radius-lg);background-color:var(--color-blue-600);color:var(--color-white)}.dark .tab-btn-pills-active{background-color:var(--color-blue-500)}.tab-btn-disabled{cursor:not-allowed;color:var(--color-gray-400);opacity:.5}.dark .tab-btn-disabled{color:var(--color-gray-600)}.toggle-container{align-items:flex-start;column-gap:calc(var(--spacing) * 2);flex-wrap:wrap;width:auto;display:inline-flex}.toggle-switch-wrapper{flex-shrink:0;align-items:center;display:inline-flex;position:relative}.toggle-switch-track{background-color:var(--color-gray-200);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));border-radius:3.40282e38px;position:relative}.toggle-switch-track:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + 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);--tw-ring-color:var(--color-blue-300);--tw-outline-style:none;outline-style:none}.toggle-switch-track:is(:where(.peer):checked~*){background-color:var(--color-blue-600)}.dark .toggle-switch-track{background-color:var(--color-gray-700)}.dark .toggle-switch-track:is(:where(.peer):focus~*){--tw-ring-color:var(--color-blue-800)}.dark .toggle-switch-track:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.toggle-switch-track:after{top:calc(var(--spacing) * .5);left:calc(var(--spacing) * .5);--tw-content:"";border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.dark .toggle-switch-track:after{content:var(--tw-content);border-color:var(--color-gray-600)}.toggle-switch-track:is(:where(.peer):checked~*):after{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y);content:var(--tw-content);border-color:var(--color-white)}.toggle-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .toggle-label{color:var(--color-white)}.toggle-description{margin-top:var(--spacing);width:100%;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .toggle-description{color:var(--color-gray-400)}.admin-footer{margin-top:calc(var(--spacing) * 6);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4)}.admin-footer-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}@media(min-width:48rem){.admin-footer-row{flex-direction:row}}.admin-footer-group{align-items:center;gap:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);display:flex}.dark .admin-footer-group{color:var(--color-gray-400)}.admin-footer-link-hover{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))}@media(hover:hover){.admin-footer-link-hover:hover{color:var(--color-blue-600)}.dark .admin-footer-link-hover:hover{color:var(--color-blue-400)}}.admin-footer-link{align-items:center;gap:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);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));display:flex}@media(hover:hover){.admin-footer-link:hover{color:var(--color-blue-600)}}.dark .admin-footer-link{color:var(--color-gray-400)}@media(hover:hover){.dark .admin-footer-link:hover{color:var(--color-blue-400)}}.responsive-grid{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.grid-cols-1-lg-3{gap:calc(var(--spacing) * 6);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.col-span-1-lg,.col-span-2-lg{grid-column:span 1/span 1}.modal-description{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .modal-description{color:var(--color-gray-400)}.toast-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.visible\!{visibility:visible!important}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-1{inset:var(--spacing)}.inset-2{inset:calc(var(--spacing) * 2)}.inset-3{inset:calc(var(--spacing) * 3)}.inset-4{inset:calc(var(--spacing) * 4)}.inset-5{inset:calc(var(--spacing) * 5)}.inset-6{inset:calc(var(--spacing) * 6)}.inset-8{inset:calc(var(--spacing) * 8)}.inset-auto{inset:auto}.inset-x-0{inset-inline:0}.inset-x-1{inset-inline:var(--spacing)}.inset-x-2{inset-inline:calc(var(--spacing) * 2)}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.inset-x-4{inset-inline:calc(var(--spacing) * 4)}.inset-x-auto{inset-inline:auto}.inset-y-0{inset-block:0}.inset-y-1{inset-block:var(--spacing)}.inset-y-2{inset-block:calc(var(--spacing) * 2)}.inset-y-3{inset-block:calc(var(--spacing) * 3)}.inset-y-4{inset-block:calc(var(--spacing) * 4)}.inset-y-auto{inset-block:auto}.-top-1{top:calc(var(--spacing) * -1)}.-top-2{top:calc(var(--spacing) * -2)}.-top-3{top:calc(var(--spacing) * -3)}.-top-4{top:calc(var(--spacing) * -4)}.top-0{top:0}.top-1{top:var(--spacing)}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-1\/2{top:50%}.top-1\/3{top:33.3333%}.top-1\/4{top:25%}.top-2{top:calc(var(--spacing) * 2)}.top-2\/3{top:66.6667%}.top-3{top:calc(var(--spacing) * 3)}.top-3\/4{top:75%}.top-4{top:calc(var(--spacing) * 4)}.top-5{top:calc(var(--spacing) * 5)}.top-6{top:calc(var(--spacing) * 6)}.top-8{top:calc(var(--spacing) * 8)}.top-10{top:calc(var(--spacing) * 10)}.top-12{top:calc(var(--spacing) * 12)}.top-16{top:calc(var(--spacing) * 16)}.top-auto{top:auto}.top-full{top:100%}.-right-1{right:calc(var(--spacing) * -1)}.-right-2{right:calc(var(--spacing) * -2)}.-right-3{right:calc(var(--spacing) * -3)}.-right-4{right:calc(var(--spacing) * -4)}.right-0{right:0}.right-1{right:var(--spacing)}.right-1\/2{right:50%}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.right-4{right:calc(var(--spacing) * 4)}.right-5{right:calc(var(--spacing) * 5)}.right-6{right:calc(var(--spacing) * 6)}.right-8{right:calc(var(--spacing) * 8)}.right-10{right:calc(var(--spacing) * 10)}.right-12{right:calc(var(--spacing) * 12)}.right-16{right:calc(var(--spacing) * 16)}.right-auto{right:auto}.right-full{right:100%}.-bottom-1{bottom:calc(var(--spacing) * -1)}.-bottom-2{bottom:calc(var(--spacing) * -2)}.-bottom-3{bottom:calc(var(--spacing) * -3)}.-bottom-4{bottom:calc(var(--spacing) * -4)}.bottom-0{bottom:0}.bottom-1{bottom:var(--spacing)}.bottom-1\/2{bottom:50%}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-4{bottom:calc(var(--spacing) * 4)}.bottom-5{bottom:calc(var(--spacing) * 5)}.bottom-6{bottom:calc(var(--spacing) * 6)}.bottom-8{bottom:calc(var(--spacing) * 8)}.bottom-10{bottom:calc(var(--spacing) * 10)}.bottom-12{bottom:calc(var(--spacing) * 12)}.bottom-16{bottom:calc(var(--spacing) * 16)}.bottom-auto{bottom:auto}.bottom-full{bottom:100%}.-left-1{left:calc(var(--spacing) * -1)}.-left-2{left:calc(var(--spacing) * -2)}.-left-3{left:calc(var(--spacing) * -3)}.-left-4{left:calc(var(--spacing) * -4)}.left-0{left:0}.left-1{left:var(--spacing)}.left-1\.5{left:calc(var(--spacing) * 1.5)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing) * 2)}.left-3{left:calc(var(--spacing) * 3)}.left-4{left:calc(var(--spacing) * 4)}.left-5{left:calc(var(--spacing) * 5)}.left-6{left:calc(var(--spacing) * 6)}.left-8{left:calc(var(--spacing) * 8)}.left-10{left:calc(var(--spacing) * 10)}.left-12{left:calc(var(--spacing) * 12)}.left-16{left:calc(var(--spacing) * 16)}.left-auto{left:auto}.left-full{left:100%}.isolate{isolation:isolate}.isolation-auto{isolation:auto}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[999\]{z-index:999}.z-\[9999\]{z-index:9999}.z-auto{z-index:auto}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-first{order:-9999}.order-last{order:9999}.order-none{order:0}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-4{grid-column:span 4/span 4}.col-span-5{grid-column:span 5/span 5}.col-span-6{grid-column:span 6/span 6}.col-span-7{grid-column:span 7/span 7}.col-span-8{grid-column:span 8/span 8}.col-span-9{grid-column:span 9/span 9}.col-span-10{grid-column:span 10/span 10}.col-span-11{grid-column:span 11/span 11}.col-span-12{grid-column:span 12/span 12}.col-span-full{grid-column:1/-1}.col-start-1{grid-column-start:1}.col-start-2{grid-column-start:2}.col-start-3{grid-column-start:3}.col-start-4{grid-column-start:4}.col-start-5{grid-column-start:5}.col-start-6{grid-column-start:6}.col-start-7{grid-column-start:7}.col-start-8{grid-column-start:8}.col-start-9{grid-column-start:9}.col-start-10{grid-column-start:10}.col-start-11{grid-column-start:11}.col-start-12{grid-column-start:12}.col-start-13{grid-column-start:13}.col-end-1{grid-column-end:1}.col-end-2{grid-column-end:2}.col-end-3{grid-column-end:3}.col-end-4{grid-column-end:4}.col-end-5{grid-column-end:5}.col-end-6{grid-column-end:6}.col-end-7{grid-column-end:7}.col-end-8{grid-column-end:8}.col-end-9{grid-column-end:9}.col-end-10{grid-column-end:10}.col-end-11{grid-column-end:11}.col-end-12{grid-column-end:12}.col-end-13{grid-column-end:13}.row-span-1{grid-row:span 1/span 1}.row-span-2{grid-row:span 2/span 2}.row-span-3{grid-row:span 3/span 3}.row-span-4{grid-row:span 4/span 4}.row-span-5{grid-row:span 5/span 5}.row-span-6{grid-row:span 6/span 6}.row-span-full{grid-row:1/-1}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.row-start-3{grid-row-start:3}.row-start-4{grid-row-start:4}.row-start-5{grid-row-start:5}.row-start-6{grid-row-start:6}.row-start-7{grid-row-start:7}.row-end-1{grid-row-end:1}.row-end-2{grid-row-end:2}.row-end-3{grid-row-end:3}.row-end-4{grid-row-end:4}.row-end-5{grid-row-end:5}.row-end-6{grid-row-end:6}.row-end-7{grid-row-end:7}.float-end{float:inline-end}.float-left{float:left}.float-none{float:none}.float-right{float:right}.float-start{float:inline-start}.clear-both{clear:both}.clear-end{clear:inline-end}.clear-left{clear:left}.clear-none{clear:none}.clear-right{clear:right}.clear-start{clear:inline-start}.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:0!important}.\!m-1{margin:var(--spacing)!important}.\!m-2{margin:calc(var(--spacing) * 2)!important}.\!m-3{margin:calc(var(--spacing) * 3)!important}.\!m-4{margin:calc(var(--spacing) * 4)!important}.\!m-5{margin:calc(var(--spacing) * 5)!important}.\!m-6{margin:calc(var(--spacing) * 6)!important}.\!m-8{margin:calc(var(--spacing) * 8)!important}.\!m-10{margin:calc(var(--spacing) * 10)!important}.\!m-12{margin:calc(var(--spacing) * 12)!important}.\!m-16{margin:calc(var(--spacing) * 16)!important}.\!m-auto{margin:auto!important}.-m-1{margin:calc(var(--spacing) * -1)}.-m-2{margin:calc(var(--spacing) * -2)}.-m-3{margin:calc(var(--spacing) * -3)}.-m-4{margin:calc(var(--spacing) * -4)}.-m-5{margin:calc(var(--spacing) * -5)}.-m-6{margin:calc(var(--spacing) * -6)}.-m-8{margin:calc(var(--spacing) * -8)}.m-0{margin:0}.m-1{margin:var(--spacing)}.m-2{margin:calc(var(--spacing) * 2)}.m-3{margin:calc(var(--spacing) * 3)}.m-4{margin:calc(var(--spacing) * 4)}.m-5{margin:calc(var(--spacing) * 5)}.m-6{margin:calc(var(--spacing) * 6)}.m-8{margin:calc(var(--spacing) * 8)}.m-10{margin:calc(var(--spacing) * 10)}.m-12{margin:calc(var(--spacing) * 12)}.m-16{margin:calc(var(--spacing) * 16)}.m-auto{margin:auto}.\!mx-0{margin-inline:0!important}.\!mx-1{margin-inline:var(--spacing)!important}.\!mx-2{margin-inline:calc(var(--spacing) * 2)!important}.\!mx-3{margin-inline:calc(var(--spacing) * 3)!important}.\!mx-4{margin-inline:calc(var(--spacing) * 4)!important}.\!mx-5{margin-inline:calc(var(--spacing) * 5)!important}.\!mx-6{margin-inline:calc(var(--spacing) * 6)!important}.\!mx-8{margin-inline:calc(var(--spacing) * 8)!important}.\!mx-10{margin-inline:calc(var(--spacing) * 10)!important}.\!mx-12{margin-inline:calc(var(--spacing) * 12)!important}.\!mx-16{margin-inline:calc(var(--spacing) * 16)!important}.\!mx-auto{margin-inline:auto!important}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.-mx-2{margin-inline:calc(var(--spacing) * -2)}.-mx-3{margin-inline:calc(var(--spacing) * -3)}.-mx-4{margin-inline:calc(var(--spacing) * -4)}.-mx-5{margin-inline:calc(var(--spacing) * -5)}.-mx-6{margin-inline:calc(var(--spacing) * -6)}.-mx-8{margin-inline:calc(var(--spacing) * -8)}.mx-0{margin-inline:0}.mx-1{margin-inline:var(--spacing)}.mx-1\.5{margin-inline:calc(var(--spacing) * 1.5)}.mx-2{margin-inline:calc(var(--spacing) * 2)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-5{margin-inline:calc(var(--spacing) * 5)}.mx-6{margin-inline:calc(var(--spacing) * 6)}.mx-8{margin-inline:calc(var(--spacing) * 8)}.mx-10{margin-inline:calc(var(--spacing) * 10)}.mx-12{margin-inline:calc(var(--spacing) * 12)}.mx-16{margin-inline:calc(var(--spacing) * 16)}.mx-auto{margin-inline:auto}.\!my-0{margin-block:0!important}.\!my-1{margin-block:var(--spacing)!important}.\!my-2{margin-block:calc(var(--spacing) * 2)!important}.\!my-3{margin-block:calc(var(--spacing) * 3)!important}.\!my-4{margin-block:calc(var(--spacing) * 4)!important}.\!my-5{margin-block:calc(var(--spacing) * 5)!important}.\!my-6{margin-block:calc(var(--spacing) * 6)!important}.\!my-8{margin-block:calc(var(--spacing) * 8)!important}.\!my-10{margin-block:calc(var(--spacing) * 10)!important}.\!my-12{margin-block:calc(var(--spacing) * 12)!important}.\!my-16{margin-block:calc(var(--spacing) * 16)!important}.\!my-auto{margin-block:auto!important}.-my-1{margin-block:calc(var(--spacing) * -1)}.-my-2{margin-block:calc(var(--spacing) * -2)}.-my-3{margin-block:calc(var(--spacing) * -3)}.-my-4{margin-block:calc(var(--spacing) * -4)}.-my-5{margin-block:calc(var(--spacing) * -5)}.-my-6{margin-block:calc(var(--spacing) * -6)}.-my-8{margin-block:calc(var(--spacing) * -8)}.my-0{margin-block:0}.my-1{margin-block:var(--spacing)}.my-2{margin-block:calc(var(--spacing) * 2)}.my-3{margin-block:calc(var(--spacing) * 3)}.my-4{margin-block:calc(var(--spacing) * 4)}.my-5{margin-block:calc(var(--spacing) * 5)}.my-6{margin-block:calc(var(--spacing) * 6)}.my-8{margin-block:calc(var(--spacing) * 8)}.my-10{margin-block:calc(var(--spacing) * 10)}.my-12{margin-block:calc(var(--spacing) * 12)}.my-16{margin-block:calc(var(--spacing) * 16)}.my-auto{margin-block:auto}.-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-5{margin-top:calc(var(--spacing) * -5)}.-mt-6{margin-top:calc(var(--spacing) * -6)}.-mt-8{margin-top:calc(var(--spacing) * -8)}.mt-0{margin-top:0}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.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-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-16{margin-top:calc(var(--spacing) * 16)}.mt-auto{margin-top:auto}.-mr-1{margin-right:calc(var(--spacing) * -1)}.-mr-2{margin-right:calc(var(--spacing) * -2)}.-mr-3{margin-right:calc(var(--spacing) * -3)}.-mr-4{margin-right:calc(var(--spacing) * -4)}.-mr-5{margin-right:calc(var(--spacing) * -5)}.-mr-6{margin-right:calc(var(--spacing) * -6)}.-mr-8{margin-right:calc(var(--spacing) * -8)}.mr-0{margin-right:0}.mr-1{margin-right:var(--spacing)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mr-4{margin-right:calc(var(--spacing) * 4)}.mr-5{margin-right:calc(var(--spacing) * 5)}.mr-6{margin-right:calc(var(--spacing) * 6)}.mr-8{margin-right:calc(var(--spacing) * 8)}.mr-10{margin-right:calc(var(--spacing) * 10)}.mr-12{margin-right:calc(var(--spacing) * 12)}.mr-16{margin-right:calc(var(--spacing) * 16)}.mr-auto{margin-right:auto}.-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-5{margin-bottom:calc(var(--spacing) * -5)}.-mb-6{margin-bottom:calc(var(--spacing) * -6)}.-mb-8{margin-bottom:calc(var(--spacing) * -8)}.-mb-px{margin-bottom:-1px}.mb-0{margin-bottom:0}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.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-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.mb-16{margin-bottom:calc(var(--spacing) * 16)}.mb-auto{margin-bottom:auto}.-ml-1{margin-left:calc(var(--spacing) * -1)}.-ml-2{margin-left:calc(var(--spacing) * -2)}.-ml-3{margin-left:calc(var(--spacing) * -3)}.-ml-4{margin-left:calc(var(--spacing) * -4)}.-ml-5{margin-left:calc(var(--spacing) * -5)}.-ml-6{margin-left:calc(var(--spacing) * -6)}.-ml-8{margin-left:calc(var(--spacing) * -8)}.ml-0{margin-left:0}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:var(--spacing)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-5{margin-left:calc(var(--spacing) * 5)}.ml-6{margin-left:calc(var(--spacing) * 6)}.ml-8{margin-left:calc(var(--spacing) * 8)}.ml-10{margin-left:calc(var(--spacing) * 10)}.ml-12{margin-left:calc(var(--spacing) * 12)}.ml-16{margin-left:calc(var(--spacing) * 16)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.box-content{box-sizing:content-box}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.\!block{display:block!important}.\!flex{display:flex!important}.\!grid{display:grid!important}.\!hidden{display:none!important}.\!inline{display:inline!important}.\!inline-block{display:inline-block!important}.\!inline-flex{display:inline-flex!important}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.inline-table{display:inline-table}.list-item{display:list-item}.table{display:table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-column{display:table-column}.table-column-group{display:table-column-group}.table-footer-group{display:table-footer-group}.table-header-group{display:table-header-group}.table-row{display:table-row}.table-row-group{display:table-row-group}.aspect-auto{aspect-ratio:auto}.aspect-square{aspect-ratio:1}.aspect-video{aspect-ratio:var(--aspect-video)}.\!h-0{height:0!important}.\!h-1{height:var(--spacing)!important}.\!h-2{height:calc(var(--spacing) * 2)!important}.\!h-3{height:calc(var(--spacing) * 3)!important}.\!h-4{height:calc(var(--spacing) * 4)!important}.\!h-5{height:calc(var(--spacing) * 5)!important}.\!h-6{height:calc(var(--spacing) * 6)!important}.\!h-8{height:calc(var(--spacing) * 8)!important}.\!h-10{height:calc(var(--spacing) * 10)!important}.\!h-12{height:calc(var(--spacing) * 12)!important}.\!h-16{height:calc(var(--spacing) * 16)!important}.\!h-20{height:calc(var(--spacing) * 20)!important}.\!h-24{height:calc(var(--spacing) * 24)!important}.\!h-32{height:calc(var(--spacing) * 32)!important}.\!h-40{height:calc(var(--spacing) * 40)!important}.\!h-48{height:calc(var(--spacing) * 48)!important}.\!h-56{height:calc(var(--spacing) * 56)!important}.\!h-64{height:calc(var(--spacing) * 64)!important}.\!h-72{height:calc(var(--spacing) * 72)!important}.\!h-80{height:calc(var(--spacing) * 80)!important}.\!h-96{height:calc(var(--spacing) * 96)!important}.\!h-auto{height:auto!important}.\!h-fit{height:fit-content!important}.\!h-full{height:100%!important}.\!h-max{height:max-content!important}.\!h-min{height:min-content!important}.\!h-px{height:1px!important}.\!h-screen{height:100vh!important}.h-0{height:0}.h-1{height:var(--spacing)}.h-1\/2{height:50%}.h-1\/3{height:33.3333%}.h-1\/4{height:25%}.h-1\/5{height:20%}.h-1\/6{height:16.6667%}.h-2{height:calc(var(--spacing) * 2)}.h-2\/3{height:66.6667%}.h-2\/4{height:50%}.h-2\/5{height:40%}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-3\/4{height:75%}.h-3\/5{height:60%}.h-4{height:calc(var(--spacing) * 4)}.h-4\/5{height:80%}.h-5{height:calc(var(--spacing) * 5)}.h-5\/6{height:83.3333%}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-20{height:calc(var(--spacing) * 20)}.h-24{height:calc(var(--spacing) * 24)}.h-32{height:calc(var(--spacing) * 32)}.h-40{height:calc(var(--spacing) * 40)}.h-48{height:calc(var(--spacing) * 48)}.h-56{height:calc(var(--spacing) * 56)}.h-64{height:calc(var(--spacing) * 64)}.h-72{height:calc(var(--spacing) * 72)}.h-80{height:calc(var(--spacing) * 80)}.h-96{height:calc(var(--spacing) * 96)}.h-\[42px\]{height:42px}.h-auto{height:auto}.h-fit{height:fit-content}.h-full{height:100%}.h-max{height:max-content}.h-min{height:min-content}.h-px{height:1px}.h-screen{height:100vh}.max-h-0{max-height:0}.max-h-20{max-height:calc(var(--spacing) * 20)}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-56{max-height:calc(var(--spacing) * 56)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[400px\]{max-height:400px}.max-h-\[500px\]{max-height:500px}.max-h-\[calc\(90vh-140px\)\]{max-height:calc(90vh - 140px)}.max-h-\[calc\(100vh-320px\)\]{max-height:calc(100vh - 320px)}.max-h-fit{max-height:fit-content}.max-h-full{max-height:100%}.max-h-max{max-height:max-content}.max-h-min{max-height:min-content}.max-h-none{max-height:none}.max-h-screen{max-height:100vh}.min-h-0{min-height:0}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-12{min-height:calc(var(--spacing) * 12)}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-24{min-height:calc(var(--spacing) * 24)}.min-h-32{min-height:calc(var(--spacing) * 32)}.min-h-40{min-height:calc(var(--spacing) * 40)}.min-h-48{min-height:calc(var(--spacing) * 48)}.min-h-56{min-height:calc(var(--spacing) * 56)}.min-h-64{min-height:calc(var(--spacing) * 64)}.min-h-\[40px\]{min-height:40px}.min-h-\[42px\]{min-height:42px}.min-h-\[60px\]{min-height:60px}.min-h-\[60vh\]{min-height:60vh}.min-h-\[80px\]{min-height:80px}.min-h-\[200px\]{min-height:200px}.min-h-\[400px\]{min-height:400px}.min-h-fit{min-height:fit-content}.min-h-full{min-height:100%}.min-h-max{min-height:max-content}.min-h-min{min-height:min-content}.min-h-screen{min-height:100vh}.\!w-0{width:0!important}.\!w-1{width:var(--spacing)!important}.\!w-1\/2{width:50%!important}.\!w-1\/3{width:33.3333%!important}.\!w-1\/4{width:25%!important}.\!w-1\/5{width:20%!important}.\!w-1\/6{width:16.6667%!important}.\!w-2{width:calc(var(--spacing) * 2)!important}.\!w-2\/3{width:66.6667%!important}.\!w-2\/4{width:50%!important}.\!w-2\/5{width:40%!important}.\!w-3{width:calc(var(--spacing) * 3)!important}.\!w-3\/4{width:75%!important}.\!w-3\/5{width:60%!important}.\!w-4{width:calc(var(--spacing) * 4)!important}.\!w-4\/5{width:80%!important}.\!w-5{width:calc(var(--spacing) * 5)!important}.\!w-5\/6{width:83.3333%!important}.\!w-6{width:calc(var(--spacing) * 6)!important}.\!w-8{width:calc(var(--spacing) * 8)!important}.\!w-10{width:calc(var(--spacing) * 10)!important}.\!w-12{width:calc(var(--spacing) * 12)!important}.\!w-16{width:calc(var(--spacing) * 16)!important}.\!w-20{width:calc(var(--spacing) * 20)!important}.\!w-24{width:calc(var(--spacing) * 24)!important}.\!w-32{width:calc(var(--spacing) * 32)!important}.\!w-40{width:calc(var(--spacing) * 40)!important}.\!w-48{width:calc(var(--spacing) * 48)!important}.\!w-52{width:calc(var(--spacing) * 52)!important}.\!w-56{width:calc(var(--spacing) * 56)!important}.\!w-60{width:calc(var(--spacing) * 60)!important}.\!w-64{width:calc(var(--spacing) * 64)!important}.\!w-72{width:calc(var(--spacing) * 72)!important}.\!w-80{width:calc(var(--spacing) * 80)!important}.\!w-96{width:calc(var(--spacing) * 96)!important}.\!w-auto{width:auto!important}.\!w-fit{width:fit-content!important}.\!w-full{width:100%!important}.\!w-max{width:max-content!important}.\!w-min{width:min-content!important}.\!w-px{width:1px!important}.\!w-screen{width:100vw!important}.w-0{width:0}.w-1{width:var(--spacing)}.w-1\/2{width:50%}.w-1\/3{width:33.3333%}.w-1\/4{width:25%}.w-1\/5{width:20%}.w-1\/6{width:16.6667%}.w-2{width:calc(var(--spacing) * 2)}.w-2\/3{width:66.6667%}.w-2\/4{width:50%}.w-2\/5{width:40%}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-3\/4{width:75%}.w-3\/5{width:60%}.w-4{width:calc(var(--spacing) * 4)}.w-4\/5{width:80%}.w-5{width:calc(var(--spacing) * 5)}.w-5\/6{width:83.3333%}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-40{width:calc(var(--spacing) * 40)}.w-48{width:calc(var(--spacing) * 48)}.w-52{width:calc(var(--spacing) * 52)}.w-56{width:calc(var(--spacing) * 56)}.w-60{width:calc(var(--spacing) * 60)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-80{width:calc(var(--spacing) * 80)}.w-96{width:calc(var(--spacing) * 96)}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.w-max{width:max-content}.w-min{width:min-content}.w-px{width:1px}.w-screen{width:100vw}.\!max-w-0{max-width:0!important}.\!max-w-2xl{max-width:var(--container-2xl)!important}.\!max-w-3xl{max-width:var(--container-3xl)!important}.\!max-w-4xl{max-width:var(--container-4xl)!important}.\!max-w-full{max-width:100%!important}.\!max-w-lg{max-width:var(--container-lg)!important}.\!max-w-md{max-width:var(--container-md)!important}.\!max-w-none{max-width:none!important}.\!max-w-sm{max-width:var(--container-sm)!important}.\!max-w-xl{max-width:var(--container-xl)!important}.\!max-w-xs{max-width:var(--container-xs)!important}.max-w-0{max-width:0}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[200px\]{max-width:200px}.max-w-\[calc\(100vw-1rem\)\]{max-width:calc(100vw - 1rem)}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-fit{max-width:fit-content}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-min{max-width:min-content}.max-w-none{max-width:none}.max-w-prose{max-width:65ch}.max-w-screen-2xl{max-width:var(--breakpoint-2xl)}.max-w-screen-lg{max-width:var(--breakpoint-lg)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-screen-sm{max-width:var(--breakpoint-sm)}.max-w-screen-xl{max-width:var(--breakpoint-xl)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.\!min-w-0{min-width:0!important}.\!min-w-4{min-width:calc(var(--spacing) * 4)!important}.\!min-w-8{min-width:calc(var(--spacing) * 8)!important}.\!min-w-12{min-width:calc(var(--spacing) * 12)!important}.\!min-w-16{min-width:calc(var(--spacing) * 16)!important}.\!min-w-20{min-width:calc(var(--spacing) * 20)!important}.\!min-w-24{min-width:calc(var(--spacing) * 24)!important}.\!min-w-32{min-width:calc(var(--spacing) * 32)!important}.\!min-w-40{min-width:calc(var(--spacing) * 40)!important}.\!min-w-48{min-width:calc(var(--spacing) * 48)!important}.\!min-w-56{min-width:calc(var(--spacing) * 56)!important}.\!min-w-64{min-width:calc(var(--spacing) * 64)!important}.\!min-w-fit{min-width:fit-content!important}.\!min-w-full{min-width:100%!important}.\!min-w-max{min-width:max-content!important}.\!min-w-min{min-width:min-content!important}.min-w-0{min-width:0}.min-w-\[20px\]{min-width:20px}.min-w-\[80px\]{min-width:80px}.min-w-\[120px\]{min-width:120px}.min-w-\[200px\]{min-width:200px}.min-w-\[280px\]{min-width:280px}.min-w-\[300px\]{min-width:300px}.min-w-fit{min-width:fit-content}.min-w-full{min-width:100%}.min-w-max{min-width:max-content}.min-w-min{min-width:min-content}.\!flex-1{flex:1!important}.\!flex-auto{flex:auto!important}.\!flex-initial{flex:0 auto!important}.\!flex-none{flex:none!important}.flex-1{flex:1}.flex-auto{flex:auto}.flex-initial{flex:0 auto}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-0{flex-basis:0}.basis-1{flex-basis:var(--spacing)}.basis-1\/2{flex-basis:50%}.basis-1\/3{flex-basis:33.3333%}.basis-1\/4{flex-basis:25%}.basis-2{flex-basis:calc(var(--spacing) * 2)}.basis-2\/3{flex-basis:66.6667%}.basis-3\/4{flex-basis:75%}.basis-auto{flex-basis:auto}.basis-full{flex-basis:100%}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.caption-bottom{caption-side:bottom}.caption-top{caption-side:top}.border-collapse{border-collapse:collapse}.border-separate{border-collapse:separate}.border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-1{--tw-border-spacing-x:var(--spacing);--tw-border-spacing-y:var(--spacing);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-2{--tw-border-spacing-x:calc(var(--spacing) * 2);--tw-border-spacing-y:calc(var(--spacing) * 2);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-3{--tw-border-spacing-x:calc(var(--spacing) * 3);--tw-border-spacing-y:calc(var(--spacing) * 3);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-4{--tw-border-spacing-x:calc(var(--spacing) * 4);--tw-border-spacing-y:calc(var(--spacing) * 4);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-5{--tw-border-spacing-x:calc(var(--spacing) * 5);--tw-border-spacing-y:calc(var(--spacing) * 5);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-6{--tw-border-spacing-x:calc(var(--spacing) * 6);--tw-border-spacing-y:calc(var(--spacing) * 6);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-8{--tw-border-spacing-x:calc(var(--spacing) * 8);--tw-border-spacing-y:calc(var(--spacing) * 8);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.origin-bottom{transform-origin:bottom}.origin-bottom-left{transform-origin:0 100%}.origin-bottom-right{transform-origin:100% 100%}.origin-center{transform-origin:50%}.origin-left{transform-origin:0}.origin-right{transform-origin:100%}.origin-top{transform-origin:top}.origin-top-left{transform-origin:0 0}.origin-top-right{transform-origin:100% 0}.-translate-x-1{--tw-translate-x:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-2{--tw-translate-x:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-3{--tw-translate-x:calc(var(--spacing) * -3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-4{--tw-translate-x:calc(var(--spacing) * -4);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1{--tw-translate-x:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/2{--tw-translate-x: 50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/3{--tw-translate-x:calc(1 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/4{--tw-translate-x: 25% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-2{--tw-translate-x:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-2\/3{--tw-translate-x:calc(2 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-3{--tw-translate-x:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-3\/4{--tw-translate-x: 75% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-6{--tw-translate-x:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-8{--tw-translate-x:calc(var(--spacing) * 8);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-10{--tw-translate-x:calc(var(--spacing) * 10);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-12{--tw-translate-x:calc(var(--spacing) * 12);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.-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: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-2{--tw-translate-y:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-3{--tw-translate-y:calc(var(--spacing) * -3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-4{--tw-translate-y:calc(var(--spacing) * -4);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-full{--tw-translate-y:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-0{--tw-translate-y:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1{--tw-translate-y:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/2{--tw-translate-y: 50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/3{--tw-translate-y:calc(1 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/4{--tw-translate-y: 25% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2{--tw-translate-y:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2\/3{--tw-translate-y:calc(2 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3{--tw-translate-y:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3\/4{--tw-translate-y: 75% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-4{--tw-translate-y:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-5{--tw-translate-y:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-6{--tw-translate-y:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-8{--tw-translate-y:calc(var(--spacing) * 8);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-10{--tw-translate-y:calc(var(--spacing) * 10);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-12{--tw-translate-y:calc(var(--spacing) * 12);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-full{--tw-translate-y:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-50{--tw-scale-x:50%;--tw-scale-y:50%;--tw-scale-z:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-90{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-105{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-125{--tw-scale-x:125%;--tw-scale-y:125%;--tw-scale-z:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-150{--tw-scale-x:150%;--tw-scale-y:150%;--tw-scale-z:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-0{--tw-scale-x:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-50{--tw-scale-x:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-75{--tw-scale-x:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-90{--tw-scale-x:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-95{--tw-scale-x:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-100{--tw-scale-x:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-105{--tw-scale-x:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-110{--tw-scale-x:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-125{--tw-scale-x:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-150{--tw-scale-x:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-0{--tw-scale-y:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-50{--tw-scale-y:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-75{--tw-scale-y:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-90{--tw-scale-y:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-95{--tw-scale-y:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-100{--tw-scale-y:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-105{--tw-scale-y:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-110{--tw-scale-y:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-125{--tw-scale-y:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-150{--tw-scale-y:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.-rotate-1{rotate:-1deg}.-rotate-2{rotate:-2deg}.-rotate-3{rotate:-3deg}.-rotate-6{rotate:-6deg}.-rotate-12{rotate:-12deg}.-rotate-45{rotate:-45deg}.-rotate-90{rotate:-90deg}.-rotate-180{rotate:-180deg}.rotate-0{rotate:0deg}.rotate-1{rotate:1deg}.rotate-2{rotate:2deg}.rotate-3{rotate:3deg}.rotate-6{rotate:6deg}.rotate-12{rotate:12deg}.rotate-45{rotate:45deg}.rotate-90{rotate:90deg}.rotate-180{rotate:180deg}.-skew-x-1{--tw-skew-x:skewX( -1deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-2{--tw-skew-x:skewX( -2deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-3{--tw-skew-x:skewX( -3deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-6{--tw-skew-x:skewX( -6deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-12{--tw-skew-x:skewX( -12deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-0{--tw-skew-x:skewX(0deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-1{--tw-skew-x:skewX(1deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-2{--tw-skew-x:skewX(2deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-3{--tw-skew-x:skewX(3deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-6{--tw-skew-x:skewX(6deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-12{--tw-skew-x:skewX(12deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-1{--tw-skew-y:skewY( -1deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-2{--tw-skew-y:skewY( -2deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-3{--tw-skew-y:skewY( -3deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-6{--tw-skew-y:skewY( -6deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-12{--tw-skew-y:skewY( -12deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-0{--tw-skew-y:skewY(0deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-1{--tw-skew-y:skewY(1deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-2{--tw-skew-y:skewY(2deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-3{--tw-skew-y:skewY(3deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-6{--tw-skew-y:skewY(6deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-12{--tw-skew-y:skewY(12deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform,.transform-cpu{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform-gpu{transform:translateZ(0) var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform-none{transform:none}.animate-\[loading_1\.5s_ease-in-out_infinite\]{animation:1.5s ease-in-out infinite loading}.animate-bounce{animation:var(--animate-bounce)}.animate-none{animation:none}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-alias{cursor:alias}.cursor-all-scroll{cursor:all-scroll}.cursor-auto{cursor:auto}.cursor-cell{cursor:cell}.cursor-col-resize{cursor:col-resize}.cursor-context-menu{cursor:context-menu}.cursor-copy{cursor:copy}.cursor-crosshair{cursor:crosshair}.cursor-default{cursor:default}.cursor-e-resize{cursor:e-resize}.cursor-ew-resize{cursor:ew-resize}.cursor-grab{cursor:grab}.cursor-grabbing{cursor:grabbing}.cursor-help{cursor:help}.cursor-move{cursor:move}.cursor-n-resize{cursor:n-resize}.cursor-ne-resize{cursor:ne-resize}.cursor-nesw-resize{cursor:nesw-resize}.cursor-no-drop{cursor:no-drop}.cursor-none{cursor:none}.cursor-not-allowed{cursor:not-allowed}.cursor-ns-resize{cursor:ns-resize}.cursor-nw-resize{cursor:nw-resize}.cursor-nwse-resize{cursor:nwse-resize}.cursor-pointer{cursor:pointer}.cursor-progress{cursor:progress}.cursor-row-resize{cursor:row-resize}.cursor-s-resize{cursor:s-resize}.cursor-se-resize{cursor:se-resize}.cursor-sw-resize{cursor:sw-resize}.cursor-text{cursor:text}.cursor-vertical-text{cursor:vertical-text}.cursor-w-resize{cursor:w-resize}.cursor-wait{cursor:wait}.cursor-zoom-in{cursor:zoom-in}.cursor-zoom-out{cursor:zoom-out}.touch-pan-left{--tw-pan-x:pan-left;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-right{--tw-pan-x:pan-right;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-x{--tw-pan-x:pan-x;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-down{--tw-pan-y:pan-down;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-up{--tw-pan-y:pan-up;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-y{--tw-pan-y:pan-y;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pinch-zoom{--tw-pinch-zoom:pinch-zoom;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-auto{touch-action:auto}.touch-manipulation{touch-action:manipulation}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.resize-x{resize:horizontal}.resize-y{resize:vertical}.snap-both{scroll-snap-type:both var(--tw-scroll-snap-strictness)}.snap-none{scroll-snap-type:none}.snap-x{scroll-snap-type:x var(--tw-scroll-snap-strictness)}.snap-y{scroll-snap-type:y var(--tw-scroll-snap-strictness)}.snap-mandatory{--tw-scroll-snap-strictness:mandatory}.snap-proximity{--tw-scroll-snap-strictness:proximity}.snap-align-none{scroll-snap-align:none}.snap-center{scroll-snap-align:center}.snap-end{scroll-snap-align:end}.snap-start{scroll-snap-align:start}.snap-always{scroll-snap-stop:always}.snap-normal{scroll-snap-stop:normal}.scroll-mt-44{scroll-margin-top:calc(var(--spacing) * 44)}.list-inside{list-style-position:inside}.list-outside{list-style-position:outside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.appearance-auto{appearance:auto}.appearance-none{appearance:none}.columns-1{columns:1}.columns-2{columns:2}.columns-2xl{columns:var(--container-2xl)}.columns-2xs{columns:var(--container-2xs)}.columns-3{columns:3}.columns-3xl{columns:var(--container-3xl)}.columns-3xs{columns:var(--container-3xs)}.columns-4{columns:4}.columns-4xl{columns:var(--container-4xl)}.columns-5{columns:5}.columns-5xl{columns:var(--container-5xl)}.columns-6{columns:6}.columns-6xl{columns:var(--container-6xl)}.columns-7{columns:7}.columns-7xl{columns:var(--container-7xl)}.columns-8{columns:8}.columns-9{columns:9}.columns-10{columns:10}.columns-11{columns:11}.columns-12{columns:12}.columns-auto{columns:auto}.columns-lg{columns:var(--container-lg)}.columns-md{columns:var(--container-md)}.columns-sm{columns:var(--container-sm)}.columns-xl{columns:var(--container-xl)}.columns-xs{columns:var(--container-xs)}.break-before-all{break-before:all}.break-before-auto{break-before:auto}.break-before-avoid{break-before:avoid}.break-before-avoid-page{break-before:avoid-page}.break-before-column{break-before:column}.break-before-left{break-before:left}.break-before-page{break-before:page}.break-before-right{break-before:right}.break-inside-auto{break-inside:auto}.break-inside-avoid{break-inside:avoid}.break-inside-avoid-column{break-inside:avoid-column}.break-inside-avoid-page{break-inside:avoid-page}.break-after-all{break-after:all}.break-after-auto{break-after:auto}.break-after-avoid{break-after:avoid}.break-after-avoid-page{break-after:avoid-page}.break-after-column{break-after:column}.break-after-left{break-after:left}.break-after-page{break-after:page}.break-after-right{break-after:right}.auto-cols-auto{grid-auto-columns:auto}.auto-cols-fr{grid-auto-columns:minmax(0,1fr)}.auto-cols-max{grid-auto-columns:max-content}.auto-cols-min{grid-auto-columns:min-content}.grid-flow-col{grid-auto-flow:column}.grid-flow-col-dense{grid-auto-flow:column dense}.grid-flow-dense{grid-auto-flow:dense}.grid-flow-row{grid-auto-flow:row}.grid-flow-row-dense{grid-auto-flow:dense}.auto-rows-auto{grid-auto-rows:auto}.auto-rows-fr{grid-auto-rows:minmax(0,1fr)}.auto-rows-max{grid-auto-rows:max-content}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-9{grid-template-columns:repeat(9,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.grid-cols-11{grid-template-columns:repeat(11,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.\!flex-col{flex-direction:column!important}.\!flex-row{flex-direction:row!important}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.\!flex-nowrap{flex-wrap:nowrap!important}.\!flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.flex-wrap-reverse{flex-wrap:wrap-reverse}.place-content-around{place-content:space-around}.place-content-baseline{place-content:baseline start}.place-content-between{place-content:space-between}.place-content-center{place-content:center}.place-content-end{place-content:end}.place-content-evenly{place-content:space-evenly}.place-content-start{place-content:start}.place-content-stretch{place-content:stretch}.place-items-baseline{place-items:baseline}.place-items-center{place-items:center}.place-items-end{place-items:end}.place-items-start{place-items:start}.place-items-stretch{place-items:stretch stretch}.content-around{align-content:space-around}.content-between{align-content:space-between}.content-center{align-content:center}.content-end{align-content:flex-end}.content-evenly{align-content:space-evenly}.content-start{align-content:flex-start}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-around{justify-content:space-around}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-evenly{justify-content:space-evenly}.justify-start{justify-content:flex-start}.\!gap-0{gap:0!important}.\!gap-1{gap:var(--spacing)!important}.\!gap-2{gap:calc(var(--spacing) * 2)!important}.\!gap-3{gap:calc(var(--spacing) * 3)!important}.\!gap-4{gap:calc(var(--spacing) * 4)!important}.\!gap-5{gap:calc(var(--spacing) * 5)!important}.\!gap-6{gap:calc(var(--spacing) * 6)!important}.\!gap-8{gap:calc(var(--spacing) * 8)!important}.\!gap-10{gap:calc(var(--spacing) * 10)!important}.\!gap-12{gap:calc(var(--spacing) * 12)!important}.\!gap-16{gap:calc(var(--spacing) * 16)!important}.gap-0{gap:0}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-10{gap:calc(var(--spacing) * 10)}.gap-12{gap:calc(var(--spacing) * 12)}.gap-16{gap:calc(var(--spacing) * 16)}: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-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * 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-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * 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)))}.gap-x-0{column-gap:0}.gap-x-1{column-gap:var(--spacing)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-5{column-gap:calc(var(--spacing) * 5)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-x-8{column-gap:calc(var(--spacing) * 8)}.gap-x-10{column-gap:calc(var(--spacing) * 10)}.gap-x-12{column-gap:calc(var(--spacing) * 12)}:where(.-space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -1) * calc(1 - var(--tw-space-x-reverse)))}:where(.-space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 5) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.gap-y-0{row-gap:0}.gap-y-1{row-gap:var(--spacing)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}.gap-y-4{row-gap:calc(var(--spacing) * 4)}.gap-y-5{row-gap:calc(var(--spacing) * 5)}.gap-y-6{row-gap:calc(var(--spacing) * 6)}.gap-y-8{row-gap:calc(var(--spacing) * 8)}.gap-y-10{row-gap:calc(var(--spacing) * 10)}.gap-y-12{row-gap:calc(var(--spacing) * 12)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px * var(--tw-divide-x-reverse));border-inline-end-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-0>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(0px * var(--tw-divide-x-reverse));border-inline-end-width:calc(0px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-2>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(2px * var(--tw-divide-x-reverse));border-inline-end-width:calc(2px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-4>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(4px * var(--tw-divide-x-reverse));border-inline-end-width:calc(4px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-8>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(8px * var(--tw-divide-x-reverse));border-inline-end-width:calc(8px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-0>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px * var(--tw-divide-y-reverse));border-bottom-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-2>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(2px * var(--tw-divide-y-reverse));border-bottom-width:calc(2px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-4>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(4px * var(--tw-divide-y-reverse));border-bottom-width:calc(4px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-8>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(8px * var(--tw-divide-y-reverse));border-bottom-width:calc(8px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-reverse>:not(:last-child)){--tw-divide-y-reverse:1}:where(.divide-dashed>:not(:last-child)){--tw-border-style:dashed;border-style:dashed}:where(.divide-dotted>:not(:last-child)){--tw-border-style:dotted;border-style:dotted}:where(.divide-double>:not(:last-child)){--tw-border-style:double;border-style:double}:where(.divide-none>:not(:last-child)){--tw-border-style:none;border-style:none}:where(.divide-solid>:not(:last-child)){--tw-border-style:solid;border-style:solid}:where(.divide-black>:not(:last-child)){border-color:var(--color-black)}:where(.divide-current>:not(:last-child)){border-color:currentColor}:where(.divide-gray-50>:not(:last-child)){border-color:var(--color-gray-50)}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}:where(.divide-gray-400>:not(:last-child)){border-color:var(--color-gray-400)}:where(.divide-gray-500>:not(:last-child)){border-color:var(--color-gray-500)}:where(.divide-gray-600>:not(:last-child)){border-color:var(--color-gray-600)}:where(.divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}:where(.divide-gray-800>:not(:last-child)){border-color:var(--color-gray-800)}:where(.divide-gray-900>:not(:last-child)){border-color:var(--color-gray-900)}:where(.divide-inherit>:not(:last-child)){border-color:inherit}:where(.divide-purple-100>:not(:last-child)){border-color:var(--color-purple-100)}:where(.divide-slate-50>:not(:last-child)){border-color:var(--color-slate-50)}:where(.divide-slate-100>:not(:last-child)){border-color:var(--color-slate-100)}:where(.divide-slate-200>:not(:last-child)){border-color:var(--color-slate-200)}:where(.divide-slate-300>:not(:last-child)){border-color:var(--color-slate-300)}:where(.divide-slate-400>:not(:last-child)){border-color:var(--color-slate-400)}:where(.divide-slate-500>:not(:last-child)){border-color:var(--color-slate-500)}:where(.divide-slate-600>:not(:last-child)){border-color:var(--color-slate-600)}:where(.divide-slate-700>:not(:last-child)){border-color:var(--color-slate-700)}:where(.divide-slate-800>:not(:last-child)){border-color:var(--color-slate-800)}:where(.divide-slate-900>:not(:last-child)){border-color:var(--color-slate-900)}:where(.divide-transparent>:not(:last-child)){border-color:#0000}:where(.divide-white>:not(:last-child)){border-color:var(--color-white)}.place-self-auto{place-self:auto}.place-self-center{place-self:center}.place-self-end{place-self:end}.place-self-start{place-self:start}.place-self-stretch{place-self:stretch stretch}.self-auto{align-self:auto}.self-baseline{align-self:baseline}.self-center{align-self:center}.self-end{align-self:flex-end}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-clip{overflow:clip}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-x-clip{overflow-x:clip}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-x-visible{overflow-x:visible}.overflow-y-auto{overflow-y:auto}.overflow-y-clip{overflow-y:clip}.overflow-y-hidden{overflow-y:hidden}.overflow-y-scroll{overflow-y:scroll}.overflow-y-visible{overflow-y:visible}.overscroll-auto{overscroll-behavior:auto}.overscroll-contain{overscroll-behavior:contain}.overscroll-none{overscroll-behavior:none}.overscroll-x-auto{overscroll-behavior-x:auto}.overscroll-x-contain{overscroll-behavior-x:contain}.overscroll-x-none{overscroll-behavior-x:none}.overscroll-y-auto{overscroll-behavior-y:auto}.overscroll-y-contain{overscroll-behavior-y:contain}.overscroll-y-none{overscroll-behavior-y:none}.scroll-auto{scroll-behavior:auto}.scroll-smooth{scroll-behavior:smooth}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-2xl{border-top-left-radius:var(--radius-2xl);border-top-right-radius:var(--radius-2xl)}.rounded-t-3xl{border-top-left-radius:var(--radius-3xl);border-top-right-radius:var(--radius-3xl)}.rounded-t-full{border-top-left-radius:3.40282e38px;border-top-right-radius:3.40282e38px}.rounded-t-lg{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.rounded-t-md{border-top-left-radius:var(--radius-md);border-top-right-radius:var(--radius-md)}.rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.rounded-t-sm{border-top-left-radius:var(--radius-sm);border-top-right-radius:var(--radius-sm)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-l-2xl{border-top-left-radius:var(--radius-2xl);border-bottom-left-radius:var(--radius-2xl)}.rounded-l-3xl{border-top-left-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-l-full{border-top-left-radius:3.40282e38px;border-bottom-left-radius:3.40282e38px}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-l-sm{border-top-left-radius:var(--radius-sm);border-bottom-left-radius:var(--radius-sm)}.rounded-l-xl{border-top-left-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-tl{border-top-left-radius:.25rem}.rounded-tl-2xl{border-top-left-radius:var(--radius-2xl)}.rounded-tl-3xl{border-top-left-radius:var(--radius-3xl)}.rounded-tl-full{border-top-left-radius:3.40282e38px}.rounded-tl-lg{border-top-left-radius:var(--radius-lg)}.rounded-tl-md{border-top-left-radius:var(--radius-md)}.rounded-tl-none{border-top-left-radius:0}.rounded-tl-sm{border-top-left-radius:var(--radius-sm)}.rounded-tl-xl{border-top-left-radius:var(--radius-xl)}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-r-2xl{border-top-right-radius:var(--radius-2xl);border-bottom-right-radius:var(--radius-2xl)}.rounded-r-3xl{border-top-right-radius:var(--radius-3xl);border-bottom-right-radius:var(--radius-3xl)}.rounded-r-full{border-top-right-radius:3.40282e38px;border-bottom-right-radius:3.40282e38px}.rounded-r-lg{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.rounded-r-sm{border-top-right-radius:var(--radius-sm);border-bottom-right-radius:var(--radius-sm)}.rounded-r-xl{border-top-right-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl)}.rounded-tr{border-top-right-radius:.25rem}.rounded-tr-2xl{border-top-right-radius:var(--radius-2xl)}.rounded-tr-3xl{border-top-right-radius:var(--radius-3xl)}.rounded-tr-full{border-top-right-radius:3.40282e38px}.rounded-tr-lg{border-top-right-radius:var(--radius-lg)}.rounded-tr-md{border-top-right-radius:var(--radius-md)}.rounded-tr-none{border-top-right-radius:0}.rounded-tr-sm{border-top-right-radius:var(--radius-sm)}.rounded-tr-xl{border-top-right-radius:var(--radius-xl)}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-b-2xl{border-bottom-right-radius:var(--radius-2xl);border-bottom-left-radius:var(--radius-2xl)}.rounded-b-3xl{border-bottom-right-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-b-full{border-bottom-right-radius:3.40282e38px;border-bottom-left-radius:3.40282e38px}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-b-none{border-bottom-right-radius:0;border-bottom-left-radius:0}.rounded-b-sm{border-bottom-right-radius:var(--radius-sm);border-bottom-left-radius:var(--radius-sm)}.rounded-b-xl{border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-br{border-bottom-right-radius:.25rem}.rounded-br-2xl{border-bottom-right-radius:var(--radius-2xl)}.rounded-br-3xl{border-bottom-right-radius:var(--radius-3xl)}.rounded-br-full{border-bottom-right-radius:3.40282e38px}.rounded-br-lg{border-bottom-right-radius:var(--radius-lg)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.rounded-br-none{border-bottom-right-radius:0}.rounded-br-sm{border-bottom-right-radius:var(--radius-sm)}.rounded-br-xl{border-bottom-right-radius:var(--radius-xl)}.rounded-bl{border-bottom-left-radius:.25rem}.rounded-bl-2xl{border-bottom-left-radius:var(--radius-2xl)}.rounded-bl-3xl{border-bottom-left-radius:var(--radius-3xl)}.rounded-bl-full{border-bottom-left-radius:3.40282e38px}.rounded-bl-lg{border-bottom-left-radius:var(--radius-lg)}.rounded-bl-md{border-bottom-left-radius:var(--radius-md)}.rounded-bl-none{border-bottom-left-radius:0}.rounded-bl-sm{border-bottom-left-radius:var(--radius-sm)}.rounded-bl-xl{border-bottom-left-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-3{border-style:var(--tw-border-style);border-width:3px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-8{border-style:var(--tw-border-style);border-width:8px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-x-0{border-inline-style:var(--tw-border-style);border-inline-width:0}.border-x-2{border-inline-style:var(--tw-border-style);border-inline-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-y-0{border-block-style:var(--tw-border-style);border-block-width:0}.border-y-2{border-block-style:var(--tw-border-style);border-block-width:2px}.\!border-t-0{border-top-style:var(--tw-border-style)!important;border-top-width:0!important}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-t-4{border-top-style:var(--tw-border-style);border-top-width:4px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.border-r-2{border-right-style:var(--tw-border-style);border-right-width:2px}.border-r-4{border-right-style:var(--tw-border-style);border-right-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-dotted{--tw-border-style:dotted;border-style:dotted}.border-double{--tw-border-style:double;border-style:double}.border-hidden{--tw-border-style:hidden;border-style:hidden}.border-none{--tw-border-style:none;border-style:none}.border-solid{--tw-border-style:solid;border-style:solid}.border-amber-50{border-color:var(--color-amber-50)}.border-amber-100{border-color:var(--color-amber-100)}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-amber-400{border-color:var(--color-amber-400)}.border-amber-500{border-color:var(--color-amber-500)}.border-amber-600{border-color:var(--color-amber-600)}.border-amber-700{border-color:var(--color-amber-700)}.border-amber-800{border-color:var(--color-amber-800)}.border-amber-900{border-color:var(--color-amber-900)}.border-amber-950{border-color:var(--color-amber-950)}.border-black{border-color:var(--color-black)}.border-blue-50{border-color:var(--color-blue-50)}.border-blue-100{border-color:var(--color-blue-100)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-300{border-color:var(--color-blue-300)}.border-blue-400{border-color:var(--color-blue-400)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-600{border-color:var(--color-blue-600)}.border-blue-700{border-color:var(--color-blue-700)}.border-blue-800{border-color:var(--color-blue-800)}.border-blue-900{border-color:var(--color-blue-900)}.border-blue-950{border-color:var(--color-blue-950)}.border-current{border-color:currentColor}.border-cyan-50{border-color:var(--color-cyan-50)}.border-cyan-100{border-color:var(--color-cyan-100)}.border-cyan-200{border-color:var(--color-cyan-200)}.border-cyan-300{border-color:var(--color-cyan-300)}.border-cyan-400{border-color:var(--color-cyan-400)}.border-cyan-500{border-color:var(--color-cyan-500)}.border-cyan-600{border-color:var(--color-cyan-600)}.border-cyan-700{border-color:var(--color-cyan-700)}.border-cyan-800{border-color:var(--color-cyan-800)}.border-cyan-900{border-color:var(--color-cyan-900)}.border-cyan-950{border-color:var(--color-cyan-950)}.border-emerald-50{border-color:var(--color-emerald-50)}.border-emerald-100{border-color:var(--color-emerald-100)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-emerald-300{border-color:var(--color-emerald-300)}.border-emerald-400{border-color:var(--color-emerald-400)}.border-emerald-500{border-color:var(--color-emerald-500)}.border-emerald-600{border-color:var(--color-emerald-600)}.border-emerald-700{border-color:var(--color-emerald-700)}.border-emerald-800{border-color:var(--color-emerald-800)}.border-emerald-900{border-color:var(--color-emerald-900)}.border-emerald-950{border-color:var(--color-emerald-950)}.border-fuchsia-50{border-color:var(--color-fuchsia-50)}.border-fuchsia-100{border-color:var(--color-fuchsia-100)}.border-fuchsia-200{border-color:var(--color-fuchsia-200)}.border-fuchsia-300{border-color:var(--color-fuchsia-300)}.border-fuchsia-400{border-color:var(--color-fuchsia-400)}.border-fuchsia-500{border-color:var(--color-fuchsia-500)}.border-fuchsia-600{border-color:var(--color-fuchsia-600)}.border-fuchsia-700{border-color:var(--color-fuchsia-700)}.border-fuchsia-800{border-color:var(--color-fuchsia-800)}.border-fuchsia-900{border-color:var(--color-fuchsia-900)}.border-fuchsia-950{border-color:var(--color-fuchsia-950)}.border-gray-50{border-color:var(--color-gray-50)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-400{border-color:var(--color-gray-400)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-gray-900{border-color:var(--color-gray-900)}.border-gray-950{border-color:var(--color-gray-950)}.border-green-50{border-color:var(--color-green-50)}.border-green-100{border-color:var(--color-green-100)}.border-green-200{border-color:var(--color-green-200)}.border-green-300{border-color:var(--color-green-300)}.border-green-400{border-color:var(--color-green-400)}.border-green-500{border-color:var(--color-green-500)}.border-green-600{border-color:var(--color-green-600)}.border-green-700{border-color:var(--color-green-700)}.border-green-800{border-color:var(--color-green-800)}.border-green-900{border-color:var(--color-green-900)}.border-green-950{border-color:var(--color-green-950)}.border-indigo-50{border-color:var(--color-indigo-50)}.border-indigo-100{border-color:var(--color-indigo-100)}.border-indigo-200{border-color:var(--color-indigo-200)}.border-indigo-300{border-color:var(--color-indigo-300)}.border-indigo-400{border-color:var(--color-indigo-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-indigo-600{border-color:var(--color-indigo-600)}.border-indigo-700{border-color:var(--color-indigo-700)}.border-indigo-800{border-color:var(--color-indigo-800)}.border-indigo-900{border-color:var(--color-indigo-900)}.border-indigo-950{border-color:var(--color-indigo-950)}.border-inherit{border-color:inherit}.border-lime-50{border-color:var(--color-lime-50)}.border-lime-100{border-color:var(--color-lime-100)}.border-lime-200{border-color:var(--color-lime-200)}.border-lime-300{border-color:var(--color-lime-300)}.border-lime-400{border-color:var(--color-lime-400)}.border-lime-500{border-color:var(--color-lime-500)}.border-lime-600{border-color:var(--color-lime-600)}.border-lime-700{border-color:var(--color-lime-700)}.border-lime-800{border-color:var(--color-lime-800)}.border-lime-900{border-color:var(--color-lime-900)}.border-lime-950{border-color:var(--color-lime-950)}.border-neutral-50{border-color:var(--color-neutral-50)}.border-neutral-100{border-color:var(--color-neutral-100)}.border-neutral-200{border-color:var(--color-neutral-200)}.border-neutral-300{border-color:var(--color-neutral-300)}.border-neutral-400{border-color:var(--color-neutral-400)}.border-neutral-500{border-color:var(--color-neutral-500)}.border-neutral-600{border-color:var(--color-neutral-600)}.border-neutral-700{border-color:var(--color-neutral-700)}.border-neutral-800{border-color:var(--color-neutral-800)}.border-neutral-900{border-color:var(--color-neutral-900)}.border-neutral-950{border-color:var(--color-neutral-950)}.border-orange-50{border-color:var(--color-orange-50)}.border-orange-100{border-color:var(--color-orange-100)}.border-orange-200{border-color:var(--color-orange-200)}.border-orange-300{border-color:var(--color-orange-300)}.border-orange-400{border-color:var(--color-orange-400)}.border-orange-500{border-color:var(--color-orange-500)}.border-orange-600{border-color:var(--color-orange-600)}.border-orange-700{border-color:var(--color-orange-700)}.border-orange-800{border-color:var(--color-orange-800)}.border-orange-900{border-color:var(--color-orange-900)}.border-orange-950{border-color:var(--color-orange-950)}.border-pink-50{border-color:var(--color-pink-50)}.border-pink-100{border-color:var(--color-pink-100)}.border-pink-200{border-color:var(--color-pink-200)}.border-pink-300{border-color:var(--color-pink-300)}.border-pink-400{border-color:var(--color-pink-400)}.border-pink-500{border-color:var(--color-pink-500)}.border-pink-600{border-color:var(--color-pink-600)}.border-pink-700{border-color:var(--color-pink-700)}.border-pink-800{border-color:var(--color-pink-800)}.border-pink-900{border-color:var(--color-pink-900)}.border-pink-950{border-color:var(--color-pink-950)}.border-purple-50{border-color:var(--color-purple-50)}.border-purple-100{border-color:var(--color-purple-100)}.border-purple-200{border-color:var(--color-purple-200)}.border-purple-300{border-color:var(--color-purple-300)}.border-purple-400{border-color:var(--color-purple-400)}.border-purple-500{border-color:var(--color-purple-500)}.border-purple-600{border-color:var(--color-purple-600)}.border-purple-700{border-color:var(--color-purple-700)}.border-purple-800{border-color:var(--color-purple-800)}.border-purple-900{border-color:var(--color-purple-900)}.border-purple-950{border-color:var(--color-purple-950)}.border-red-50{border-color:var(--color-red-50)}.border-red-100{border-color:var(--color-red-100)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-red-500{border-color:var(--color-red-500)}.border-red-600{border-color:var(--color-red-600)}.border-red-700{border-color:var(--color-red-700)}.border-red-800{border-color:var(--color-red-800)}.border-red-900{border-color:var(--color-red-900)}.border-red-950{border-color:var(--color-red-950)}.border-rose-50{border-color:var(--color-rose-50)}.border-rose-100{border-color:var(--color-rose-100)}.border-rose-200{border-color:var(--color-rose-200)}.border-rose-300{border-color:var(--color-rose-300)}.border-rose-400{border-color:var(--color-rose-400)}.border-rose-500{border-color:var(--color-rose-500)}.border-rose-600{border-color:var(--color-rose-600)}.border-rose-700{border-color:var(--color-rose-700)}.border-rose-800{border-color:var(--color-rose-800)}.border-rose-900{border-color:var(--color-rose-900)}.border-rose-950{border-color:var(--color-rose-950)}.border-sky-50{border-color:var(--color-sky-50)}.border-sky-100{border-color:var(--color-sky-100)}.border-sky-200{border-color:var(--color-sky-200)}.border-sky-300{border-color:var(--color-sky-300)}.border-sky-400{border-color:var(--color-sky-400)}.border-sky-500{border-color:var(--color-sky-500)}.border-sky-600{border-color:var(--color-sky-600)}.border-sky-700{border-color:var(--color-sky-700)}.border-sky-800{border-color:var(--color-sky-800)}.border-sky-900{border-color:var(--color-sky-900)}.border-sky-950{border-color:var(--color-sky-950)}.border-slate-50{border-color:var(--color-slate-50)}.border-slate-100{border-color:var(--color-slate-100)}.border-slate-200{border-color:var(--color-slate-200)}.border-slate-300{border-color:var(--color-slate-300)}.border-slate-400{border-color:var(--color-slate-400)}.border-slate-500{border-color:var(--color-slate-500)}.border-slate-600{border-color:var(--color-slate-600)}.border-slate-700{border-color:var(--color-slate-700)}.border-slate-800{border-color:var(--color-slate-800)}.border-slate-900{border-color:var(--color-slate-900)}.border-slate-950{border-color:var(--color-slate-950)}.border-stone-50{border-color:var(--color-stone-50)}.border-stone-100{border-color:var(--color-stone-100)}.border-stone-200{border-color:var(--color-stone-200)}.border-stone-300{border-color:var(--color-stone-300)}.border-stone-400{border-color:var(--color-stone-400)}.border-stone-500{border-color:var(--color-stone-500)}.border-stone-600{border-color:var(--color-stone-600)}.border-stone-700{border-color:var(--color-stone-700)}.border-stone-800{border-color:var(--color-stone-800)}.border-stone-900{border-color:var(--color-stone-900)}.border-stone-950{border-color:var(--color-stone-950)}.border-teal-50{border-color:var(--color-teal-50)}.border-teal-100{border-color:var(--color-teal-100)}.border-teal-200{border-color:var(--color-teal-200)}.border-teal-300{border-color:var(--color-teal-300)}.border-teal-400{border-color:var(--color-teal-400)}.border-teal-500{border-color:var(--color-teal-500)}.border-teal-600{border-color:var(--color-teal-600)}.border-teal-700{border-color:var(--color-teal-700)}.border-teal-800{border-color:var(--color-teal-800)}.border-teal-900{border-color:var(--color-teal-900)}.border-teal-950{border-color:var(--color-teal-950)}.border-transparent{border-color:#0000}.border-violet-50{border-color:var(--color-violet-50)}.border-violet-100{border-color:var(--color-violet-100)}.border-violet-200{border-color:var(--color-violet-200)}.border-violet-300{border-color:var(--color-violet-300)}.border-violet-400{border-color:var(--color-violet-400)}.border-violet-500{border-color:var(--color-violet-500)}.border-violet-600{border-color:var(--color-violet-600)}.border-violet-700{border-color:var(--color-violet-700)}.border-violet-800{border-color:var(--color-violet-800)}.border-violet-900{border-color:var(--color-violet-900)}.border-violet-950{border-color:var(--color-violet-950)}.border-white{border-color:var(--color-white)}.border-white\/30{border-color:#ffffff4d}@supports (color:color-mix(in lab,red,red)){.border-white\/30{border-color:color-mix(in oklab,var(--color-white) 30%,transparent)}}.border-yellow-50{border-color:var(--color-yellow-50)}.border-yellow-100{border-color:var(--color-yellow-100)}.border-yellow-200{border-color:var(--color-yellow-200)}.border-yellow-300{border-color:var(--color-yellow-300)}.border-yellow-400{border-color:var(--color-yellow-400)}.border-yellow-500{border-color:var(--color-yellow-500)}.border-yellow-600{border-color:var(--color-yellow-600)}.border-yellow-700{border-color:var(--color-yellow-700)}.border-yellow-800{border-color:var(--color-yellow-800)}.border-yellow-900{border-color:var(--color-yellow-900)}.border-yellow-950{border-color:var(--color-yellow-950)}.border-zinc-50{border-color:var(--color-zinc-50)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-400{border-color:var(--color-zinc-400)}.border-zinc-500{border-color:var(--color-zinc-500)}.border-zinc-600{border-color:var(--color-zinc-600)}.border-zinc-700{border-color:var(--color-zinc-700)}.border-zinc-800{border-color:var(--color-zinc-800)}.border-zinc-900{border-color:var(--color-zinc-900)}.border-zinc-950{border-color:var(--color-zinc-950)}.border-t-current{border-top-color:currentColor}.border-t-white{border-top-color:var(--color-white)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-200{background-color:var(--color-amber-200)}.bg-amber-300{background-color:var(--color-amber-300)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-600{background-color:var(--color-amber-600)}.bg-amber-700{background-color:var(--color-amber-700)}.bg-amber-800{background-color:var(--color-amber-800)}.bg-amber-900{background-color:var(--color-amber-900)}.bg-amber-950{background-color:var(--color-amber-950)}.bg-black{background-color:var(--color-black)}.bg-black\/0{background-color:#0000}@supports (color:color-mix(in lab,red,red)){.bg-black\/0{background-color:color-mix(in oklab,var(--color-black) 0%,transparent)}}.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-blue-50{background-color:var(--color-blue-50)}.bg-blue-50\/70{background-color:#eff6ffb3}@supports (color:color-mix(in lab,red,red)){.bg-blue-50\/70{background-color:color-mix(in oklab,var(--color-blue-50) 70%,transparent)}}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-200{background-color:var(--color-blue-200)}.bg-blue-300{background-color:var(--color-blue-300)}.bg-blue-400{background-color:var(--color-blue-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-700{background-color:var(--color-blue-700)}.bg-blue-800{background-color:var(--color-blue-800)}.bg-blue-900{background-color:var(--color-blue-900)}.bg-blue-950{background-color:var(--color-blue-950)}.bg-current{background-color:currentColor}.bg-cyan-50{background-color:var(--color-cyan-50)}.bg-cyan-100{background-color:var(--color-cyan-100)}.bg-cyan-200{background-color:var(--color-cyan-200)}.bg-cyan-300{background-color:var(--color-cyan-300)}.bg-cyan-400{background-color:var(--color-cyan-400)}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-cyan-700{background-color:var(--color-cyan-700)}.bg-cyan-800{background-color:var(--color-cyan-800)}.bg-cyan-900{background-color:var(--color-cyan-900)}.bg-cyan-950{background-color:var(--color-cyan-950)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-emerald-200{background-color:var(--color-emerald-200)}.bg-emerald-300{background-color:var(--color-emerald-300)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-emerald-700{background-color:var(--color-emerald-700)}.bg-emerald-800{background-color:var(--color-emerald-800)}.bg-emerald-900{background-color:var(--color-emerald-900)}.bg-emerald-950{background-color:var(--color-emerald-950)}.bg-fuchsia-50{background-color:var(--color-fuchsia-50)}.bg-fuchsia-100{background-color:var(--color-fuchsia-100)}.bg-fuchsia-200{background-color:var(--color-fuchsia-200)}.bg-fuchsia-300{background-color:var(--color-fuchsia-300)}.bg-fuchsia-400{background-color:var(--color-fuchsia-400)}.bg-fuchsia-500{background-color:var(--color-fuchsia-500)}.bg-fuchsia-600{background-color:var(--color-fuchsia-600)}.bg-fuchsia-700{background-color:var(--color-fuchsia-700)}.bg-fuchsia-800{background-color:var(--color-fuchsia-800)}.bg-fuchsia-900{background-color:var(--color-fuchsia-900)}.bg-fuchsia-950{background-color:var(--color-fuchsia-950)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-100\/60{background-color:#f3f4f699}@supports (color:color-mix(in lab,red,red)){.bg-gray-100\/60{background-color:color-mix(in oklab,var(--color-gray-100) 60%,transparent)}}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-950{background-color:var(--color-gray-950)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-200{background-color:var(--color-green-200)}.bg-green-300{background-color:var(--color-green-300)}.bg-green-400{background-color:var(--color-green-400)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-green-700{background-color:var(--color-green-700)}.bg-green-800{background-color:var(--color-green-800)}.bg-green-900{background-color:var(--color-green-900)}.bg-green-950{background-color:var(--color-green-950)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-200{background-color:var(--color-indigo-200)}.bg-indigo-300{background-color:var(--color-indigo-300)}.bg-indigo-400{background-color:var(--color-indigo-400)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-indigo-700{background-color:var(--color-indigo-700)}.bg-indigo-800{background-color:var(--color-indigo-800)}.bg-indigo-900{background-color:var(--color-indigo-900)}.bg-indigo-950{background-color:var(--color-indigo-950)}.bg-inherit{background-color:inherit}.bg-lime-50{background-color:var(--color-lime-50)}.bg-lime-100{background-color:var(--color-lime-100)}.bg-lime-200{background-color:var(--color-lime-200)}.bg-lime-300{background-color:var(--color-lime-300)}.bg-lime-400{background-color:var(--color-lime-400)}.bg-lime-500{background-color:var(--color-lime-500)}.bg-lime-600{background-color:var(--color-lime-600)}.bg-lime-700{background-color:var(--color-lime-700)}.bg-lime-800{background-color:var(--color-lime-800)}.bg-lime-900{background-color:var(--color-lime-900)}.bg-lime-950{background-color:var(--color-lime-950)}.bg-neutral-50{background-color:var(--color-neutral-50)}.bg-neutral-100{background-color:var(--color-neutral-100)}.bg-neutral-200{background-color:var(--color-neutral-200)}.bg-neutral-300{background-color:var(--color-neutral-300)}.bg-neutral-400{background-color:var(--color-neutral-400)}.bg-neutral-500{background-color:var(--color-neutral-500)}.bg-neutral-600{background-color:var(--color-neutral-600)}.bg-neutral-700{background-color:var(--color-neutral-700)}.bg-neutral-800{background-color:var(--color-neutral-800)}.bg-neutral-900{background-color:var(--color-neutral-900)}.bg-neutral-950{background-color:var(--color-neutral-950)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-200{background-color:var(--color-orange-200)}.bg-orange-300{background-color:var(--color-orange-300)}.bg-orange-400{background-color:var(--color-orange-400)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-600{background-color:var(--color-orange-600)}.bg-orange-700{background-color:var(--color-orange-700)}.bg-orange-800{background-color:var(--color-orange-800)}.bg-orange-900{background-color:var(--color-orange-900)}.bg-orange-950{background-color:var(--color-orange-950)}.bg-pink-50{background-color:var(--color-pink-50)}.bg-pink-100{background-color:var(--color-pink-100)}.bg-pink-200{background-color:var(--color-pink-200)}.bg-pink-300{background-color:var(--color-pink-300)}.bg-pink-400{background-color:var(--color-pink-400)}.bg-pink-500{background-color:var(--color-pink-500)}.bg-pink-600{background-color:var(--color-pink-600)}.bg-pink-700{background-color:var(--color-pink-700)}.bg-pink-800{background-color:var(--color-pink-800)}.bg-pink-900{background-color:var(--color-pink-900)}.bg-pink-950{background-color:var(--color-pink-950)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-purple-200{background-color:var(--color-purple-200)}.bg-purple-300{background-color:var(--color-purple-300)}.bg-purple-400{background-color:var(--color-purple-400)}.bg-purple-500{background-color:var(--color-purple-500)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-purple-700{background-color:var(--color-purple-700)}.bg-purple-800{background-color:var(--color-purple-800)}.bg-purple-900{background-color:var(--color-purple-900)}.bg-purple-950{background-color:var(--color-purple-950)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-200{background-color:var(--color-red-200)}.bg-red-300{background-color:var(--color-red-300)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/50{background-color:#fb2c3680}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/50{background-color:color-mix(in oklab,var(--color-red-500) 50%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-red-700{background-color:var(--color-red-700)}.bg-red-800{background-color:var(--color-red-800)}.bg-red-900{background-color:var(--color-red-900)}.bg-red-950{background-color:var(--color-red-950)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-100{background-color:var(--color-rose-100)}.bg-rose-200{background-color:var(--color-rose-200)}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-rose-500{background-color:var(--color-rose-500)}.bg-rose-600{background-color:var(--color-rose-600)}.bg-rose-700{background-color:var(--color-rose-700)}.bg-rose-800{background-color:var(--color-rose-800)}.bg-rose-900{background-color:var(--color-rose-900)}.bg-rose-950{background-color:var(--color-rose-950)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-sky-100{background-color:var(--color-sky-100)}.bg-sky-200{background-color:var(--color-sky-200)}.bg-sky-300{background-color:var(--color-sky-300)}.bg-sky-400{background-color:var(--color-sky-400)}.bg-sky-500{background-color:var(--color-sky-500)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-sky-700{background-color:var(--color-sky-700)}.bg-sky-800{background-color:var(--color-sky-800)}.bg-sky-900{background-color:var(--color-sky-900)}.bg-sky-950{background-color:var(--color-sky-950)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-100{background-color:var(--color-slate-100)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-slate-300{background-color:var(--color-slate-300)}.bg-slate-400{background-color:var(--color-slate-400)}.bg-slate-500{background-color:var(--color-slate-500)}.bg-slate-600{background-color:var(--color-slate-600)}.bg-slate-700{background-color:var(--color-slate-700)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-950{background-color:var(--color-slate-950)}.bg-stone-50{background-color:var(--color-stone-50)}.bg-stone-100{background-color:var(--color-stone-100)}.bg-stone-200{background-color:var(--color-stone-200)}.bg-stone-300{background-color:var(--color-stone-300)}.bg-stone-400{background-color:var(--color-stone-400)}.bg-stone-500{background-color:var(--color-stone-500)}.bg-stone-600{background-color:var(--color-stone-600)}.bg-stone-700{background-color:var(--color-stone-700)}.bg-stone-800{background-color:var(--color-stone-800)}.bg-stone-900{background-color:var(--color-stone-900)}.bg-stone-950{background-color:var(--color-stone-950)}.bg-teal-50{background-color:var(--color-teal-50)}.bg-teal-100{background-color:var(--color-teal-100)}.bg-teal-200{background-color:var(--color-teal-200)}.bg-teal-300{background-color:var(--color-teal-300)}.bg-teal-400{background-color:var(--color-teal-400)}.bg-teal-500{background-color:var(--color-teal-500)}.bg-teal-600{background-color:var(--color-teal-600)}.bg-teal-700{background-color:var(--color-teal-700)}.bg-teal-800{background-color:var(--color-teal-800)}.bg-teal-900{background-color:var(--color-teal-900)}.bg-teal-950{background-color:var(--color-teal-950)}.bg-transparent{background-color:#0000}.bg-violet-50{background-color:var(--color-violet-50)}.bg-violet-100{background-color:var(--color-violet-100)}.bg-violet-200{background-color:var(--color-violet-200)}.bg-violet-300{background-color:var(--color-violet-300)}.bg-violet-400{background-color:var(--color-violet-400)}.bg-violet-500{background-color:var(--color-violet-500)}.bg-violet-600{background-color:var(--color-violet-600)}.bg-violet-700{background-color:var(--color-violet-700)}.bg-violet-800{background-color:var(--color-violet-800)}.bg-violet-900{background-color:var(--color-violet-900)}.bg-violet-950{background-color:var(--color-violet-950)}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab,red,red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white) 90%,transparent)}}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-200{background-color:var(--color-yellow-200)}.bg-yellow-300{background-color:var(--color-yellow-300)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-yellow-600{background-color:var(--color-yellow-600)}.bg-yellow-700{background-color:var(--color-yellow-700)}.bg-yellow-800{background-color:var(--color-yellow-800)}.bg-yellow-900{background-color:var(--color-yellow-900)}.bg-yellow-950{background-color:var(--color-yellow-950)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-300{background-color:var(--color-zinc-300)}.bg-zinc-400{background-color:var(--color-zinc-400)}.bg-zinc-500{background-color:var(--color-zinc-500)}.bg-zinc-600{background-color:var(--color-zinc-600)}.bg-zinc-700{background-color:var(--color-zinc-700)}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-bl{--tw-gradient-position:to bottom left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-l{--tw-gradient-position:to left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-tl{--tw-gradient-position:to top left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-tr{--tw-gradient-position:to top right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-none{background-image:none}.from-black{--tw-gradient-from:var(--color-black);--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))}.from-blue-50{--tw-gradient-from:var(--color-blue-50);--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))}.from-blue-100{--tw-gradient-from:var(--color-blue-100);--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))}.from-blue-200{--tw-gradient-from:var(--color-blue-200);--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))}.from-blue-300{--tw-gradient-from:var(--color-blue-300);--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))}.from-blue-400{--tw-gradient-from:var(--color-blue-400);--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))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--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))}.from-blue-600{--tw-gradient-from:var(--color-blue-600);--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))}.from-blue-700{--tw-gradient-from:var(--color-blue-700);--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))}.from-blue-800{--tw-gradient-from:var(--color-blue-800);--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))}.from-blue-900{--tw-gradient-from:var(--color-blue-900);--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))}.from-current{--tw-gradient-from:currentcolor;--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))}.from-gray-50{--tw-gradient-from:var(--color-gray-50);--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))}.from-gray-100{--tw-gradient-from:var(--color-gray-100);--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))}.from-gray-200{--tw-gradient-from:var(--color-gray-200);--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))}.from-gray-300{--tw-gradient-from:var(--color-gray-300);--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))}.from-gray-400{--tw-gradient-from:var(--color-gray-400);--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))}.from-gray-500{--tw-gradient-from:var(--color-gray-500);--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))}.from-gray-600{--tw-gradient-from:var(--color-gray-600);--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))}.from-gray-700{--tw-gradient-from:var(--color-gray-700);--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))}.from-gray-800{--tw-gradient-from:var(--color-gray-800);--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))}.from-gray-900{--tw-gradient-from:var(--color-gray-900);--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))}.from-green-50{--tw-gradient-from:var(--color-green-50);--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))}.from-green-100{--tw-gradient-from:var(--color-green-100);--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))}.from-green-200{--tw-gradient-from:var(--color-green-200);--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))}.from-green-300{--tw-gradient-from:var(--color-green-300);--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))}.from-green-400{--tw-gradient-from:var(--color-green-400);--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))}.from-green-500{--tw-gradient-from:var(--color-green-500);--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))}.from-green-600{--tw-gradient-from:var(--color-green-600);--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))}.from-green-700{--tw-gradient-from:var(--color-green-700);--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))}.from-green-800{--tw-gradient-from:var(--color-green-800);--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))}.from-green-900{--tw-gradient-from:var(--color-green-900);--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))}.from-indigo-50{--tw-gradient-from:var(--color-indigo-50);--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))}.from-indigo-100{--tw-gradient-from:var(--color-indigo-100);--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))}.from-indigo-200{--tw-gradient-from:var(--color-indigo-200);--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))}.from-indigo-300{--tw-gradient-from:var(--color-indigo-300);--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))}.from-indigo-400{--tw-gradient-from:var(--color-indigo-400);--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))}.from-indigo-500{--tw-gradient-from:var(--color-indigo-500);--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))}.from-indigo-600{--tw-gradient-from:var(--color-indigo-600);--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))}.from-indigo-700{--tw-gradient-from:var(--color-indigo-700);--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))}.from-indigo-800{--tw-gradient-from:var(--color-indigo-800);--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))}.from-indigo-900{--tw-gradient-from:var(--color-indigo-900);--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))}.from-inherit{--tw-gradient-from:inherit;--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))}.from-orange-50{--tw-gradient-from:var(--color-orange-50);--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))}.from-orange-100{--tw-gradient-from:var(--color-orange-100);--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))}.from-orange-200{--tw-gradient-from:var(--color-orange-200);--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))}.from-orange-300{--tw-gradient-from:var(--color-orange-300);--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))}.from-orange-400{--tw-gradient-from:var(--color-orange-400);--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))}.from-orange-500{--tw-gradient-from:var(--color-orange-500);--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))}.from-orange-600{--tw-gradient-from:var(--color-orange-600);--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))}.from-orange-700{--tw-gradient-from:var(--color-orange-700);--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))}.from-orange-800{--tw-gradient-from:var(--color-orange-800);--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))}.from-orange-900{--tw-gradient-from:var(--color-orange-900);--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))}.from-pink-50{--tw-gradient-from:var(--color-pink-50);--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))}.from-pink-100{--tw-gradient-from:var(--color-pink-100);--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))}.from-pink-200{--tw-gradient-from:var(--color-pink-200);--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))}.from-pink-300{--tw-gradient-from:var(--color-pink-300);--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))}.from-pink-400{--tw-gradient-from:var(--color-pink-400);--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))}.from-pink-500{--tw-gradient-from:var(--color-pink-500);--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))}.from-pink-600{--tw-gradient-from:var(--color-pink-600);--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))}.from-pink-700{--tw-gradient-from:var(--color-pink-700);--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))}.from-pink-800{--tw-gradient-from:var(--color-pink-800);--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))}.from-pink-900{--tw-gradient-from:var(--color-pink-900);--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))}.from-purple-50{--tw-gradient-from:var(--color-purple-50);--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))}.from-purple-100{--tw-gradient-from:var(--color-purple-100);--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))}.from-purple-200{--tw-gradient-from:var(--color-purple-200);--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))}.from-purple-300{--tw-gradient-from:var(--color-purple-300);--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))}.from-purple-400{--tw-gradient-from:var(--color-purple-400);--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))}.from-purple-500{--tw-gradient-from:var(--color-purple-500);--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))}.from-purple-600{--tw-gradient-from:var(--color-purple-600);--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))}.from-purple-700{--tw-gradient-from:var(--color-purple-700);--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))}.from-purple-800{--tw-gradient-from:var(--color-purple-800);--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))}.from-purple-900{--tw-gradient-from:var(--color-purple-900);--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))}.from-red-50{--tw-gradient-from:var(--color-red-50);--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))}.from-red-100{--tw-gradient-from:var(--color-red-100);--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))}.from-red-200{--tw-gradient-from:var(--color-red-200);--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))}.from-red-300{--tw-gradient-from:var(--color-red-300);--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))}.from-red-400{--tw-gradient-from:var(--color-red-400);--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))}.from-red-500{--tw-gradient-from:var(--color-red-500);--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))}.from-red-600{--tw-gradient-from:var(--color-red-600);--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))}.from-red-700{--tw-gradient-from:var(--color-red-700);--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))}.from-red-800{--tw-gradient-from:var(--color-red-800);--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))}.from-red-900{--tw-gradient-from:var(--color-red-900);--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))}.from-slate-50{--tw-gradient-from:var(--color-slate-50);--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))}.from-slate-100{--tw-gradient-from:var(--color-slate-100);--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))}.from-slate-200{--tw-gradient-from:var(--color-slate-200);--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))}.from-slate-300{--tw-gradient-from:var(--color-slate-300);--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))}.from-slate-400{--tw-gradient-from:var(--color-slate-400);--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))}.from-slate-500{--tw-gradient-from:var(--color-slate-500);--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))}.from-slate-600{--tw-gradient-from:var(--color-slate-600);--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))}.from-slate-700{--tw-gradient-from:var(--color-slate-700);--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))}.from-slate-800{--tw-gradient-from:var(--color-slate-800);--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))}.from-slate-900{--tw-gradient-from:var(--color-slate-900);--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))}.from-transparent{--tw-gradient-from:transparent;--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))}.from-white{--tw-gradient-from:var(--color-white);--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))}.from-yellow-50{--tw-gradient-from:var(--color-yellow-50);--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))}.from-yellow-100{--tw-gradient-from:var(--color-yellow-100);--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))}.from-yellow-200{--tw-gradient-from:var(--color-yellow-200);--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))}.from-yellow-300{--tw-gradient-from:var(--color-yellow-300);--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))}.from-yellow-400{--tw-gradient-from:var(--color-yellow-400);--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))}.from-yellow-500{--tw-gradient-from:var(--color-yellow-500);--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))}.from-yellow-600{--tw-gradient-from:var(--color-yellow-600);--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))}.from-yellow-700{--tw-gradient-from:var(--color-yellow-700);--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))}.from-yellow-800{--tw-gradient-from:var(--color-yellow-800);--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))}.from-yellow-900{--tw-gradient-from:var(--color-yellow-900);--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))}.via-black{--tw-gradient-via:var(--color-black);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-50{--tw-gradient-via:var(--color-blue-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-100{--tw-gradient-via:var(--color-blue-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-200{--tw-gradient-via:var(--color-blue-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-300{--tw-gradient-via:var(--color-blue-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-400{--tw-gradient-via:var(--color-blue-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-500{--tw-gradient-via:var(--color-blue-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-600{--tw-gradient-via:var(--color-blue-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-700{--tw-gradient-via:var(--color-blue-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-800{--tw-gradient-via:var(--color-blue-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-900{--tw-gradient-via:var(--color-blue-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-current{--tw-gradient-via:currentcolor;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-50{--tw-gradient-via:var(--color-gray-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-100{--tw-gradient-via:var(--color-gray-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-200{--tw-gradient-via:var(--color-gray-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-300{--tw-gradient-via:var(--color-gray-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-400{--tw-gradient-via:var(--color-gray-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-500{--tw-gradient-via:var(--color-gray-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-600{--tw-gradient-via:var(--color-gray-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-700{--tw-gradient-via:var(--color-gray-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-800{--tw-gradient-via:var(--color-gray-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-900{--tw-gradient-via:var(--color-gray-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-inherit{--tw-gradient-via:inherit;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-50{--tw-gradient-via:var(--color-slate-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-100{--tw-gradient-via:var(--color-slate-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-200{--tw-gradient-via:var(--color-slate-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-300{--tw-gradient-via:var(--color-slate-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-400{--tw-gradient-via:var(--color-slate-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-500{--tw-gradient-via:var(--color-slate-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-600{--tw-gradient-via:var(--color-slate-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-700{--tw-gradient-via:var(--color-slate-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-800{--tw-gradient-via:var(--color-slate-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-900{--tw-gradient-via:var(--color-slate-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-transparent{--tw-gradient-via:transparent;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-white{--tw-gradient-via:var(--color-white);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-black{--tw-gradient-to:var(--color-black);--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))}.to-blue-50{--tw-gradient-to:var(--color-blue-50);--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))}.to-blue-100{--tw-gradient-to:var(--color-blue-100);--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))}.to-blue-200{--tw-gradient-to:var(--color-blue-200);--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))}.to-blue-300{--tw-gradient-to:var(--color-blue-300);--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))}.to-blue-400{--tw-gradient-to:var(--color-blue-400);--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))}.to-blue-500{--tw-gradient-to:var(--color-blue-500);--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))}.to-blue-600{--tw-gradient-to:var(--color-blue-600);--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))}.to-blue-700{--tw-gradient-to:var(--color-blue-700);--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))}.to-blue-800{--tw-gradient-to:var(--color-blue-800);--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))}.to-blue-900{--tw-gradient-to:var(--color-blue-900);--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))}.to-current{--tw-gradient-to:currentcolor;--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))}.to-gray-50{--tw-gradient-to:var(--color-gray-50);--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))}.to-gray-100{--tw-gradient-to:var(--color-gray-100);--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))}.to-gray-200{--tw-gradient-to:var(--color-gray-200);--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))}.to-gray-300{--tw-gradient-to:var(--color-gray-300);--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))}.to-gray-400{--tw-gradient-to:var(--color-gray-400);--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))}.to-gray-500{--tw-gradient-to:var(--color-gray-500);--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))}.to-gray-600{--tw-gradient-to:var(--color-gray-600);--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))}.to-gray-700{--tw-gradient-to:var(--color-gray-700);--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))}.to-gray-800{--tw-gradient-to:var(--color-gray-800);--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))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--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))}.to-indigo-50{--tw-gradient-to:var(--color-indigo-50);--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))}.to-inherit{--tw-gradient-to:inherit;--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))}.to-purple-50{--tw-gradient-to:var(--color-purple-50);--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))}.to-purple-100{--tw-gradient-to:var(--color-purple-100);--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))}.to-purple-200{--tw-gradient-to:var(--color-purple-200);--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))}.to-purple-300{--tw-gradient-to:var(--color-purple-300);--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))}.to-purple-400{--tw-gradient-to:var(--color-purple-400);--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))}.to-purple-500{--tw-gradient-to:var(--color-purple-500);--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))}.to-purple-600{--tw-gradient-to:var(--color-purple-600);--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))}.to-purple-700{--tw-gradient-to:var(--color-purple-700);--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))}.to-purple-800{--tw-gradient-to:var(--color-purple-800);--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))}.to-purple-900{--tw-gradient-to:var(--color-purple-900);--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))}.to-red-50{--tw-gradient-to:var(--color-red-50);--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))}.to-red-100{--tw-gradient-to:var(--color-red-100);--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))}.to-red-200{--tw-gradient-to:var(--color-red-200);--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))}.to-red-300{--tw-gradient-to:var(--color-red-300);--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))}.to-red-400{--tw-gradient-to:var(--color-red-400);--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))}.to-red-500{--tw-gradient-to:var(--color-red-500);--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))}.to-red-600{--tw-gradient-to:var(--color-red-600);--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))}.to-red-700{--tw-gradient-to:var(--color-red-700);--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))}.to-red-800{--tw-gradient-to:var(--color-red-800);--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))}.to-red-900{--tw-gradient-to:var(--color-red-900);--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))}.to-slate-50{--tw-gradient-to:var(--color-slate-50);--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))}.to-slate-100{--tw-gradient-to:var(--color-slate-100);--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))}.to-slate-200{--tw-gradient-to:var(--color-slate-200);--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))}.to-slate-300{--tw-gradient-to:var(--color-slate-300);--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))}.to-slate-400{--tw-gradient-to:var(--color-slate-400);--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))}.to-slate-500{--tw-gradient-to:var(--color-slate-500);--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))}.to-slate-600{--tw-gradient-to:var(--color-slate-600);--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))}.to-slate-700{--tw-gradient-to:var(--color-slate-700);--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))}.to-slate-800{--tw-gradient-to:var(--color-slate-800);--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))}.to-slate-900{--tw-gradient-to:var(--color-slate-900);--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))}.to-transparent{--tw-gradient-to:transparent;--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))}.to-white{--tw-gradient-to:var(--color-white);--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))}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.object-fill{object-fit:fill}.object-none{object-fit:none}.object-scale-down{object-fit:scale-down}.object-bottom{object-position:bottom}.object-center{object-position:center}.object-left{object-position:left}.object-left-bottom{object-position:left bottom}.object-left-top{object-position:left top}.object-right{object-position:right}.object-right-bottom{object-position:right bottom}.object-right-top{object-position:right top}.object-top{object-position:top}.\!p-0{padding:0!important}.\!p-1{padding:var(--spacing)!important}.\!p-2{padding:calc(var(--spacing) * 2)!important}.\!p-3{padding:calc(var(--spacing) * 3)!important}.\!p-4{padding:calc(var(--spacing) * 4)!important}.\!p-5{padding:calc(var(--spacing) * 5)!important}.\!p-6{padding:calc(var(--spacing) * 6)!important}.\!p-8{padding:calc(var(--spacing) * 8)!important}.\!p-10{padding:calc(var(--spacing) * 10)!important}.\!p-12{padding:calc(var(--spacing) * 12)!important}.\!p-16{padding:calc(var(--spacing) * 16)!important}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.p-10{padding:calc(var(--spacing) * 10)}.p-12{padding:calc(var(--spacing) * 12)}.p-16{padding:calc(var(--spacing) * 16)}.p-20{padding:calc(var(--spacing) * 20)}.p-24{padding:calc(var(--spacing) * 24)}.\!px-0{padding-inline:0!important}.\!px-1{padding-inline:var(--spacing)!important}.\!px-2{padding-inline:calc(var(--spacing) * 2)!important}.\!px-3{padding-inline:calc(var(--spacing) * 3)!important}.\!px-4{padding-inline:calc(var(--spacing) * 4)!important}.\!px-5{padding-inline:calc(var(--spacing) * 5)!important}.\!px-6{padding-inline:calc(var(--spacing) * 6)!important}.\!px-8{padding-inline:calc(var(--spacing) * 8)!important}.\!px-10{padding-inline:calc(var(--spacing) * 10)!important}.\!px-12{padding-inline:calc(var(--spacing) * 12)!important}.\!px-16{padding-inline:calc(var(--spacing) * 16)!important}.px-0{padding-inline:0}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.px-10{padding-inline:calc(var(--spacing) * 10)}.px-12{padding-inline:calc(var(--spacing) * 12)}.px-16{padding-inline:calc(var(--spacing) * 16)}.\!py-0{padding-block:0!important}.\!py-1{padding-block:var(--spacing)!important}.\!py-2{padding-block:calc(var(--spacing) * 2)!important}.\!py-3{padding-block:calc(var(--spacing) * 3)!important}.\!py-4{padding-block:calc(var(--spacing) * 4)!important}.\!py-5{padding-block:calc(var(--spacing) * 5)!important}.\!py-6{padding-block:calc(var(--spacing) * 6)!important}.\!py-8{padding-block:calc(var(--spacing) * 8)!important}.\!py-10{padding-block:calc(var(--spacing) * 10)!important}.\!py-12{padding-block:calc(var(--spacing) * 12)!important}.\!py-16{padding-block:calc(var(--spacing) * 16)!important}.py-0{padding-block:0}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-0{padding-top:0}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pt-1{padding-top:var(--spacing)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pt-16{padding-top:calc(var(--spacing) * 16)}.pr-0{padding-right:0}.pr-1{padding-right:var(--spacing)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pr-5{padding-right:calc(var(--spacing) * 5)}.pr-6{padding-right:calc(var(--spacing) * 6)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-10{padding-right:calc(var(--spacing) * 10)}.pr-12{padding-right:calc(var(--spacing) * 12)}.pr-16{padding-right:calc(var(--spacing) * 16)}.pb-0{padding-bottom:0}.pb-1{padding-bottom:var(--spacing)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pb-10{padding-bottom:calc(var(--spacing) * 10)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pb-16{padding-bottom:calc(var(--spacing) * 16)}.pb-24{padding-bottom:calc(var(--spacing) * 24)}.pl-0{padding-left:0}.pl-1{padding-left:var(--spacing)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-7{padding-left:calc(var(--spacing) * 7)}.pl-8{padding-left:calc(var(--spacing) * 8)}.pl-10{padding-left:calc(var(--spacing) * 10)}.pl-12{padding-left:calc(var(--spacing) * 12)}.pl-16{padding-left:calc(var(--spacing) * 16)}.\!text-center{text-align:center!important}.\!text-justify{text-align:justify!important}.\!text-left{text-align:left!important}.\!text-right{text-align:right!important}.text-center{text-align:center}.text-end{text-align:end}.text-justify{text-align:justify}.text-left{text-align:left}.text-right{text-align:right}.text-start{text-align:start}.indent-0{text-indent:0}.indent-1{text-indent:var(--spacing)}.indent-2{text-indent:calc(var(--spacing) * 2)}.indent-4{text-indent:calc(var(--spacing) * 4)}.indent-8{text-indent:calc(var(--spacing) * 8)}.align-baseline{vertical-align:baseline}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-sub{vertical-align:sub}.align-super{vertical-align:super}.align-text-bottom{vertical-align:text-bottom}.align-text-top{vertical-align:text-top}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.\!text-2xl{font-size:var(--text-2xl)!important;line-height:var(--tw-leading,var(--text-2xl--line-height))!important}.\!text-3xl{font-size:var(--text-3xl)!important;line-height:var(--tw-leading,var(--text-3xl--line-height))!important}.\!text-4xl{font-size:var(--text-4xl)!important;line-height:var(--tw-leading,var(--text-4xl--line-height))!important}.\!text-base{font-size:var(--text-base)!important;line-height:var(--tw-leading,var(--text-base--line-height))!important}.\!text-lg{font-size:var(--text-lg)!important;line-height:var(--tw-leading,var(--text-lg--line-height))!important}.\!text-sm{font-size:var(--text-sm)!important;line-height:var(--tw-leading,var(--text-sm--line-height))!important}.\!text-xl{font-size:var(--text-xl)!important;line-height:var(--tw-leading,var(--text-xl--line-height))!important}.\!text-xs{font-size:var(--text-xs)!important;line-height:var(--tw-leading,var(--text-xs--line-height))!important}.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-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-8xl{font-size:var(--text-8xl);line-height:var(--tw-leading,var(--text-8xl--line-height))}.text-9xl{font-size:var(--text-9xl);line-height:var(--tw-leading,var(--text-9xl--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))}.text-\[2rem\]{font-size:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-3{--tw-leading:calc(var(--spacing) * 3);line-height:calc(var(--spacing) * 3)}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-7{--tw-leading:calc(var(--spacing) * 7);line-height:calc(var(--spacing) * 7)}.leading-8{--tw-leading:calc(var(--spacing) * 8);line-height:calc(var(--spacing) * 8)}.leading-9{--tw-leading:calc(var(--spacing) * 9);line-height:calc(var(--spacing) * 9)}.leading-10{--tw-leading:calc(var(--spacing) * 10);line-height:calc(var(--spacing) * 10)}.leading-loose{--tw-leading:var(--leading-loose);line-height:var(--leading-loose)}.leading-none{--tw-leading:1;line-height:1}.leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.\!font-bold{--tw-font-weight:var(--font-weight-bold)!important;font-weight:var(--font-weight-bold)!important}.\!font-medium{--tw-font-weight:var(--font-weight-medium)!important;font-weight:var(--font-weight-medium)!important}.\!font-normal{--tw-font-weight:var(--font-weight-normal)!important;font-weight:var(--font-weight-normal)!important}.\!font-semibold{--tw-font-weight:var(--font-weight-semibold)!important;font-weight:var(--font-weight-semibold)!important}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-normal{overflow-wrap:normal;word-break:normal}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.break-keep{word-break:keep-all}.text-clip{text-overflow:clip}.text-ellipsis{text-overflow:ellipsis}.whitespace-break-spaces{white-space:break-spaces}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-50{color:var(--color-amber-50)}.text-amber-100{color:var(--color-amber-100)}.text-amber-200{color:var(--color-amber-200)}.text-amber-300{color:var(--color-amber-300)}.text-amber-400{color:var(--color-amber-400)}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-amber-950{color:var(--color-amber-950)}.text-black{color:var(--color-black)}.text-blue-50{color:var(--color-blue-50)}.text-blue-100{color:var(--color-blue-100)}.text-blue-200{color:var(--color-blue-200)}.text-blue-300{color:var(--color-blue-300)}.text-blue-400{color:var(--color-blue-400)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-blue-900{color:var(--color-blue-900)}.text-blue-950{color:var(--color-blue-950)}.text-current{color:currentColor}.text-cyan-50{color:var(--color-cyan-50)}.text-cyan-100{color:var(--color-cyan-100)}.text-cyan-200{color:var(--color-cyan-200)}.text-cyan-300{color:var(--color-cyan-300)}.text-cyan-400{color:var(--color-cyan-400)}.text-cyan-500{color:var(--color-cyan-500)}.text-cyan-600{color:var(--color-cyan-600)}.text-cyan-700{color:var(--color-cyan-700)}.text-cyan-800{color:var(--color-cyan-800)}.text-cyan-900{color:var(--color-cyan-900)}.text-cyan-950{color:var(--color-cyan-950)}.text-emerald-50{color:var(--color-emerald-50)}.text-emerald-100{color:var(--color-emerald-100)}.text-emerald-200{color:var(--color-emerald-200)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-500{color:var(--color-emerald-500)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-emerald-900{color:var(--color-emerald-900)}.text-emerald-950{color:var(--color-emerald-950)}.text-fuchsia-50{color:var(--color-fuchsia-50)}.text-fuchsia-100{color:var(--color-fuchsia-100)}.text-fuchsia-200{color:var(--color-fuchsia-200)}.text-fuchsia-300{color:var(--color-fuchsia-300)}.text-fuchsia-400{color:var(--color-fuchsia-400)}.text-fuchsia-500{color:var(--color-fuchsia-500)}.text-fuchsia-600{color:var(--color-fuchsia-600)}.text-fuchsia-700{color:var(--color-fuchsia-700)}.text-fuchsia-800{color:var(--color-fuchsia-800)}.text-fuchsia-900{color:var(--color-fuchsia-900)}.text-fuchsia-950{color:var(--color-fuchsia-950)}.text-gray-50{color:var(--color-gray-50)}.text-gray-100{color:var(--color-gray-100)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-gray-950{color:var(--color-gray-950)}.text-green-50{color:var(--color-green-50)}.text-green-100{color:var(--color-green-100)}.text-green-200{color:var(--color-green-200)}.text-green-300{color:var(--color-green-300)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-green-950{color:var(--color-green-950)}.text-indigo-50{color:var(--color-indigo-50)}.text-indigo-100{color:var(--color-indigo-100)}.text-indigo-200{color:var(--color-indigo-200)}.text-indigo-300{color:var(--color-indigo-300)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-500{color:var(--color-indigo-500)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-indigo-800{color:var(--color-indigo-800)}.text-indigo-900{color:var(--color-indigo-900)}.text-indigo-950{color:var(--color-indigo-950)}.text-inherit{color:inherit}.text-lime-50{color:var(--color-lime-50)}.text-lime-100{color:var(--color-lime-100)}.text-lime-200{color:var(--color-lime-200)}.text-lime-300{color:var(--color-lime-300)}.text-lime-400{color:var(--color-lime-400)}.text-lime-500{color:var(--color-lime-500)}.text-lime-600{color:var(--color-lime-600)}.text-lime-700{color:var(--color-lime-700)}.text-lime-800{color:var(--color-lime-800)}.text-lime-900{color:var(--color-lime-900)}.text-lime-950{color:var(--color-lime-950)}.text-neutral-50{color:var(--color-neutral-50)}.text-neutral-100{color:var(--color-neutral-100)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-700{color:var(--color-neutral-700)}.text-neutral-800{color:var(--color-neutral-800)}.text-neutral-900{color:var(--color-neutral-900)}.text-neutral-950{color:var(--color-neutral-950)}.text-orange-50{color:var(--color-orange-50)}.text-orange-100{color:var(--color-orange-100)}.text-orange-200{color:var(--color-orange-200)}.text-orange-300{color:var(--color-orange-300)}.text-orange-400{color:var(--color-orange-400)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-orange-800{color:var(--color-orange-800)}.text-orange-900{color:var(--color-orange-900)}.text-orange-950{color:var(--color-orange-950)}.text-pink-50{color:var(--color-pink-50)}.text-pink-100{color:var(--color-pink-100)}.text-pink-200{color:var(--color-pink-200)}.text-pink-300{color:var(--color-pink-300)}.text-pink-400{color:var(--color-pink-400)}.text-pink-500{color:var(--color-pink-500)}.text-pink-600{color:var(--color-pink-600)}.text-pink-700{color:var(--color-pink-700)}.text-pink-800{color:var(--color-pink-800)}.text-pink-900{color:var(--color-pink-900)}.text-pink-950{color:var(--color-pink-950)}.text-purple-50{color:var(--color-purple-50)}.text-purple-100{color:var(--color-purple-100)}.text-purple-200{color:var(--color-purple-200)}.text-purple-300{color:var(--color-purple-300)}.text-purple-400{color:var(--color-purple-400)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-purple-700{color:var(--color-purple-700)}.text-purple-800{color:var(--color-purple-800)}.text-purple-900{color:var(--color-purple-900)}.text-purple-950{color:var(--color-purple-950)}.text-red-50{color:var(--color-red-50)}.text-red-100{color:var(--color-red-100)}.text-red-200{color:var(--color-red-200)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-red-900{color:var(--color-red-900)}.text-red-950{color:var(--color-red-950)}.text-rose-50{color:var(--color-rose-50)}.text-rose-100{color:var(--color-rose-100)}.text-rose-200{color:var(--color-rose-200)}.text-rose-300{color:var(--color-rose-300)}.text-rose-400{color:var(--color-rose-400)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-rose-700{color:var(--color-rose-700)}.text-rose-800{color:var(--color-rose-800)}.text-rose-900{color:var(--color-rose-900)}.text-rose-950{color:var(--color-rose-950)}.text-sky-50{color:var(--color-sky-50)}.text-sky-100{color:var(--color-sky-100)}.text-sky-200{color:var(--color-sky-200)}.text-sky-300{color:var(--color-sky-300)}.text-sky-400{color:var(--color-sky-400)}.text-sky-500{color:var(--color-sky-500)}.text-sky-600{color:var(--color-sky-600)}.text-sky-700{color:var(--color-sky-700)}.text-sky-800{color:var(--color-sky-800)}.text-sky-900{color:var(--color-sky-900)}.text-sky-950{color:var(--color-sky-950)}.text-slate-50{color:var(--color-slate-50)}.text-slate-100{color:var(--color-slate-100)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-slate-800{color:var(--color-slate-800)}.text-slate-900{color:var(--color-slate-900)}.text-slate-950{color:var(--color-slate-950)}.text-stone-50{color:var(--color-stone-50)}.text-stone-100{color:var(--color-stone-100)}.text-stone-200{color:var(--color-stone-200)}.text-stone-300{color:var(--color-stone-300)}.text-stone-400{color:var(--color-stone-400)}.text-stone-500{color:var(--color-stone-500)}.text-stone-600{color:var(--color-stone-600)}.text-stone-700{color:var(--color-stone-700)}.text-stone-800{color:var(--color-stone-800)}.text-stone-900{color:var(--color-stone-900)}.text-stone-950{color:var(--color-stone-950)}.text-teal-50{color:var(--color-teal-50)}.text-teal-100{color:var(--color-teal-100)}.text-teal-200{color:var(--color-teal-200)}.text-teal-300{color:var(--color-teal-300)}.text-teal-400{color:var(--color-teal-400)}.text-teal-500{color:var(--color-teal-500)}.text-teal-600{color:var(--color-teal-600)}.text-teal-700{color:var(--color-teal-700)}.text-teal-800{color:var(--color-teal-800)}.text-teal-900{color:var(--color-teal-900)}.text-teal-950{color:var(--color-teal-950)}.text-transparent{color:#0000}.text-violet-50{color:var(--color-violet-50)}.text-violet-100{color:var(--color-violet-100)}.text-violet-200{color:var(--color-violet-200)}.text-violet-300{color:var(--color-violet-300)}.text-violet-400{color:var(--color-violet-400)}.text-violet-500{color:var(--color-violet-500)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-violet-800{color:var(--color-violet-800)}.text-violet-900{color:var(--color-violet-900)}.text-violet-950{color:var(--color-violet-950)}.text-white{color:var(--color-white)}.text-yellow-50{color:var(--color-yellow-50)}.text-yellow-100{color:var(--color-yellow-100)}.text-yellow-200{color:var(--color-yellow-200)}.text-yellow-300{color:var(--color-yellow-300)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.text-yellow-900{color:var(--color-yellow-900)}.text-yellow-950{color:var(--color-yellow-950)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.text-zinc-950{color:var(--color-zinc-950)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.not-italic{font-style:normal}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.placeholder-gray-400::placeholder{color:var(--color-gray-400)}.accent-blue-500{accent-color:var(--color-blue-500)}.opacity-0{opacity:0}.opacity-5{opacity:.05}.opacity-10{opacity:.1}.opacity-15{opacity:.15}.opacity-20{opacity:.2}.opacity-25{opacity:.25}.opacity-30{opacity:.3}.opacity-35{opacity:.35}.opacity-40{opacity:.4}.opacity-45{opacity:.45}.opacity-50{opacity:.5}.opacity-55{opacity:.55}.opacity-60{opacity:.6}.opacity-65{opacity:.65}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}.opacity-95{opacity:.95}.opacity-100{opacity:1}.bg-blend-color{background-blend-mode:color}.bg-blend-color-burn{background-blend-mode:color-burn}.bg-blend-color-dodge{background-blend-mode:color-dodge}.bg-blend-darken{background-blend-mode:darken}.bg-blend-difference{background-blend-mode:difference}.bg-blend-exclusion{background-blend-mode:exclusion}.bg-blend-hard-light{background-blend-mode:hard-light}.bg-blend-hue{background-blend-mode:hue}.bg-blend-lighten{background-blend-mode:lighten}.bg-blend-luminosity{background-blend-mode:luminosity}.bg-blend-multiply{background-blend-mode:multiply}.bg-blend-normal{background-blend-mode:normal}.bg-blend-overlay{background-blend-mode:overlay}.bg-blend-saturation{background-blend-mode:saturation}.bg-blend-screen{background-blend-mode:screen}.bg-blend-soft-light{background-blend-mode:soft-light}.mix-blend-color{mix-blend-mode:color}.mix-blend-color-burn{mix-blend-mode:color-burn}.mix-blend-color-dodge{mix-blend-mode:color-dodge}.mix-blend-darken{mix-blend-mode:darken}.mix-blend-difference{mix-blend-mode:difference}.mix-blend-exclusion{mix-blend-mode:exclusion}.mix-blend-hard-light{mix-blend-mode:hard-light}.mix-blend-hue{mix-blend-mode:hue}.mix-blend-lighten{mix-blend-mode:lighten}.mix-blend-luminosity{mix-blend-mode:luminosity}.mix-blend-multiply{mix-blend-mode:multiply}.mix-blend-normal{mix-blend-mode:normal}.mix-blend-overlay{mix-blend-mode:overlay}.mix-blend-plus-darker{mix-blend-mode:plus-darker}.mix-blend-plus-lighter{mix-blend-mode:plus-lighter}.mix-blend-saturation{mix-blend-mode:saturation}.mix-blend-screen{mix-blend-mode:screen}.mix-blend-soft-light{mix-blend-mode:soft-light}.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-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);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-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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-none{--tw-shadow:0 0 #0000;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)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + 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)}.ring-1{--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)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + 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)}.ring-8{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + 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)}.shadow-purple-200{--tw-shadow-color:oklch(90.2% .063 306.703)}@supports (color:color-mix(in lab,red,red)){.shadow-purple-200{--tw-shadow-color:color-mix(in oklab, var(--color-purple-200) var(--tw-shadow-alpha), transparent)}}.ring-black{--tw-ring-color:var(--color-black)}.ring-blue-50{--tw-ring-color:var(--color-blue-50)}.ring-blue-100{--tw-ring-color:var(--color-blue-100)}.ring-blue-200{--tw-ring-color:var(--color-blue-200)}.ring-blue-300{--tw-ring-color:var(--color-blue-300)}.ring-blue-400{--tw-ring-color:var(--color-blue-400)}.ring-blue-500{--tw-ring-color:var(--color-blue-500)}.ring-blue-600{--tw-ring-color:var(--color-blue-600)}.ring-blue-700{--tw-ring-color:var(--color-blue-700)}.ring-blue-800{--tw-ring-color:var(--color-blue-800)}.ring-blue-900{--tw-ring-color:var(--color-blue-900)}.ring-current{--tw-ring-color:currentcolor}.ring-gray-50{--tw-ring-color:var(--color-gray-50)}.ring-gray-100{--tw-ring-color:var(--color-gray-100)}.ring-gray-200{--tw-ring-color:var(--color-gray-200)}.ring-gray-300{--tw-ring-color:var(--color-gray-300)}.ring-gray-400{--tw-ring-color:var(--color-gray-400)}.ring-gray-500{--tw-ring-color:var(--color-gray-500)}.ring-gray-600{--tw-ring-color:var(--color-gray-600)}.ring-gray-700{--tw-ring-color:var(--color-gray-700)}.ring-gray-800{--tw-ring-color:var(--color-gray-800)}.ring-gray-900{--tw-ring-color:var(--color-gray-900)}.ring-inherit{--tw-ring-color:inherit}.ring-slate-50{--tw-ring-color:var(--color-slate-50)}.ring-slate-100{--tw-ring-color:var(--color-slate-100)}.ring-slate-200{--tw-ring-color:var(--color-slate-200)}.ring-slate-300{--tw-ring-color:var(--color-slate-300)}.ring-slate-400{--tw-ring-color:var(--color-slate-400)}.ring-slate-500{--tw-ring-color:var(--color-slate-500)}.ring-slate-600{--tw-ring-color:var(--color-slate-600)}.ring-slate-700{--tw-ring-color:var(--color-slate-700)}.ring-slate-800{--tw-ring-color:var(--color-slate-800)}.ring-slate-900{--tw-ring-color:var(--color-slate-900)}.ring-transparent{--tw-ring-color:transparent}.ring-white{--tw-ring-color:var(--color-white)}.ring-offset-0{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-4{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-8{--tw-ring-offset-width:8px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-black{--tw-ring-offset-color:var(--color-black)}.ring-offset-current{--tw-ring-offset-color:currentcolor}.ring-offset-inherit{--tw-ring-offset-color:inherit}.ring-offset-transparent{--tw-ring-offset-color:transparent}.ring-offset-white{--tw-ring-offset-color:var(--color-white)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-0{outline-style:var(--tw-outline-style);outline-width:0}.outline-1{outline-style:var(--tw-outline-style);outline-width:1px}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-4{outline-style:var(--tw-outline-style);outline-width:4px}.outline-8{outline-style:var(--tw-outline-style);outline-width:8px}.outline-offset-0{outline-offset:0px}.outline-offset-1{outline-offset:1px}.outline-offset-2{outline-offset:2px}.outline-offset-4{outline-offset:4px}.outline-offset-8{outline-offset:8px}.outline-black{outline-color:var(--color-black)}.outline-current{outline-color:currentColor}.outline-inherit{outline-color:inherit}.outline-transparent{outline-color:#0000}.outline-white{outline-color:var(--color-white)}.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,)}.blur-2xl{--tw-blur:blur(var(--blur-2xl));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,)}.blur-3xl{--tw-blur:blur(var(--blur-3xl));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,)}.blur-lg{--tw-blur:blur(var(--blur-lg));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,)}.blur-md{--tw-blur:blur(var(--blur-md));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,)}.blur-none{--tw-blur: ;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,)}.blur-sm{--tw-blur:blur(var(--blur-sm));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,)}.blur-xl{--tw-blur:blur(var(--blur-xl));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,)}.brightness-0{--tw-brightness:brightness(0%);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,)}.brightness-50{--tw-brightness:brightness(50%);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,)}.brightness-75{--tw-brightness:brightness(75%);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,)}.brightness-90{--tw-brightness:brightness(90%);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,)}.brightness-95{--tw-brightness:brightness(95%);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,)}.brightness-100{--tw-brightness:brightness(100%);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,)}.brightness-105{--tw-brightness:brightness(105%);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,)}.brightness-110{--tw-brightness:brightness(110%);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,)}.brightness-125{--tw-brightness:brightness(125%);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,)}.brightness-150{--tw-brightness:brightness(150%);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,)}.brightness-200{--tw-brightness:brightness(200%);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,)}.contrast-0{--tw-contrast:contrast(0%);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,)}.contrast-50{--tw-contrast:contrast(50%);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,)}.contrast-75{--tw-contrast:contrast(75%);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,)}.contrast-100{--tw-contrast:contrast(100%);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,)}.contrast-125{--tw-contrast:contrast(125%);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,)}.contrast-150{--tw-contrast:contrast(150%);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,)}.contrast-200{--tw-contrast:contrast(200%);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,)}.drop-shadow-2xl{--tw-drop-shadow-size:drop-shadow(0 25px 25px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-2xl));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-lg{--tw-drop-shadow-size:drop-shadow(0 4px 4px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-lg));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-md{--tw-drop-shadow-size:drop-shadow(0 3px 3px var(--tw-drop-shadow-color,#0000001f));--tw-drop-shadow:drop-shadow(var(--drop-shadow-md));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-sm{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-sm));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-xl{--tw-drop-shadow-size:drop-shadow(0 9px 7px var(--tw-drop-shadow-color,#0000001a));--tw-drop-shadow:drop-shadow(var(--drop-shadow-xl));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-none{--tw-drop-shadow: ;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,)}.grayscale{--tw-grayscale:grayscale(100%);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,)}.grayscale-0{--tw-grayscale:grayscale(0%);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,)}.-hue-rotate-15{--tw-hue-rotate:hue-rotate( -15deg );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,)}.-hue-rotate-30{--tw-hue-rotate:hue-rotate( -30deg );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,)}.-hue-rotate-60{--tw-hue-rotate:hue-rotate( -60deg );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,)}.-hue-rotate-90{--tw-hue-rotate:hue-rotate( -90deg );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,)}.-hue-rotate-180{--tw-hue-rotate:hue-rotate( -180deg );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,)}.hue-rotate-0{--tw-hue-rotate:hue-rotate(0deg);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,)}.hue-rotate-15{--tw-hue-rotate:hue-rotate(15deg);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,)}.hue-rotate-30{--tw-hue-rotate:hue-rotate(30deg);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,)}.hue-rotate-60{--tw-hue-rotate:hue-rotate(60deg);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,)}.hue-rotate-90{--tw-hue-rotate:hue-rotate(90deg);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,)}.hue-rotate-180{--tw-hue-rotate:hue-rotate(180deg);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,)}.invert{--tw-invert:invert(100%);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,)}.invert-0{--tw-invert:invert(0%);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,)}.saturate-0{--tw-saturate:saturate(0%);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,)}.saturate-50{--tw-saturate:saturate(50%);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,)}.saturate-100{--tw-saturate:saturate(100%);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,)}.saturate-150{--tw-saturate:saturate(150%);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,)}.saturate-200{--tw-saturate:saturate(200%);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,)}.sepia{--tw-sepia:sepia(100%);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,)}.sepia-0{--tw-sepia:sepia(0%);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,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-2xl{--tw-backdrop-blur:blur(var(--blur-2xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-3xl{--tw-backdrop-blur:blur(var(--blur-3xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-lg{--tw-backdrop-blur:blur(var(--blur-lg));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-none{--tw-backdrop-blur: ;-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-0{--tw-backdrop-brightness:brightness(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-50{--tw-backdrop-brightness:brightness(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-75{--tw-backdrop-brightness:brightness(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-90{--tw-backdrop-brightness:brightness(90%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-95{--tw-backdrop-brightness:brightness(95%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-100{--tw-backdrop-brightness:brightness(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-105{--tw-backdrop-brightness:brightness(105%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-110{--tw-backdrop-brightness:brightness(110%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-125{--tw-backdrop-brightness:brightness(125%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-150{--tw-backdrop-brightness:brightness(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-200{--tw-backdrop-brightness:brightness(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-0{--tw-backdrop-contrast:contrast(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-50{--tw-backdrop-contrast:contrast(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-75{--tw-backdrop-contrast:contrast(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-100{--tw-backdrop-contrast:contrast(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-125{--tw-backdrop-contrast:contrast(125%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-150{--tw-backdrop-contrast:contrast(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-200{--tw-backdrop-contrast:contrast(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-grayscale{--tw-backdrop-grayscale:grayscale(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-grayscale-0{--tw-backdrop-grayscale:grayscale(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-0{--tw-backdrop-hue-rotate:hue-rotate(0deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-15{--tw-backdrop-hue-rotate:hue-rotate(15deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-30{--tw-backdrop-hue-rotate:hue-rotate(30deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-60{--tw-backdrop-hue-rotate:hue-rotate(60deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-90{--tw-backdrop-hue-rotate:hue-rotate(90deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-180{--tw-backdrop-hue-rotate:hue-rotate(180deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-invert{--tw-backdrop-invert:invert(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-invert-0{--tw-backdrop-invert:invert(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-0{--tw-backdrop-opacity:opacity(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-5{--tw-backdrop-opacity:opacity(5%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-10{--tw-backdrop-opacity:opacity(10%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-20{--tw-backdrop-opacity:opacity(20%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-25{--tw-backdrop-opacity:opacity(25%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-30{--tw-backdrop-opacity:opacity(30%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-40{--tw-backdrop-opacity:opacity(40%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-50{--tw-backdrop-opacity:opacity(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-60{--tw-backdrop-opacity:opacity(60%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-70{--tw-backdrop-opacity:opacity(70%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-75{--tw-backdrop-opacity:opacity(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-80{--tw-backdrop-opacity:opacity(80%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-90{--tw-backdrop-opacity:opacity(90%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-95{--tw-backdrop-opacity:opacity(95%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-100{--tw-backdrop-opacity:opacity(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-0{--tw-backdrop-saturate:saturate(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-50{--tw-backdrop-saturate:saturate(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-100{--tw-backdrop-saturate:saturate(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-150{--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-200{--tw-backdrop-saturate:saturate(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-sepia{--tw-backdrop-sepia:sepia(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-sepia-0{--tw-backdrop-sepia:sepia(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.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))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-none{transition-property:none}.delay-75{transition-delay:75ms}.delay-100{transition-delay:.1s}.delay-150{transition-delay:.15s}.delay-200{transition-delay:.2s}.delay-300{transition-delay:.3s}.delay-500{transition-delay:.5s}.delay-700{transition-delay:.7s}.delay-1000{transition-delay:1s}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.duration-700{--tw-duration:.7s;transition-duration:.7s}.duration-1000{--tw-duration:1s;transition-duration:1s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-linear{--tw-ease:linear;transition-timing-function:linear}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-dashed{--tw-outline-style:dashed;outline-style:dashed}.outline-dotted{--tw-outline-style:dotted;outline-style:dotted}.outline-double{--tw-outline-style:double;outline-style:double}.outline-none{--tw-outline-style:none;outline-style:none}.select-all{-webkit-user-select:all;user-select:all}.select-auto{-webkit-user-select:auto;user-select:auto}.select-none{-webkit-user-select:none;user-select:none}.select-text{-webkit-user-select:text;user-select:text}:where(.divide-x-reverse>:not(:last-child)){--tw-divide-x-reverse:1}.ring-inset{--tw-ring-inset:inset}@media(hover:hover){.group-hover\:visible:is(:where(.group):hover *){visibility:visible}.group-hover\:translate-x-1:is(:where(.group):hover *){--tw-translate-x:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:translate-y-1:is(:where(.group):hover *){--tw-translate-y:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:scale-100:is(:where(.group):hover *){--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:scale-105:is(:where(.group):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:bg-black\/40:is(:where(.group):hover *){background-color:#0006}@supports (color:color-mix(in lab,red,red)){.group-hover\:bg-black\/40:is(:where(.group):hover *){background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.group-hover\:bg-blue-100:is(:where(.group):hover *){background-color:var(--color-blue-100)}.group-hover\:bg-blue-500:is(:where(.group):hover *){background-color:var(--color-blue-500)}.group-hover\:bg-blue-600:is(:where(.group):hover *){background-color:var(--color-blue-600)}.group-hover\:bg-gray-100:is(:where(.group):hover *){background-color:var(--color-gray-100)}.group-hover\:bg-gray-200:is(:where(.group):hover *){background-color:var(--color-gray-200)}.group-hover\:bg-gray-700:is(:where(.group):hover *){background-color:var(--color-gray-700)}.group-hover\:bg-gray-800:is(:where(.group):hover *){background-color:var(--color-gray-800)}.group-hover\:text-black:is(:where(.group):hover *){color:var(--color-black)}.group-hover\:text-blue-500:is(:where(.group):hover *){color:var(--color-blue-500)}.group-hover\:text-blue-600:is(:where(.group):hover *){color:var(--color-blue-600)}.group-hover\:text-blue-700:is(:where(.group):hover *){color:var(--color-blue-700)}.group-hover\:text-gray-700:is(:where(.group):hover *){color:var(--color-gray-700)}.group-hover\:text-gray-800:is(:where(.group):hover *){color:var(--color-gray-800)}.group-hover\:text-gray-900:is(:where(.group):hover *){color:var(--color-gray-900)}.group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.group-focus\:border-blue-500:is(:where(.group):focus *){border-color:var(--color-blue-500)}.group-focus\:ring-2:is(:where(.group):focus *){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.peer-checked\:border-blue-500:is(:where(.peer):checked~*){border-color:var(--color-blue-500)}.peer-checked\:bg-blue-500:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.peer-invalid\:border-red-500:is(:where(.peer):invalid~*){border-color:var(--color-red-500)}.peer-invalid\:text-red-500:is(:where(.peer):invalid~*){color:var(--color-red-500)}.peer-focus\:border-blue-500:is(:where(.peer):focus~*){border-color:var(--color-blue-500)}.peer-focus\:ring-2:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.first-letter\:text-2xl:first-letter{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.first-letter\:font-bold:first-letter{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.first-letter\:text-blue-600:first-letter{color:var(--color-blue-600)}.first-line\:font-semibold:first-line{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.first-line\:text-gray-700:first-line{color:var(--color-gray-700)}.marker\:text-blue-500 ::marker{color:var(--color-blue-500)}.marker\:text-blue-500::marker{color:var(--color-blue-500)}.marker\:text-blue-500 ::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-blue-500::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-gray-500 ::marker{color:var(--color-gray-500)}.marker\:text-gray-500::marker{color:var(--color-gray-500)}.marker\:text-gray-500 ::-webkit-details-marker{color:var(--color-gray-500)}.marker\:text-gray-500::-webkit-details-marker{color:var(--color-gray-500)}.selection\:bg-blue-200 ::selection{background-color:var(--color-blue-200)}.selection\:bg-blue-200::selection{background-color:var(--color-blue-200)}.selection\:text-blue-900 ::selection{color:var(--color-blue-900)}.selection\:text-blue-900::selection{color:var(--color-blue-900)}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing) * 4)}.file\:cursor-pointer::file-selector-button{cursor:pointer}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-blue-50::file-selector-button{background-color:var(--color-blue-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing) * 4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing) * 2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-blue-700::file-selector-button{color:var(--color-blue-700)}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:inset-0:before{content:var(--tw-content);inset:0}.before\:block:before{content:var(--tw-content);display:block}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-0:after{content:var(--tw-content);inset:0}.after\:clear-both:after{content:var(--tw-content);clear:both}.after\:block:after{content:var(--tw-content);display:block}.after\:h-4:after{content:var(--tw-content);height:calc(var(--spacing) * 4)}.after\:h-5:after{content:var(--tw-content);height:calc(var(--spacing) * 5)}.after\:h-6:after{content:var(--tw-content);height:calc(var(--spacing) * 6)}.after\:w-4:after{content:var(--tw-content);width:calc(var(--spacing) * 4)}.after\:w-5:after{content:var(--tw-content);width:calc(var(--spacing) * 5)}.after\:w-6:after{content:var(--tw-content);width:calc(var(--spacing) * 6)}.after\:content-\[\'\'\]:after{--tw-content:"";content:var(--tw-content)}.first\:mt-0:first-child{margin-top:0}.first\:rounded-t-lg:first-child{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.first\:rounded-l:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.first\:rounded-l-lg:first-child{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.first\:border-t-0:first-child{border-top-style:var(--tw-border-style);border-top-width:0}.first\:pt-0:first-child{padding-top:0}.last\:mb-0:last-child{margin-bottom:0}.last\:rounded-r:last-child{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.last\:rounded-r-lg:last-child{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.last\:rounded-b-lg:last-child{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.last\:border-r:last-child{border-right-style:var(--tw-border-style);border-right-width:1px}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.last\:pb-0:last-child{padding-bottom:0}.odd\:bg-gray-50:nth-child(odd){background-color:var(--color-gray-50)}.odd\:bg-gray-100:nth-child(odd){background-color:var(--color-gray-100)}.odd\:bg-white:nth-child(odd){background-color:var(--color-white)}.even\:bg-gray-50:nth-child(2n){background-color:var(--color-gray-50)}.even\:bg-gray-100:nth-child(2n){background-color:var(--color-gray-100)}.even\:bg-white:nth-child(2n){background-color:var(--color-white)}.checked\:border-blue-500:checked{border-color:var(--color-blue-500)}.checked\:border-blue-600:checked{border-color:var(--color-blue-600)}.checked\:bg-blue-500:checked{background-color:var(--color-blue-500)}.checked\:bg-blue-600:checked{background-color:var(--color-blue-600)}.checked\:bg-green-500:checked{background-color:var(--color-green-500)}.checked\:text-white:checked{color:var(--color-white)}.indeterminate\:border-gray-400:indeterminate{border-color:var(--color-gray-400)}.indeterminate\:bg-gray-300:indeterminate{background-color:var(--color-gray-300)}.placeholder-shown\:border-gray-300:placeholder-shown{border-color:var(--color-gray-300)}.autofill\:bg-yellow-100:autofill{background-color:var(--color-yellow-100)}.autofill\:text-black:autofill{color:var(--color-black)}.valid\:border-green-500:valid{border-color:var(--color-green-500)}.valid\:text-green-600:valid{color:var(--color-green-600)}.valid\:ring-green-500:valid{--tw-ring-color:var(--color-green-500)}.invalid\:border-red-500:invalid{border-color:var(--color-red-500)}.invalid\:text-red-600:invalid{color:var(--color-red-600)}.invalid\:ring-red-500:invalid{--tw-ring-color:var(--color-red-500)}.read-only\:cursor-not-allowed:read-only{cursor:not-allowed}.read-only\:bg-gray-50:read-only{background-color:var(--color-gray-50)}.read-only\:bg-gray-100:read-only{background-color:var(--color-gray-100)}.empty\:hidden:empty{display:none}.focus-within\:border-blue-500:focus-within{border-color:var(--color-blue-500)}.focus-within\:border-gray-500:focus-within{border-color:var(--color-gray-500)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.focus-within\:ring-blue-500:focus-within{--tw-ring-color:var(--color-blue-500)}.focus-within\:ring-gray-500:focus-within{--tw-ring-color:var(--color-gray-500)}@media(hover:hover){.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:scale-95:hover{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-100:hover{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:border-blue-50:hover{border-color:var(--color-blue-50)}.hover\:border-blue-100:hover{border-color:var(--color-blue-100)}.hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.hover\:border-blue-600:hover{border-color:var(--color-blue-600)}.hover\:border-blue-700:hover{border-color:var(--color-blue-700)}.hover\:border-blue-800:hover{border-color:var(--color-blue-800)}.hover\:border-blue-900:hover{border-color:var(--color-blue-900)}.hover\:border-gray-50:hover{border-color:var(--color-gray-50)}.hover\:border-gray-100:hover{border-color:var(--color-gray-100)}.hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.hover\:border-gray-600:hover{border-color:var(--color-gray-600)}.hover\:border-gray-700:hover{border-color:var(--color-gray-700)}.hover\:border-gray-800:hover{border-color:var(--color-gray-800)}.hover\:border-gray-900:hover{border-color:var(--color-gray-900)}.hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-black\/10:hover{background-color:#0000001a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/10:hover{background-color:color-mix(in oklab,var(--color-black) 10%,transparent)}}.hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.hover\:bg-blue-300:hover{background-color:var(--color-blue-300)}.hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-blue-800:hover{background-color:var(--color-blue-800)}.hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.hover\:bg-green-50:hover{background-color:var(--color-green-50)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-200:hover{background-color:var(--color-green-200)}.hover\:bg-green-300:hover{background-color:var(--color-green-300)}.hover\:bg-green-400:hover{background-color:var(--color-green-400)}.hover\:bg-green-500:hover{background-color:var(--color-green-500)}.hover\:bg-green-600:hover{background-color:var(--color-green-600)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-green-800:hover{background-color:var(--color-green-800)}.hover\:bg-green-900:hover{background-color:var(--color-green-900)}.hover\:bg-indigo-50:hover{background-color:var(--color-indigo-50)}.hover\:bg-indigo-100:hover{background-color:var(--color-indigo-100)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-300:hover{background-color:var(--color-indigo-300)}.hover\:bg-indigo-400:hover{background-color:var(--color-indigo-400)}.hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-indigo-800:hover{background-color:var(--color-indigo-800)}.hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.hover\:bg-orange-300:hover{background-color:var(--color-orange-300)}.hover\:bg-orange-400:hover{background-color:var(--color-orange-400)}.hover\:bg-orange-500:hover{background-color:var(--color-orange-500)}.hover\:bg-orange-600:hover{background-color:var(--color-orange-600)}.hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.hover\:bg-pink-50:hover{background-color:var(--color-pink-50)}.hover\:bg-pink-100:hover{background-color:var(--color-pink-100)}.hover\:bg-pink-200:hover{background-color:var(--color-pink-200)}.hover\:bg-pink-300:hover{background-color:var(--color-pink-300)}.hover\:bg-pink-400:hover{background-color:var(--color-pink-400)}.hover\:bg-pink-500:hover{background-color:var(--color-pink-500)}.hover\:bg-pink-600:hover{background-color:var(--color-pink-600)}.hover\:bg-pink-700:hover{background-color:var(--color-pink-700)}.hover\:bg-pink-800:hover{background-color:var(--color-pink-800)}.hover\:bg-pink-900:hover{background-color:var(--color-pink-900)}.hover\:bg-purple-50:hover{background-color:var(--color-purple-50)}.hover\:bg-purple-100:hover{background-color:var(--color-purple-100)}.hover\:bg-purple-200:hover{background-color:var(--color-purple-200)}.hover\:bg-purple-300:hover{background-color:var(--color-purple-300)}.hover\:bg-purple-400:hover{background-color:var(--color-purple-400)}.hover\:bg-purple-500:hover{background-color:var(--color-purple-500)}.hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-purple-800:hover{background-color:var(--color-purple-800)}.hover\:bg-purple-900:hover{background-color:var(--color-purple-900)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-100:hover{background-color:var(--color-red-100)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-300:hover{background-color:var(--color-red-300)}.hover\:bg-red-400:hover{background-color:var(--color-red-400)}.hover\:bg-red-500:hover{background-color:var(--color-red-500)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-red-800:hover{background-color:var(--color-red-800)}.hover\:bg-red-900:hover{background-color:var(--color-red-900)}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}.hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}.hover\:bg-slate-600:hover{background-color:var(--color-slate-600)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.hover\:bg-slate-900:hover{background-color:var(--color-slate-900)}.hover\:bg-transparent:hover{background-color:#0000}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:bg-yellow-200:hover{background-color:var(--color-yellow-200)}.hover\:bg-yellow-300:hover{background-color:var(--color-yellow-300)}.hover\:bg-yellow-400:hover{background-color:var(--color-yellow-400)}.hover\:bg-yellow-500:hover{background-color:var(--color-yellow-500)}.hover\:bg-yellow-600:hover{background-color:var(--color-yellow-600)}.hover\:bg-yellow-700:hover{background-color:var(--color-yellow-700)}.hover\:bg-yellow-800:hover{background-color:var(--color-yellow-800)}.hover\:bg-yellow-900:hover{background-color:var(--color-yellow-900)}.hover\:text-black:hover{color:var(--color-black)}.hover\:text-blue-50:hover{color:var(--color-blue-50)}.hover\:text-blue-100:hover{color:var(--color-blue-100)}.hover\:text-blue-200:hover{color:var(--color-blue-200)}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-blue-900:hover{color:var(--color-blue-900)}.hover\:text-gray-50:hover{color:var(--color-gray-50)}.hover\:text-gray-100:hover{color:var(--color-gray-100)}.hover\:text-gray-200:hover{color:var(--color-gray-200)}.hover\:text-gray-300:hover{color:var(--color-gray-300)}.hover\:text-gray-400:hover{color:var(--color-gray-400)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-800:hover{color:var(--color-gray-800)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-50:hover{color:var(--color-green-50)}.hover\:text-green-100:hover{color:var(--color-green-100)}.hover\:text-green-200:hover{color:var(--color-green-200)}.hover\:text-green-300:hover{color:var(--color-green-300)}.hover\:text-green-400:hover{color:var(--color-green-400)}.hover\:text-green-500:hover{color:var(--color-green-500)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-green-700:hover{color:var(--color-green-700)}.hover\:text-green-800:hover{color:var(--color-green-800)}.hover\:text-green-900:hover{color:var(--color-green-900)}.hover\:text-purple-50:hover{color:var(--color-purple-50)}.hover\:text-purple-100:hover{color:var(--color-purple-100)}.hover\:text-purple-200:hover{color:var(--color-purple-200)}.hover\:text-purple-300:hover{color:var(--color-purple-300)}.hover\:text-purple-400:hover{color:var(--color-purple-400)}.hover\:text-purple-500:hover{color:var(--color-purple-500)}.hover\:text-purple-600:hover{color:var(--color-purple-600)}.hover\:text-purple-700:hover{color:var(--color-purple-700)}.hover\:text-purple-800:hover{color:var(--color-purple-800)}.hover\:text-purple-900:hover{color:var(--color-purple-900)}.hover\:text-red-50:hover{color:var(--color-red-50)}.hover\:text-red-100:hover{color:var(--color-red-100)}.hover\:text-red-200:hover{color:var(--color-red-200)}.hover\:text-red-300:hover{color:var(--color-red-300)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-red-800:hover{color:var(--color-red-800)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:text-slate-50:hover{color:var(--color-slate-50)}.hover\:text-slate-100:hover{color:var(--color-slate-100)}.hover\:text-slate-200:hover{color:var(--color-slate-200)}.hover\:text-slate-300:hover{color:var(--color-slate-300)}.hover\:text-slate-400:hover{color:var(--color-slate-400)}.hover\:text-slate-500:hover{color:var(--color-slate-500)}.hover\:text-slate-600:hover{color:var(--color-slate-600)}.hover\:text-slate-700:hover{color:var(--color-slate-700)}.hover\:text-slate-800:hover{color:var(--color-slate-800)}.hover\:text-slate-900:hover{color:var(--color-slate-900)}.hover\:text-white:hover{color:var(--color-white)}.hover\:line-through:hover{text-decoration-line:line-through}.hover\:no-underline:hover{text-decoration-line:none}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-0:hover{opacity:0}.hover\:opacity-25:hover{opacity:.25}.hover\:opacity-50:hover{opacity:.5}.hover\:opacity-75:hover{opacity:.75}.hover\:opacity-100:hover{opacity:1}.hover\:shadow:hover{--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)}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--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)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}.hover\:shadow-none:hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-sm:hover{--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)}.hover\:shadow-xl:hover{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.hover\:ring-0:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + 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)}.hover\:ring-1:hover{--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)}.hover\:ring-2:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.hover\:ring-4:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + 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)}.hover\:ring-8:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + 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)}.hover\:ring-blue-500:hover{--tw-ring-color:var(--color-blue-500)}.hover\:ring-gray-500:hover{--tw-ring-color:var(--color-gray-500)}.hover\:ring-green-500:hover{--tw-ring-color:var(--color-green-500)}.hover\:ring-red-500:hover{--tw-ring-color:var(--color-red-500)}.file\:hover\:bg-blue-100::file-selector-button:hover{background-color:var(--color-blue-100)}}.focus\:border-blue-50:focus{border-color:var(--color-blue-50)}.focus\:border-blue-100:focus{border-color:var(--color-blue-100)}.focus\:border-blue-200:focus{border-color:var(--color-blue-200)}.focus\:border-blue-300:focus{border-color:var(--color-blue-300)}.focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:border-blue-600:focus{border-color:var(--color-blue-600)}.focus\:border-blue-700:focus{border-color:var(--color-blue-700)}.focus\:border-blue-800:focus{border-color:var(--color-blue-800)}.focus\:border-blue-900:focus{border-color:var(--color-blue-900)}.focus\:border-gray-50:focus{border-color:var(--color-gray-50)}.focus\:border-gray-100:focus{border-color:var(--color-gray-100)}.focus\:border-gray-200:focus{border-color:var(--color-gray-200)}.focus\:border-gray-300:focus{border-color:var(--color-gray-300)}.focus\:border-gray-400:focus{border-color:var(--color-gray-400)}.focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.focus\:border-gray-600:focus{border-color:var(--color-gray-600)}.focus\:border-gray-700:focus{border-color:var(--color-gray-700)}.focus\:border-gray-800:focus{border-color:var(--color-gray-800)}.focus\:border-gray-900:focus{border-color:var(--color-gray-900)}.focus\:border-green-500:focus{border-color:var(--color-green-500)}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-purple-500:focus{border-color:var(--color-purple-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:border-transparent:focus{border-color:#0000}.focus\:border-yellow-500:focus{border-color:var(--color-yellow-500)}.focus\:bg-black:focus{background-color:var(--color-black)}.focus\:bg-blue-50:focus{background-color:var(--color-blue-50)}.focus\:bg-blue-100:focus{background-color:var(--color-blue-100)}.focus\:bg-blue-200:focus{background-color:var(--color-blue-200)}.focus\:bg-blue-300:focus{background-color:var(--color-blue-300)}.focus\:bg-blue-400:focus{background-color:var(--color-blue-400)}.focus\:bg-blue-500:focus{background-color:var(--color-blue-500)}.focus\:bg-blue-600:focus{background-color:var(--color-blue-600)}.focus\:bg-blue-700:focus{background-color:var(--color-blue-700)}.focus\:bg-blue-800:focus{background-color:var(--color-blue-800)}.focus\:bg-blue-900:focus{background-color:var(--color-blue-900)}.focus\:bg-gray-50:focus{background-color:var(--color-gray-50)}.focus\:bg-gray-100:focus{background-color:var(--color-gray-100)}.focus\:bg-gray-200:focus{background-color:var(--color-gray-200)}.focus\:bg-gray-300:focus{background-color:var(--color-gray-300)}.focus\:bg-gray-400:focus{background-color:var(--color-gray-400)}.focus\:bg-gray-500:focus{background-color:var(--color-gray-500)}.focus\:bg-gray-600:focus{background-color:var(--color-gray-600)}.focus\:bg-gray-700:focus{background-color:var(--color-gray-700)}.focus\:bg-gray-800:focus{background-color:var(--color-gray-800)}.focus\:bg-gray-900:focus{background-color:var(--color-gray-900)}.focus\:bg-transparent:focus{background-color:#0000}.focus\:bg-white:focus{background-color:var(--color-white)}.focus\:text-black:focus{color:var(--color-black)}.focus\:text-blue-50:focus{color:var(--color-blue-50)}.focus\:text-blue-100:focus{color:var(--color-blue-100)}.focus\:text-blue-200:focus{color:var(--color-blue-200)}.focus\:text-blue-300:focus{color:var(--color-blue-300)}.focus\:text-blue-400:focus{color:var(--color-blue-400)}.focus\:text-blue-500:focus{color:var(--color-blue-500)}.focus\:text-blue-600:focus{color:var(--color-blue-600)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-blue-800:focus{color:var(--color-blue-800)}.focus\:text-blue-900:focus{color:var(--color-blue-900)}.focus\:text-gray-50:focus{color:var(--color-gray-50)}.focus\:text-gray-100:focus{color:var(--color-gray-100)}.focus\:text-gray-200:focus{color:var(--color-gray-200)}.focus\:text-gray-300:focus{color:var(--color-gray-300)}.focus\:text-gray-400:focus{color:var(--color-gray-400)}.focus\:text-gray-500:focus{color:var(--color-gray-500)}.focus\:text-gray-600:focus{color:var(--color-gray-600)}.focus\:text-gray-700:focus{color:var(--color-gray-700)}.focus\:text-gray-800:focus{color:var(--color-gray-800)}.focus\:text-gray-900:focus{color:var(--color-gray-900)}.focus\:text-white:focus{color:var(--color-white)}.focus\:shadow:focus{--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)}.focus\:shadow-lg:focus{--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)}.focus\:shadow-md:focus{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}.focus\:shadow-none:focus{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-sm:focus{--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)}.focus\:shadow-xl:focus{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + 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)}.focus\:ring-1:focus{--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)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.focus\:ring-4:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + 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)}.focus\:ring-8:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + 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)}.focus\:ring-amber-500:focus{--tw-ring-color:var(--color-amber-500)}.focus\:ring-blue-50:focus{--tw-ring-color:var(--color-blue-50)}.focus\:ring-blue-100:focus{--tw-ring-color:var(--color-blue-100)}.focus\:ring-blue-200:focus{--tw-ring-color:var(--color-blue-200)}.focus\:ring-blue-300:focus{--tw-ring-color:var(--color-blue-300)}.focus\:ring-blue-400:focus{--tw-ring-color:var(--color-blue-400)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-600:focus{--tw-ring-color:var(--color-blue-600)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-blue-800:focus{--tw-ring-color:var(--color-blue-800)}.focus\:ring-blue-900:focus{--tw-ring-color:var(--color-blue-900)}.focus\:ring-current:focus{--tw-ring-color:currentcolor}.focus\:ring-gray-50:focus{--tw-ring-color:var(--color-gray-50)}.focus\:ring-gray-100:focus{--tw-ring-color:var(--color-gray-100)}.focus\:ring-gray-200:focus{--tw-ring-color:var(--color-gray-200)}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.focus\:ring-gray-700:focus{--tw-ring-color:var(--color-gray-700)}.focus\:ring-gray-800:focus{--tw-ring-color:var(--color-gray-800)}.focus\:ring-gray-900:focus{--tw-ring-color:var(--color-gray-900)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-slate-400:focus{--tw-ring-color:var(--color-slate-400)}.focus\:ring-transparent:focus{--tw-ring-color:transparent}.focus\:ring-yellow-500:focus{--tw-ring-color:var(--color-yellow-500)}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-4:focus{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-gray-100:focus{--tw-ring-offset-color:var(--color-gray-100)}.focus\:ring-offset-gray-900:focus{--tw-ring-offset-color:var(--color-gray-900)}.focus\:ring-offset-white:focus{--tw-ring-offset-color:var(--color-white)}.focus\:outline:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-0:focus{outline-style:var(--tw-outline-style);outline-width:0}.focus\:outline-1:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-2:focus{outline-style:var(--tw-outline-style);outline-width:2px}.focus\:outline-4:focus{outline-style:var(--tw-outline-style);outline-width:4px}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.focus-visible\:ring-blue-500:focus-visible{--tw-ring-color:var(--color-blue-500)}.focus-visible\:ring-gray-500:focus-visible{--tw-ring-color:var(--color-gray-500)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:outline:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:scale-100:active{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:cursor-grabbing:active{cursor:grabbing}.active\:bg-blue-600:active{background-color:var(--color-blue-600)}.active\:bg-blue-700:active{background-color:var(--color-blue-700)}.active\:bg-blue-800:active{background-color:var(--color-blue-800)}.active\:bg-gray-100:active{background-color:var(--color-gray-100)}.active\:bg-gray-200:active{background-color:var(--color-gray-200)}.active\:bg-gray-300:active{background-color:var(--color-gray-300)}.active\:bg-gray-700:active{background-color:var(--color-gray-700)}.active\:bg-gray-800:active{background-color:var(--color-gray-800)}.active\:bg-green-600:active{background-color:var(--color-green-600)}.active\:bg-green-700:active{background-color:var(--color-green-700)}.active\:bg-red-600:active{background-color:var(--color-red-600)}.active\:bg-red-700:active{background-color:var(--color-red-700)}.active\:opacity-75:active{opacity:.75}.active\:opacity-100:active{opacity:1}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:border-gray-200:disabled{border-color:var(--color-gray-200)}.disabled\:border-gray-300:disabled{border-color:var(--color-gray-300)}.disabled\:bg-blue-400:disabled{background-color:var(--color-blue-400)}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:bg-gray-200:disabled{background-color:var(--color-gray-200)}.disabled\:bg-gray-300:disabled{background-color:var(--color-gray-300)}.disabled\:bg-gray-400:disabled{background-color:var(--color-gray-400)}.disabled\:text-gray-300:disabled{color:var(--color-gray-300)}.disabled\:text-gray-400:disabled{color:var(--color-gray-400)}.disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.disabled\:no-underline:disabled{text-decoration-line:none}.disabled\:opacity-25:disabled{opacity:.25}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-75:disabled{opacity:.75}@media(hover:hover){.disabled\:hover\:bg-indigo-600:disabled:hover{background-color:var(--color-indigo-600)}.disabled\:hover\:bg-transparent:disabled:hover{background-color:#0000}}@media(min-width:40rem){.sm\:col-span-1{grid-column:span 1/span 1}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:col-span-4{grid-column:span 4/span 4}.sm\:col-span-6{grid-column:span 6/span 6}.sm\:col-span-12{grid-column:span 12/span 12}.sm\:col-span-full{grid-column:1/-1}.sm\:m-0{margin:0}.sm\:m-1{margin:var(--spacing)}.sm\:m-2{margin:calc(var(--spacing) * 2)}.sm\:m-3{margin:calc(var(--spacing) * 3)}.sm\:m-4{margin:calc(var(--spacing) * 4)}.sm\:m-auto{margin:auto}.sm\:mx-auto{margin-inline:auto}.sm\:my-auto{margin-block:auto}.sm\:mt-6{margin-top:calc(var(--spacing) * 6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:inline-flex{display:inline-flex}.sm\:w-1\/2{width:50%}.sm\:w-1\/3{width:33.3333%}.sm\:w-1\/4{width:25%}.sm\:w-2\/3{width:66.6667%}.sm\:w-3\/4{width:75%}.sm\:w-40{width:calc(var(--spacing) * 40)}.sm\:w-auto{width:auto}.sm\:w-full{width:100%}.sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:flex-col{flex-direction:column}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:items-start{align-items:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:justify-center{justify-content:center}.sm\:justify-end{justify-content:flex-end}.sm\:justify-start{justify-content:flex-start}.sm\:gap-0{gap:0}.sm\:gap-1{gap:var(--spacing)}.sm\:gap-2{gap:calc(var(--spacing) * 2)}.sm\:gap-3{gap:calc(var(--spacing) * 3)}.sm\:gap-4{gap:calc(var(--spacing) * 4)}.sm\:gap-5{gap:calc(var(--spacing) * 5)}.sm\:gap-6{gap:calc(var(--spacing) * 6)}.sm\:gap-8{gap:calc(var(--spacing) * 8)}:where(.sm\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.sm\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\: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(.sm\: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(.sm\: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(.sm\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.sm\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}.sm\:p-0{padding:0}.sm\:p-1{padding:var(--spacing)}.sm\:p-2{padding:calc(var(--spacing) * 2)}.sm\:p-3{padding:calc(var(--spacing) * 3)}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:p-5{padding:calc(var(--spacing) * 5)}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:p-8{padding:calc(var(--spacing) * 8)}.sm\:px-0{padding-inline:0}.sm\:px-1{padding-inline:var(--spacing)}.sm\:px-2{padding-inline:calc(var(--spacing) * 2)}.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}.sm\:px-5{padding-inline:calc(var(--spacing) * 5)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:px-8{padding-inline:calc(var(--spacing) * 8)}.sm\:py-0{padding-block:0}.sm\:py-1{padding-block:var(--spacing)}.sm\:py-2{padding-block:calc(var(--spacing) * 2)}.sm\:py-3{padding-block:calc(var(--spacing) * 3)}.sm\:py-4{padding-block:calc(var(--spacing) * 4)}.sm\:py-5{padding-block:calc(var(--spacing) * 5)}.sm\:py-6{padding-block:calc(var(--spacing) * 6)}.sm\:py-8{padding-block:calc(var(--spacing) * 8)}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.sm\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:48rem){.md\:absolute{position:absolute}.md\:fixed{position:fixed}.md\:relative{position:relative}.md\:static{position:static}.md\:sticky{position:sticky}.md\:inset-0{inset:0}.md\:top-0{top:0}.md\:right-0{right:0}.md\:bottom-0{bottom:0}.md\:left-0{left:0}.md\:order-1{order:1}.md\:order-2{order:2}.md\:order-3{order:3}.md\:order-first{order:-9999}.md\:order-last{order:9999}.md\:order-none{order:0}.md\:col-span-1{grid-column:span 1/span 1}.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-3{grid-column:span 3/span 3}.md\:col-span-4{grid-column:span 4/span 4}.md\:col-span-5{grid-column:span 5/span 5}.md\:col-span-6{grid-column:span 6/span 6}.md\:col-span-12{grid-column:span 12/span 12}.md\:col-span-full{grid-column:1/-1}.md\:row-span-1{grid-row:span 1/span 1}.md\:row-span-2{grid-row:span 2/span 2}.md\:row-span-3{grid-row:span 3/span 3}.md\:row-span-4{grid-row:span 4/span 4}.md\:row-span-full{grid-row:1/-1}.md\:m-0{margin:0}.md\:m-1{margin:var(--spacing)}.md\:m-2{margin:calc(var(--spacing) * 2)}.md\:m-3{margin:calc(var(--spacing) * 3)}.md\:m-4{margin:calc(var(--spacing) * 4)}.md\:m-auto{margin:auto}.md\:mx-auto{margin-inline:auto}.md\:my-auto{margin-block:auto}.md\:mt-0{margin-top:0}.md\:mt-1{margin-top:var(--spacing)}.md\:mt-2{margin-top:calc(var(--spacing) * 2)}.md\:mt-3{margin-top:calc(var(--spacing) * 3)}.md\:mt-4{margin-top:calc(var(--spacing) * 4)}.md\:mt-6{margin-top:calc(var(--spacing) * 6)}.md\:mt-8{margin-top:calc(var(--spacing) * 8)}.md\:mr-0{margin-right:0}.md\:mr-1{margin-right:var(--spacing)}.md\:mr-2{margin-right:calc(var(--spacing) * 2)}.md\:mr-3{margin-right:calc(var(--spacing) * 3)}.md\:mr-4{margin-right:calc(var(--spacing) * 4)}.md\:mr-6{margin-right:calc(var(--spacing) * 6)}.md\:mr-8{margin-right:calc(var(--spacing) * 8)}.md\:mr-auto{margin-right:auto}.md\:mb-0{margin-bottom:0}.md\:mb-1{margin-bottom:var(--spacing)}.md\:mb-2{margin-bottom:calc(var(--spacing) * 2)}.md\:mb-3{margin-bottom:calc(var(--spacing) * 3)}.md\:mb-4{margin-bottom:calc(var(--spacing) * 4)}.md\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.md\:mb-8{margin-bottom:calc(var(--spacing) * 8)}.md\:ml-0{margin-left:0}.md\:ml-1{margin-left:var(--spacing)}.md\:ml-2{margin-left:calc(var(--spacing) * 2)}.md\:ml-3{margin-left:calc(var(--spacing) * 3)}.md\:ml-4{margin-left:calc(var(--spacing) * 4)}.md\:ml-6{margin-left:calc(var(--spacing) * 6)}.md\:ml-8{margin-left:calc(var(--spacing) * 8)}.md\:ml-auto{margin-left:auto}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:inline{display:inline}.md\:inline-block{display:inline-block}.md\:inline-flex{display:inline-flex}.md\:h-20{height:calc(var(--spacing) * 20)}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.3333%}.md\:w-1\/4{width:25%}.md\:w-1\/5{width:20%}.md\:w-2\/3{width:66.6667%}.md\:w-2\/5{width:40%}.md\:w-3\/4{width:75%}.md\:w-3\/5{width:60%}.md\:w-4\/5{width:80%}.md\:w-20{width:calc(var(--spacing) * 20)}.md\:w-40{width:calc(var(--spacing) * 40)}.md\:w-48{width:calc(var(--spacing) * 48)}.md\:w-64{width:calc(var(--spacing) * 64)}.md\:w-72{width:calc(var(--spacing) * 72)}.md\:w-80{width:calc(var(--spacing) * 80)}.md\:w-auto{width:auto}.md\:w-full{width:100%}.md\:max-w-2xl{max-width:var(--container-2xl)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-4xl{max-width:var(--container-4xl)}.md\:max-w-5xl{max-width:var(--container-5xl)}.md\:max-w-6xl{max-width:var(--container-6xl)}.md\:max-w-7xl{max-width:var(--container-7xl)}.md\:max-w-full{max-width:100%}.md\:max-w-lg{max-width:var(--container-lg)}.md\:max-w-md{max-width:var(--container-md)}.md\:max-w-none{max-width:none}.md\:max-w-sm{max-width:var(--container-sm)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:max-w-xs{max-width:var(--container-xs)}.md\:flex-1{flex:1}.md\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.md\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.md\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.md\:grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.md\:flex-col{flex-direction:column}.md\:flex-row{flex-direction:row}.md\:items-baseline{align-items:baseline}.md\:items-center{align-items:center}.md\:items-end{align-items:flex-end}.md\:items-start{align-items:flex-start}.md\:items-stretch{align-items:stretch}.md\:justify-around{justify-content:space-around}.md\:justify-between{justify-content:space-between}.md\:justify-center{justify-content:center}.md\:justify-end{justify-content:flex-end}.md\:justify-start{justify-content:flex-start}.md\:gap-0{gap:0}.md\:gap-1{gap:var(--spacing)}.md\:gap-2{gap:calc(var(--spacing) * 2)}.md\:gap-3{gap:calc(var(--spacing) * 3)}.md\:gap-4{gap:calc(var(--spacing) * 4)}.md\:gap-5{gap:calc(var(--spacing) * 5)}.md\:gap-6{gap:calc(var(--spacing) * 6)}.md\:gap-8{gap:calc(var(--spacing) * 8)}.md\:gap-10{gap:calc(var(--spacing) * 10)}.md\:gap-12{gap:calc(var(--spacing) * 12)}:where(.md\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.md\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\: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(.md\: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(.md\: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(.md\: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(.md\: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(.md\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.md\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.md\:rounded-2xl{border-radius:var(--radius-2xl)}.md\:p-0{padding:0}.md\:p-1{padding:var(--spacing)}.md\:p-2{padding:calc(var(--spacing) * 2)}.md\:p-3{padding:calc(var(--spacing) * 3)}.md\:p-4{padding:calc(var(--spacing) * 4)}.md\:p-5{padding:calc(var(--spacing) * 5)}.md\:p-6{padding:calc(var(--spacing) * 6)}.md\:p-8{padding:calc(var(--spacing) * 8)}.md\:p-10{padding:calc(var(--spacing) * 10)}.md\:p-12{padding:calc(var(--spacing) * 12)}.md\:px-0{padding-inline:0}.md\:px-1{padding-inline:var(--spacing)}.md\:px-2{padding-inline:calc(var(--spacing) * 2)}.md\:px-3{padding-inline:calc(var(--spacing) * 3)}.md\:px-4{padding-inline:calc(var(--spacing) * 4)}.md\:px-5{padding-inline:calc(var(--spacing) * 5)}.md\:px-6{padding-inline:calc(var(--spacing) * 6)}.md\:px-8{padding-inline:calc(var(--spacing) * 8)}.md\:px-10{padding-inline:calc(var(--spacing) * 10)}.md\:px-12{padding-inline:calc(var(--spacing) * 12)}.md\:py-0{padding-block:0}.md\:py-1{padding-block:var(--spacing)}.md\:py-2{padding-block:calc(var(--spacing) * 2)}.md\:py-3{padding-block:calc(var(--spacing) * 3)}.md\:py-4{padding-block:calc(var(--spacing) * 4)}.md\:py-5{padding-block:calc(var(--spacing) * 5)}.md\:py-6{padding-block:calc(var(--spacing) * 6)}.md\:py-8{padding-block:calc(var(--spacing) * 8)}.md\:py-10{padding-block:calc(var(--spacing) * 10)}.md\:py-12{padding-block:calc(var(--spacing) * 12)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.md\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:64rem){.lg\:absolute{position:absolute}.lg\:fixed{position:fixed}.lg\:relative{position:relative}.lg\:static{position:static}.lg\:sticky{position:sticky}.lg\:inset-0{inset:0}.lg\:top-0{top:0}.lg\:right-0{right:0}.lg\:bottom-0{bottom:0}.lg\:left-0{left:0}.lg\:order-1{order:1}.lg\:order-2{order:2}.lg\:order-3{order:3}.lg\:order-4{order:4}.lg\:order-first{order:-9999}.lg\:order-last{order:9999}.lg\:order-none{order:0}.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-4{grid-column:span 4/span 4}.lg\:col-span-5{grid-column:span 5/span 5}.lg\:col-span-6{grid-column:span 6/span 6}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:col-span-8{grid-column:span 8/span 8}.lg\:col-span-12{grid-column:span 12/span 12}.lg\:col-span-full{grid-column:1/-1}.lg\:row-span-1{grid-row:span 1/span 1}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-span-3{grid-row:span 3/span 3}.lg\:row-span-4{grid-row:span 4/span 4}.lg\:row-span-full{grid-row:1/-1}.lg\:m-0{margin:0}.lg\:m-1{margin:var(--spacing)}.lg\:m-2{margin:calc(var(--spacing) * 2)}.lg\:m-3{margin:calc(var(--spacing) * 3)}.lg\:m-4{margin:calc(var(--spacing) * 4)}.lg\:m-auto{margin:auto}.lg\:mx-auto{margin-inline:auto}.lg\:my-auto{margin-block:auto}.lg\:mt-0{margin-top:0}.lg\:mt-1{margin-top:var(--spacing)}.lg\:mt-2{margin-top:calc(var(--spacing) * 2)}.lg\:mt-3{margin-top:calc(var(--spacing) * 3)}.lg\:mt-4{margin-top:calc(var(--spacing) * 4)}.lg\:mt-6{margin-top:calc(var(--spacing) * 6)}.lg\:mt-8{margin-top:calc(var(--spacing) * 8)}.lg\:mt-10{margin-top:calc(var(--spacing) * 10)}.lg\:mt-12{margin-top:calc(var(--spacing) * 12)}.lg\:mr-0{margin-right:0}.lg\:mr-1{margin-right:var(--spacing)}.lg\:mr-2{margin-right:calc(var(--spacing) * 2)}.lg\:mr-3{margin-right:calc(var(--spacing) * 3)}.lg\:mr-4{margin-right:calc(var(--spacing) * 4)}.lg\:mr-6{margin-right:calc(var(--spacing) * 6)}.lg\:mr-8{margin-right:calc(var(--spacing) * 8)}.lg\:mr-auto{margin-right:auto}.lg\:mb-0{margin-bottom:0}.lg\:mb-1{margin-bottom:var(--spacing)}.lg\:mb-2{margin-bottom:calc(var(--spacing) * 2)}.lg\:mb-3{margin-bottom:calc(var(--spacing) * 3)}.lg\:mb-4{margin-bottom:calc(var(--spacing) * 4)}.lg\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.lg\:mb-8{margin-bottom:calc(var(--spacing) * 8)}.lg\:mb-10{margin-bottom:calc(var(--spacing) * 10)}.lg\:mb-12{margin-bottom:calc(var(--spacing) * 12)}.lg\:ml-0{margin-left:0}.lg\:ml-1{margin-left:var(--spacing)}.lg\:ml-2{margin-left:calc(var(--spacing) * 2)}.lg\:ml-3{margin-left:calc(var(--spacing) * 3)}.lg\:ml-4{margin-left:calc(var(--spacing) * 4)}.lg\:ml-6{margin-left:calc(var(--spacing) * 6)}.lg\:ml-8{margin-left:calc(var(--spacing) * 8)}.lg\:ml-auto{margin-left:auto}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:inline{display:inline}.lg\:inline-block{display:inline-block}.lg\:inline-flex{display:inline-flex}.lg\:w-1\/2{width:50%}.lg\:w-1\/3{width:33.3333%}.lg\:w-1\/4{width:25%}.lg\:w-1\/5{width:20%}.lg\:w-1\/6{width:16.6667%}.lg\:w-2\/3{width:66.6667%}.lg\:w-2\/5{width:40%}.lg\:w-3\/4{width:75%}.lg\:w-3\/5{width:60%}.lg\:w-4\/5{width:80%}.lg\:w-5\/6{width:83.3333%}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:max-w-2xl{max-width:var(--container-2xl)}.lg\:max-w-3xl{max-width:var(--container-3xl)}.lg\:max-w-4xl{max-width:var(--container-4xl)}.lg\:max-w-5xl{max-width:var(--container-5xl)}.lg\:max-w-6xl{max-width:var(--container-6xl)}.lg\:max-w-7xl{max-width:var(--container-7xl)}.lg\:max-w-full{max-width:100%}.lg\:max-w-lg{max-width:var(--container-lg)}.lg\:max-w-md{max-width:var(--container-md)}.lg\:max-w-none{max-width:none}.lg\:max-w-sm{max-width:var(--container-sm)}.lg\:max-w-xl{max-width:var(--container-xl)}.lg\:max-w-xs{max-width:var(--container-xs)}.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.lg\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.lg\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.lg\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.lg\:grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.lg\:grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.lg\:flex-col{flex-direction:column}.lg\:flex-row{flex-direction:row}.lg\:items-baseline{align-items:baseline}.lg\:items-center{align-items:center}.lg\:items-end{align-items:flex-end}.lg\:items-start{align-items:flex-start}.lg\:items-stretch{align-items:stretch}.lg\:justify-around{justify-content:space-around}.lg\:justify-between{justify-content:space-between}.lg\:justify-center{justify-content:center}.lg\:justify-end{justify-content:flex-end}.lg\:justify-evenly{justify-content:space-evenly}.lg\:justify-start{justify-content:flex-start}.lg\:gap-0{gap:0}.lg\:gap-1{gap:var(--spacing)}.lg\:gap-2{gap:calc(var(--spacing) * 2)}.lg\:gap-3{gap:calc(var(--spacing) * 3)}.lg\:gap-4{gap:calc(var(--spacing) * 4)}.lg\:gap-5{gap:calc(var(--spacing) * 5)}.lg\:gap-6{gap:calc(var(--spacing) * 6)}.lg\:gap-8{gap:calc(var(--spacing) * 8)}.lg\:gap-10{gap:calc(var(--spacing) * 10)}.lg\:gap-12{gap:calc(var(--spacing) * 12)}.lg\:gap-16{gap:calc(var(--spacing) * 16)}:where(.lg\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.lg\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\: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(.lg\: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(.lg\: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(.lg\: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(.lg\: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(.lg\:space-y-10>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 10) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.lg\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-10>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 10) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-x-reverse)))}.lg\:p-0{padding:0}.lg\:p-1{padding:var(--spacing)}.lg\:p-2{padding:calc(var(--spacing) * 2)}.lg\:p-3{padding:calc(var(--spacing) * 3)}.lg\:p-4{padding:calc(var(--spacing) * 4)}.lg\:p-5{padding:calc(var(--spacing) * 5)}.lg\:p-6{padding:calc(var(--spacing) * 6)}.lg\:p-8{padding:calc(var(--spacing) * 8)}.lg\:p-10{padding:calc(var(--spacing) * 10)}.lg\:p-12{padding:calc(var(--spacing) * 12)}.lg\:p-16{padding:calc(var(--spacing) * 16)}.lg\:px-0{padding-inline:0}.lg\:px-1{padding-inline:var(--spacing)}.lg\:px-2{padding-inline:calc(var(--spacing) * 2)}.lg\:px-3{padding-inline:calc(var(--spacing) * 3)}.lg\:px-4{padding-inline:calc(var(--spacing) * 4)}.lg\:px-5{padding-inline:calc(var(--spacing) * 5)}.lg\:px-6{padding-inline:calc(var(--spacing) * 6)}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}.lg\:px-10{padding-inline:calc(var(--spacing) * 10)}.lg\:px-12{padding-inline:calc(var(--spacing) * 12)}.lg\:px-16{padding-inline:calc(var(--spacing) * 16)}.lg\:py-0{padding-block:0}.lg\:py-1{padding-block:var(--spacing)}.lg\:py-2{padding-block:calc(var(--spacing) * 2)}.lg\:py-3{padding-block:calc(var(--spacing) * 3)}.lg\:py-4{padding-block:calc(var(--spacing) * 4)}.lg\:py-5{padding-block:calc(var(--spacing) * 5)}.lg\:py-6{padding-block:calc(var(--spacing) * 6)}.lg\:py-8{padding-block:calc(var(--spacing) * 8)}.lg\:py-10{padding-block:calc(var(--spacing) * 10)}.lg\:py-12{padding-block:calc(var(--spacing) * 12)}.lg\:py-16{padding-block:calc(var(--spacing) * 16)}.lg\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.lg\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.lg\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.lg\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.lg\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.lg\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.lg\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.lg\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.lg\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:80rem){.xl\:col-span-1{grid-column:span 1/span 1}.xl\:col-span-2{grid-column:span 2/span 2}.xl\:col-span-3{grid-column:span 3/span 3}.xl\:col-span-4{grid-column:span 4/span 4}.xl\:col-span-5{grid-column:span 5/span 5}.xl\:col-span-6{grid-column:span 6/span 6}.xl\:col-span-12{grid-column:span 12/span 12}.xl\:col-span-full{grid-column:1/-1}.xl\:row-span-1{grid-row:span 1/span 1}.xl\:row-span-2{grid-row:span 2/span 2}.xl\:row-span-3{grid-row:span 3/span 3}.xl\:row-span-full{grid-row:1/-1}.xl\:m-0{margin:0}.xl\:m-auto{margin:auto}.xl\:mx-auto{margin-inline:auto}.xl\:my-auto{margin-block:auto}.xl\:block{display:block}.xl\:flex{display:flex}.xl\:hidden{display:none}.xl\:inline{display:inline}.xl\:inline-block{display:inline-block}.xl\:inline-flex{display:inline-flex}.xl\:w-1\/2{width:50%}.xl\:w-1\/3{width:33.3333%}.xl\:w-1\/4{width:25%}.xl\:w-1\/5{width:20%}.xl\:w-2\/3{width:66.6667%}.xl\:w-2\/5{width:40%}.xl\:w-3\/4{width:75%}.xl\:w-3\/5{width:60%}.xl\:w-4\/5{width:80%}.xl\:w-auto{width:auto}.xl\:w-full{width:100%}.xl\:max-w-2xl{max-width:var(--container-2xl)}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-4xl{max-width:var(--container-4xl)}.xl\:max-w-5xl{max-width:var(--container-5xl)}.xl\:max-w-6xl{max-width:var(--container-6xl)}.xl\:max-w-7xl{max-width:var(--container-7xl)}.xl\:max-w-full{max-width:100%}.xl\:max-w-lg{max-width:var(--container-lg)}.xl\:max-w-md{max-width:var(--container-md)}.xl\:max-w-none{max-width:none}.xl\:max-w-sm{max-width:var(--container-sm)}.xl\:max-w-xl{max-width:var(--container-xl)}.xl\:max-w-xs{max-width:var(--container-xs)}.xl\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.xl\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.xl\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.xl\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.xl\:flex-col{flex-direction:column}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:items-end{align-items:flex-end}.xl\:items-start{align-items:flex-start}.xl\:justify-between{justify-content:space-between}.xl\:justify-center{justify-content:center}.xl\:justify-end{justify-content:flex-end}.xl\:justify-start{justify-content:flex-start}.xl\:gap-0{gap:0}.xl\:gap-1{gap:var(--spacing)}.xl\:gap-2{gap:calc(var(--spacing) * 2)}.xl\:gap-3{gap:calc(var(--spacing) * 3)}.xl\:gap-4{gap:calc(var(--spacing) * 4)}.xl\:gap-5{gap:calc(var(--spacing) * 5)}.xl\:gap-6{gap:calc(var(--spacing) * 6)}.xl\:gap-8{gap:calc(var(--spacing) * 8)}.xl\:gap-10{gap:calc(var(--spacing) * 10)}.xl\:gap-12{gap:calc(var(--spacing) * 12)}.xl\:gap-16{gap:calc(var(--spacing) * 16)}:where(.xl\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.xl\:space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\: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(.xl\: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(.xl\: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(.xl\: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(.xl\: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(.xl\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline:0}:where(.xl\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.xl\:p-0{padding:0}.xl\:p-1{padding:var(--spacing)}.xl\:p-2{padding:calc(var(--spacing) * 2)}.xl\:p-3{padding:calc(var(--spacing) * 3)}.xl\:p-4{padding:calc(var(--spacing) * 4)}.xl\:p-5{padding:calc(var(--spacing) * 5)}.xl\:p-6{padding:calc(var(--spacing) * 6)}.xl\:p-8{padding:calc(var(--spacing) * 8)}.xl\:p-10{padding:calc(var(--spacing) * 10)}.xl\:p-12{padding:calc(var(--spacing) * 12)}.xl\:p-16{padding:calc(var(--spacing) * 16)}.xl\:px-0{padding-inline:0}.xl\:px-1{padding-inline:var(--spacing)}.xl\:px-2{padding-inline:calc(var(--spacing) * 2)}.xl\:px-3{padding-inline:calc(var(--spacing) * 3)}.xl\:px-4{padding-inline:calc(var(--spacing) * 4)}.xl\:px-5{padding-inline:calc(var(--spacing) * 5)}.xl\:px-6{padding-inline:calc(var(--spacing) * 6)}.xl\:px-8{padding-inline:calc(var(--spacing) * 8)}.xl\:px-10{padding-inline:calc(var(--spacing) * 10)}.xl\:px-12{padding-inline:calc(var(--spacing) * 12)}.xl\:px-16{padding-inline:calc(var(--spacing) * 16)}.xl\:py-0{padding-block:0}.xl\:py-1{padding-block:var(--spacing)}.xl\:py-2{padding-block:calc(var(--spacing) * 2)}.xl\:py-3{padding-block:calc(var(--spacing) * 3)}.xl\:py-4{padding-block:calc(var(--spacing) * 4)}.xl\:py-5{padding-block:calc(var(--spacing) * 5)}.xl\:py-6{padding-block:calc(var(--spacing) * 6)}.xl\:py-8{padding-block:calc(var(--spacing) * 8)}.xl\:py-10{padding-block:calc(var(--spacing) * 10)}.xl\:py-12{padding-block:calc(var(--spacing) * 12)}.xl\:py-16{padding-block:calc(var(--spacing) * 16)}.xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.xl\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.xl\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.xl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.xl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.xl\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.xl\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.xl\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:96rem){.\32xl\:col-span-1{grid-column:span 1/span 1}.\32xl\:col-span-2{grid-column:span 2/span 2}.\32xl\:col-span-3{grid-column:span 3/span 3}.\32xl\:col-span-4{grid-column:span 4/span 4}.\32xl\:col-span-6{grid-column:span 6/span 6}.\32xl\:col-span-12{grid-column:span 12/span 12}.\32xl\:block{display:block}.\32xl\:flex{display:flex}.\32xl\:hidden{display:none}.\32xl\:w-1\/2{width:50%}.\32xl\:w-1\/3{width:33.3333%}.\32xl\:w-1\/4{width:25%}.\32xl\:w-2\/3{width:66.6667%}.\32xl\:w-3\/4{width:75%}.\32xl\:w-auto{width:auto}.\32xl\:w-full{width:100%}.\32xl\:max-w-2xl{max-width:var(--container-2xl)}.\32xl\:max-w-3xl{max-width:var(--container-3xl)}.\32xl\:max-w-4xl{max-width:var(--container-4xl)}.\32xl\:max-w-5xl{max-width:var(--container-5xl)}.\32xl\:max-w-6xl{max-width:var(--container-6xl)}.\32xl\:max-w-7xl{max-width:var(--container-7xl)}.\32xl\:max-w-full{max-width:100%}.\32xl\:max-w-lg{max-width:var(--container-lg)}.\32xl\:max-w-md{max-width:var(--container-md)}.\32xl\:max-w-sm{max-width:var(--container-sm)}.\32xl\:max-w-xl{max-width:var(--container-xl)}.\32xl\:max-w-xs{max-width:var(--container-xs)}.\32xl\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.\32xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.\32xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.\32xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.\32xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.\32xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.\32xl\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.\32xl\:gap-0{gap:0}.\32xl\:gap-4{gap:calc(var(--spacing) * 4)}.\32xl\:gap-6{gap:calc(var(--spacing) * 6)}.\32xl\:gap-8{gap:calc(var(--spacing) * 8)}.\32xl\:gap-12{gap:calc(var(--spacing) * 12)}.\32xl\:gap-16{gap:calc(var(--spacing) * 16)}.\32xl\:p-0{padding:0}.\32xl\:p-4{padding:calc(var(--spacing) * 4)}.\32xl\:p-6{padding:calc(var(--spacing) * 6)}.\32xl\:p-8{padding:calc(var(--spacing) * 8)}.\32xl\:p-12{padding:calc(var(--spacing) * 12)}.\32xl\:p-16{padding:calc(var(--spacing) * 16)}.\32xl\:px-0{padding-inline:0}.\32xl\:px-4{padding-inline:calc(var(--spacing) * 4)}.\32xl\:px-6{padding-inline:calc(var(--spacing) * 6)}.\32xl\:px-8{padding-inline:calc(var(--spacing) * 8)}.\32xl\:px-12{padding-inline:calc(var(--spacing) * 12)}.\32xl\:px-16{padding-inline:calc(var(--spacing) * 16)}.\32xl\:py-0{padding-block:0}.\32xl\:py-4{padding-block:calc(var(--spacing) * 4)}.\32xl\:py-6{padding-block:calc(var(--spacing) * 6)}.\32xl\:py-8{padding-block:calc(var(--spacing) * 8)}.\32xl\:py-12{padding-block:calc(var(--spacing) * 12)}.\32xl\:py-16{padding-block:calc(var(--spacing) * 16)}.\32xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.\32xl\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.\32xl\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.\32xl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.\32xl\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.\32xl\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.\32xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\32xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.\32xl\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.dark .dark\:block{display:block}.dark .dark\:flex{display:flex}.dark .dark\:flex-1{flex:1}.dark .dark\:flex-auto{flex:auto}.dark .dark\:flex-none{flex:none}.dark .dark\:flex-col{flex-direction:column}.dark .dark\:flex-row{flex-direction:row}.dark .dark\:flex-wrap{flex-wrap:wrap}.dark .dark\:items-center{align-items:center}.dark .dark\:items-end{align-items:flex-end}.dark .dark\:items-start{align-items:flex-start}.dark .dark\:items-stretch{align-items:stretch}.dark .dark\:justify-around{justify-content:space-around}.dark .dark\:justify-between{justify-content:space-between}.dark .dark\:justify-center{justify-content:center}.dark .dark\:justify-end{justify-content:flex-end}.dark .dark\:justify-start{justify-content:flex-start}:where(.dark .dark\:divide-gray-50>:not(:last-child)){border-color:var(--color-gray-50)}:where(.dark .dark\:divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.dark .dark\:divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.dark .dark\:divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}:where(.dark .dark\:divide-gray-400>:not(:last-child)){border-color:var(--color-gray-400)}:where(.dark .dark\:divide-gray-500>:not(:last-child)){border-color:var(--color-gray-500)}:where(.dark .dark\:divide-gray-600>:not(:last-child)){border-color:var(--color-gray-600)}:where(.dark .dark\:divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark .dark\:divide-gray-800>:not(:last-child)){border-color:var(--color-gray-800)}:where(.dark .dark\:divide-gray-900>:not(:last-child)){border-color:var(--color-gray-900)}:where(.dark .dark\:divide-purple-800>:not(:last-child)){border-color:var(--color-purple-800)}:where(.dark .dark\:divide-slate-700>:not(:last-child)){border-color:var(--color-slate-700)}.dark .dark\:self-center{align-self:center}.dark .dark\:self-end{align-self:flex-end}.dark .dark\:self-start{align-self:flex-start}.dark .dark\:self-stretch{align-self:stretch}.dark .dark\:overflow-auto{overflow:auto}.dark .dark\:overflow-hidden{overflow:hidden}.dark .dark\:overflow-visible{overflow:visible}.dark .dark\:overflow-x-auto{overflow-x:auto}.dark .dark\:overflow-y-auto{overflow-y:auto}.dark .dark\:rounded{border-radius:.25rem}.dark .dark\:rounded-full{border-radius:3.40282e38px}.dark .dark\:rounded-lg{border-radius:var(--radius-lg)}.dark .dark\:rounded-none{border-radius:0}.dark .dark\:rounded-sm{border-radius:var(--radius-sm)}.dark .dark\:rounded-xl{border-radius:var(--radius-xl)}.dark .dark\:border{border-style:var(--tw-border-style);border-width:1px}.dark .dark\:border-0{border-style:var(--tw-border-style);border-width:0}.dark .dark\:border-dashed{--tw-border-style:dashed;border-style:dashed}.dark .dark\:border-dotted{--tw-border-style:dotted;border-style:dotted}.dark .dark\:border-solid{--tw-border-style:solid;border-style:solid}.dark .dark\:border-amber-50{border-color:var(--color-amber-50)}.dark .dark\:border-amber-100{border-color:var(--color-amber-100)}.dark .dark\:border-amber-200{border-color:var(--color-amber-200)}.dark .dark\:border-amber-300{border-color:var(--color-amber-300)}.dark .dark\:border-amber-400{border-color:var(--color-amber-400)}.dark .dark\:border-amber-500{border-color:var(--color-amber-500)}.dark .dark\:border-amber-600{border-color:var(--color-amber-600)}.dark .dark\:border-amber-700{border-color:var(--color-amber-700)}.dark .dark\:border-amber-800{border-color:var(--color-amber-800)}.dark .dark\:border-amber-900{border-color:var(--color-amber-900)}.dark .dark\:border-black{border-color:var(--color-black)}.dark .dark\:border-blue-50{border-color:var(--color-blue-50)}.dark .dark\:border-blue-100{border-color:var(--color-blue-100)}.dark .dark\:border-blue-200{border-color:var(--color-blue-200)}.dark .dark\:border-blue-300{border-color:var(--color-blue-300)}.dark .dark\:border-blue-400{border-color:var(--color-blue-400)}.dark .dark\:border-blue-500{border-color:var(--color-blue-500)}.dark .dark\:border-blue-600{border-color:var(--color-blue-600)}.dark .dark\:border-blue-700{border-color:var(--color-blue-700)}.dark .dark\:border-blue-800{border-color:var(--color-blue-800)}.dark .dark\:border-blue-900{border-color:var(--color-blue-900)}.dark .dark\:border-gray-50{border-color:var(--color-gray-50)}.dark .dark\:border-gray-100{border-color:var(--color-gray-100)}.dark .dark\:border-gray-200{border-color:var(--color-gray-200)}.dark .dark\:border-gray-300{border-color:var(--color-gray-300)}.dark .dark\:border-gray-400{border-color:var(--color-gray-400)}.dark .dark\:border-gray-500{border-color:var(--color-gray-500)}.dark .dark\:border-gray-600{border-color:var(--color-gray-600)}.dark .dark\:border-gray-700{border-color:var(--color-gray-700)}.dark .dark\:border-gray-800{border-color:var(--color-gray-800)}.dark .dark\:border-gray-900{border-color:var(--color-gray-900)}.dark .dark\:border-gray-950{border-color:var(--color-gray-950)}.dark .dark\:border-green-50{border-color:var(--color-green-50)}.dark .dark\:border-green-100{border-color:var(--color-green-100)}.dark .dark\:border-green-200{border-color:var(--color-green-200)}.dark .dark\:border-green-300{border-color:var(--color-green-300)}.dark .dark\:border-green-400{border-color:var(--color-green-400)}.dark .dark\:border-green-500{border-color:var(--color-green-500)}.dark .dark\:border-green-600{border-color:var(--color-green-600)}.dark .dark\:border-green-700{border-color:var(--color-green-700)}.dark .dark\:border-green-800{border-color:var(--color-green-800)}.dark .dark\:border-green-900{border-color:var(--color-green-900)}.dark .dark\:border-indigo-400{border-color:var(--color-indigo-400)}.dark .dark\:border-orange-50{border-color:var(--color-orange-50)}.dark .dark\:border-orange-100{border-color:var(--color-orange-100)}.dark .dark\:border-orange-200{border-color:var(--color-orange-200)}.dark .dark\:border-orange-300{border-color:var(--color-orange-300)}.dark .dark\:border-orange-400{border-color:var(--color-orange-400)}.dark .dark\:border-orange-500{border-color:var(--color-orange-500)}.dark .dark\:border-orange-600{border-color:var(--color-orange-600)}.dark .dark\:border-orange-700{border-color:var(--color-orange-700)}.dark .dark\:border-orange-800{border-color:var(--color-orange-800)}.dark .dark\:border-orange-900{border-color:var(--color-orange-900)}.dark .dark\:border-pink-50{border-color:var(--color-pink-50)}.dark .dark\:border-pink-100{border-color:var(--color-pink-100)}.dark .dark\:border-pink-200{border-color:var(--color-pink-200)}.dark .dark\:border-pink-300{border-color:var(--color-pink-300)}.dark .dark\:border-pink-400{border-color:var(--color-pink-400)}.dark .dark\:border-pink-500{border-color:var(--color-pink-500)}.dark .dark\:border-pink-600{border-color:var(--color-pink-600)}.dark .dark\:border-pink-700{border-color:var(--color-pink-700)}.dark .dark\:border-pink-800{border-color:var(--color-pink-800)}.dark .dark\:border-pink-900{border-color:var(--color-pink-900)}.dark .dark\:border-purple-50{border-color:var(--color-purple-50)}.dark .dark\:border-purple-100{border-color:var(--color-purple-100)}.dark .dark\:border-purple-200{border-color:var(--color-purple-200)}.dark .dark\:border-purple-300{border-color:var(--color-purple-300)}.dark .dark\:border-purple-400{border-color:var(--color-purple-400)}.dark .dark\:border-purple-500{border-color:var(--color-purple-500)}.dark .dark\:border-purple-600{border-color:var(--color-purple-600)}.dark .dark\:border-purple-700{border-color:var(--color-purple-700)}.dark .dark\:border-purple-800{border-color:var(--color-purple-800)}.dark .dark\:border-purple-900{border-color:var(--color-purple-900)}.dark .dark\:border-red-50{border-color:var(--color-red-50)}.dark .dark\:border-red-100{border-color:var(--color-red-100)}.dark .dark\:border-red-200{border-color:var(--color-red-200)}.dark .dark\:border-red-300{border-color:var(--color-red-300)}.dark .dark\:border-red-400{border-color:var(--color-red-400)}.dark .dark\:border-red-500{border-color:var(--color-red-500)}.dark .dark\:border-red-600{border-color:var(--color-red-600)}.dark .dark\:border-red-700{border-color:var(--color-red-700)}.dark .dark\:border-red-800{border-color:var(--color-red-800)}.dark .dark\:border-red-900{border-color:var(--color-red-900)}.dark .dark\:border-slate-50{border-color:var(--color-slate-50)}.dark .dark\:border-slate-100{border-color:var(--color-slate-100)}.dark .dark\:border-slate-200{border-color:var(--color-slate-200)}.dark .dark\:border-slate-300{border-color:var(--color-slate-300)}.dark .dark\:border-slate-400{border-color:var(--color-slate-400)}.dark .dark\:border-slate-500{border-color:var(--color-slate-500)}.dark .dark\:border-slate-600{border-color:var(--color-slate-600)}.dark .dark\:border-slate-700{border-color:var(--color-slate-700)}.dark .dark\:border-slate-800{border-color:var(--color-slate-800)}.dark .dark\:border-slate-900{border-color:var(--color-slate-900)}.dark .dark\:border-slate-950{border-color:var(--color-slate-950)}.dark .dark\:border-transparent{border-color:#0000}.dark .dark\:border-white{border-color:var(--color-white)}.dark .dark\:border-yellow-50{border-color:var(--color-yellow-50)}.dark .dark\:border-yellow-100{border-color:var(--color-yellow-100)}.dark .dark\:border-yellow-200{border-color:var(--color-yellow-200)}.dark .dark\:border-yellow-300{border-color:var(--color-yellow-300)}.dark .dark\:border-yellow-400{border-color:var(--color-yellow-400)}.dark .dark\:border-yellow-500{border-color:var(--color-yellow-500)}.dark .dark\:border-yellow-600{border-color:var(--color-yellow-600)}.dark .dark\:border-yellow-700{border-color:var(--color-yellow-700)}.dark .dark\:border-yellow-800{border-color:var(--color-yellow-800)}.dark .dark\:border-yellow-900{border-color:var(--color-yellow-900)}.dark .dark\:bg-amber-50{background-color:var(--color-amber-50)}.dark .dark\:bg-amber-100{background-color:var(--color-amber-100)}.dark .dark\:bg-amber-200{background-color:var(--color-amber-200)}.dark .dark\:bg-amber-300{background-color:var(--color-amber-300)}.dark .dark\:bg-amber-400{background-color:var(--color-amber-400)}.dark .dark\:bg-amber-500{background-color:var(--color-amber-500)}.dark .dark\:bg-amber-600{background-color:var(--color-amber-600)}.dark .dark\:bg-amber-700{background-color:var(--color-amber-700)}.dark .dark\:bg-amber-800{background-color:var(--color-amber-800)}.dark .dark\:bg-amber-900{background-color:var(--color-amber-900)}.dark .dark\:bg-amber-900\/20{background-color:#7b330633}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/20{background-color:color-mix(in oklab,var(--color-amber-900) 20%,transparent)}}.dark .dark\:bg-amber-900\/30{background-color:#7b33064d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/30{background-color:color-mix(in oklab,var(--color-amber-900) 30%,transparent)}}.dark .dark\:bg-amber-900\/40{background-color:#7b330666}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/40{background-color:color-mix(in oklab,var(--color-amber-900) 40%,transparent)}}.dark .dark\:bg-amber-950{background-color:var(--color-amber-950)}.dark .dark\:bg-black{background-color:var(--color-black)}.dark .dark\:bg-blue-50{background-color:var(--color-blue-50)}.dark .dark\:bg-blue-100{background-color:var(--color-blue-100)}.dark .dark\:bg-blue-200{background-color:var(--color-blue-200)}.dark .dark\:bg-blue-300{background-color:var(--color-blue-300)}.dark .dark\:bg-blue-400{background-color:var(--color-blue-400)}.dark .dark\:bg-blue-500{background-color:var(--color-blue-500)}.dark .dark\:bg-blue-600{background-color:var(--color-blue-600)}.dark .dark\:bg-blue-700{background-color:var(--color-blue-700)}.dark .dark\:bg-blue-800{background-color:var(--color-blue-800)}.dark .dark\:bg-blue-900{background-color:var(--color-blue-900)}.dark .dark\:bg-blue-900\/20{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/20{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .dark\:bg-blue-900\/30{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/30{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .dark\:bg-blue-900\/40{background-color:#1c398e66}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/40{background-color:color-mix(in oklab,var(--color-blue-900) 40%,transparent)}}.dark .dark\:bg-blue-900\/50{background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/50{background-color:color-mix(in oklab,var(--color-blue-900) 50%,transparent)}}.dark .dark\:bg-blue-950{background-color:var(--color-blue-950)}.dark .dark\:bg-cyan-50{background-color:var(--color-cyan-50)}.dark .dark\:bg-cyan-100{background-color:var(--color-cyan-100)}.dark .dark\:bg-cyan-200{background-color:var(--color-cyan-200)}.dark .dark\:bg-cyan-300{background-color:var(--color-cyan-300)}.dark .dark\:bg-cyan-400{background-color:var(--color-cyan-400)}.dark .dark\:bg-cyan-500{background-color:var(--color-cyan-500)}.dark .dark\:bg-cyan-600{background-color:var(--color-cyan-600)}.dark .dark\:bg-cyan-700{background-color:var(--color-cyan-700)}.dark .dark\:bg-cyan-800{background-color:var(--color-cyan-800)}.dark .dark\:bg-cyan-900{background-color:var(--color-cyan-900)}.dark .dark\:bg-cyan-900\/30{background-color:#104e644d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-cyan-900\/30{background-color:color-mix(in oklab,var(--color-cyan-900) 30%,transparent)}}.dark .dark\:bg-cyan-950{background-color:var(--color-cyan-950)}.dark .dark\:bg-emerald-50{background-color:var(--color-emerald-50)}.dark .dark\:bg-emerald-100{background-color:var(--color-emerald-100)}.dark .dark\:bg-emerald-200{background-color:var(--color-emerald-200)}.dark .dark\:bg-emerald-300{background-color:var(--color-emerald-300)}.dark .dark\:bg-emerald-400{background-color:var(--color-emerald-400)}.dark .dark\:bg-emerald-500{background-color:var(--color-emerald-500)}.dark .dark\:bg-emerald-600{background-color:var(--color-emerald-600)}.dark .dark\:bg-emerald-700{background-color:var(--color-emerald-700)}.dark .dark\:bg-emerald-800{background-color:var(--color-emerald-800)}.dark .dark\:bg-emerald-900{background-color:var(--color-emerald-900)}.dark .dark\:bg-emerald-950{background-color:var(--color-emerald-950)}.dark .dark\:bg-gray-50{background-color:var(--color-gray-50)}.dark .dark\:bg-gray-100{background-color:var(--color-gray-100)}.dark .dark\:bg-gray-200{background-color:var(--color-gray-200)}.dark .dark\:bg-gray-300{background-color:var(--color-gray-300)}.dark .dark\:bg-gray-400{background-color:var(--color-gray-400)}.dark .dark\:bg-gray-500{background-color:var(--color-gray-500)}.dark .dark\:bg-gray-600{background-color:var(--color-gray-600)}.dark .dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark .dark\:bg-gray-700\/30{background-color:#3641534d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-700\/30{background-color:color-mix(in oklab,var(--color-gray-700) 30%,transparent)}}.dark .dark\:bg-gray-700\/50{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-700\/50{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.dark .dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark .dark\:bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.dark .dark\:bg-gray-800\/90{background-color:#1e2939e6}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-800\/90{background-color:color-mix(in oklab,var(--color-gray-800) 90%,transparent)}}.dark .dark\:bg-gray-900{background-color:var(--color-gray-900)}.dark .dark\:bg-gray-900\/50{background-color:#10182880}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-900\/50{background-color:color-mix(in oklab,var(--color-gray-900) 50%,transparent)}}.dark .dark\:bg-gray-950{background-color:var(--color-gray-950)}.dark .dark\:bg-green-50{background-color:var(--color-green-50)}.dark .dark\:bg-green-100{background-color:var(--color-green-100)}.dark .dark\:bg-green-200{background-color:var(--color-green-200)}.dark .dark\:bg-green-300{background-color:var(--color-green-300)}.dark .dark\:bg-green-400{background-color:var(--color-green-400)}.dark .dark\:bg-green-500{background-color:var(--color-green-500)}.dark .dark\:bg-green-600{background-color:var(--color-green-600)}.dark .dark\:bg-green-700{background-color:var(--color-green-700)}.dark .dark\:bg-green-800{background-color:var(--color-green-800)}.dark .dark\:bg-green-900{background-color:var(--color-green-900)}.dark .dark\:bg-green-900\/20{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-green-900\/20{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .dark\:bg-green-900\/30{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-green-900\/30{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .dark\:bg-green-950{background-color:var(--color-green-950)}.dark .dark\:bg-indigo-50{background-color:var(--color-indigo-50)}.dark .dark\:bg-indigo-100{background-color:var(--color-indigo-100)}.dark .dark\:bg-indigo-200{background-color:var(--color-indigo-200)}.dark .dark\:bg-indigo-300{background-color:var(--color-indigo-300)}.dark .dark\:bg-indigo-400{background-color:var(--color-indigo-400)}.dark .dark\:bg-indigo-500{background-color:var(--color-indigo-500)}.dark .dark\:bg-indigo-600{background-color:var(--color-indigo-600)}.dark .dark\:bg-indigo-700{background-color:var(--color-indigo-700)}.dark .dark\:bg-indigo-800{background-color:var(--color-indigo-800)}.dark .dark\:bg-indigo-900{background-color:var(--color-indigo-900)}.dark .dark\:bg-indigo-900\/30{background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-indigo-900\/30{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .dark\:bg-indigo-950{background-color:var(--color-indigo-950)}.dark .dark\:bg-orange-50{background-color:var(--color-orange-50)}.dark .dark\:bg-orange-100{background-color:var(--color-orange-100)}.dark .dark\:bg-orange-200{background-color:var(--color-orange-200)}.dark .dark\:bg-orange-300{background-color:var(--color-orange-300)}.dark .dark\:bg-orange-400{background-color:var(--color-orange-400)}.dark .dark\:bg-orange-500{background-color:var(--color-orange-500)}.dark .dark\:bg-orange-600{background-color:var(--color-orange-600)}.dark .dark\:bg-orange-700{background-color:var(--color-orange-700)}.dark .dark\:bg-orange-800{background-color:var(--color-orange-800)}.dark .dark\:bg-orange-900{background-color:var(--color-orange-900)}.dark .dark\:bg-orange-900\/20{background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-orange-900\/20{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .dark\:bg-orange-900\/30{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-orange-900\/30{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.dark .dark\:bg-orange-950{background-color:var(--color-orange-950)}.dark .dark\:bg-pink-50{background-color:var(--color-pink-50)}.dark .dark\:bg-pink-100{background-color:var(--color-pink-100)}.dark .dark\:bg-pink-200{background-color:var(--color-pink-200)}.dark .dark\:bg-pink-300{background-color:var(--color-pink-300)}.dark .dark\:bg-pink-400{background-color:var(--color-pink-400)}.dark .dark\:bg-pink-500{background-color:var(--color-pink-500)}.dark .dark\:bg-pink-600{background-color:var(--color-pink-600)}.dark .dark\:bg-pink-700{background-color:var(--color-pink-700)}.dark .dark\:bg-pink-800{background-color:var(--color-pink-800)}.dark .dark\:bg-pink-900{background-color:var(--color-pink-900)}.dark .dark\:bg-pink-950{background-color:var(--color-pink-950)}.dark .dark\:bg-purple-50{background-color:var(--color-purple-50)}.dark .dark\:bg-purple-100{background-color:var(--color-purple-100)}.dark .dark\:bg-purple-200{background-color:var(--color-purple-200)}.dark .dark\:bg-purple-300{background-color:var(--color-purple-300)}.dark .dark\:bg-purple-400{background-color:var(--color-purple-400)}.dark .dark\:bg-purple-500{background-color:var(--color-purple-500)}.dark .dark\:bg-purple-600{background-color:var(--color-purple-600)}.dark .dark\:bg-purple-700{background-color:var(--color-purple-700)}.dark .dark\:bg-purple-800{background-color:var(--color-purple-800)}.dark .dark\:bg-purple-800\/50{background-color:#6e11b080}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-800\/50{background-color:color-mix(in oklab,var(--color-purple-800) 50%,transparent)}}.dark .dark\:bg-purple-900{background-color:var(--color-purple-900)}.dark .dark\:bg-purple-900\/20{background-color:#59168b33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/20{background-color:color-mix(in oklab,var(--color-purple-900) 20%,transparent)}}.dark .dark\:bg-purple-900\/30{background-color:#59168b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/30{background-color:color-mix(in oklab,var(--color-purple-900) 30%,transparent)}}.dark .dark\:bg-purple-900\/50{background-color:#59168b80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/50{background-color:color-mix(in oklab,var(--color-purple-900) 50%,transparent)}}.dark .dark\:bg-purple-950{background-color:var(--color-purple-950)}.dark .dark\:bg-red-50{background-color:var(--color-red-50)}.dark .dark\:bg-red-100{background-color:var(--color-red-100)}.dark .dark\:bg-red-200{background-color:var(--color-red-200)}.dark .dark\:bg-red-300{background-color:var(--color-red-300)}.dark .dark\:bg-red-400{background-color:var(--color-red-400)}.dark .dark\:bg-red-500{background-color:var(--color-red-500)}.dark .dark\:bg-red-600{background-color:var(--color-red-600)}.dark .dark\:bg-red-700{background-color:var(--color-red-700)}.dark .dark\:bg-red-800{background-color:var(--color-red-800)}.dark .dark\:bg-red-900{background-color:var(--color-red-900)}.dark .dark\:bg-red-900\/20{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-red-900\/20{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .dark\:bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .dark\:bg-red-950{background-color:var(--color-red-950)}.dark .dark\:bg-rose-50{background-color:var(--color-rose-50)}.dark .dark\:bg-rose-100{background-color:var(--color-rose-100)}.dark .dark\:bg-rose-200{background-color:var(--color-rose-200)}.dark .dark\:bg-rose-300{background-color:var(--color-rose-300)}.dark .dark\:bg-rose-400{background-color:var(--color-rose-400)}.dark .dark\:bg-rose-500{background-color:var(--color-rose-500)}.dark .dark\:bg-rose-600{background-color:var(--color-rose-600)}.dark .dark\:bg-rose-700{background-color:var(--color-rose-700)}.dark .dark\:bg-rose-800{background-color:var(--color-rose-800)}.dark .dark\:bg-rose-900{background-color:var(--color-rose-900)}.dark .dark\:bg-rose-950{background-color:var(--color-rose-950)}.dark .dark\:bg-slate-50{background-color:var(--color-slate-50)}.dark .dark\:bg-slate-100{background-color:var(--color-slate-100)}.dark .dark\:bg-slate-200{background-color:var(--color-slate-200)}.dark .dark\:bg-slate-300{background-color:var(--color-slate-300)}.dark .dark\:bg-slate-400{background-color:var(--color-slate-400)}.dark .dark\:bg-slate-500{background-color:var(--color-slate-500)}.dark .dark\:bg-slate-600{background-color:var(--color-slate-600)}.dark .dark\:bg-slate-700{background-color:var(--color-slate-700)}.dark .dark\:bg-slate-700\/50{background-color:#31415880}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-slate-700\/50{background-color:color-mix(in oklab,var(--color-slate-700) 50%,transparent)}}.dark .dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark .dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark .dark\:bg-slate-950{background-color:var(--color-slate-950)}.dark .dark\:bg-teal-50{background-color:var(--color-teal-50)}.dark .dark\:bg-teal-100{background-color:var(--color-teal-100)}.dark .dark\:bg-teal-200{background-color:var(--color-teal-200)}.dark .dark\:bg-teal-300{background-color:var(--color-teal-300)}.dark .dark\:bg-teal-400{background-color:var(--color-teal-400)}.dark .dark\:bg-teal-500{background-color:var(--color-teal-500)}.dark .dark\:bg-teal-600{background-color:var(--color-teal-600)}.dark .dark\:bg-teal-700{background-color:var(--color-teal-700)}.dark .dark\:bg-teal-800{background-color:var(--color-teal-800)}.dark .dark\:bg-teal-900{background-color:var(--color-teal-900)}.dark .dark\:bg-teal-900\/30{background-color:#0b4f4a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-teal-900\/30{background-color:color-mix(in oklab,var(--color-teal-900) 30%,transparent)}}.dark .dark\:bg-teal-950{background-color:var(--color-teal-950)}.dark .dark\:bg-white{background-color:var(--color-white)}.dark .dark\:bg-yellow-50{background-color:var(--color-yellow-50)}.dark .dark\:bg-yellow-100{background-color:var(--color-yellow-100)}.dark .dark\:bg-yellow-200{background-color:var(--color-yellow-200)}.dark .dark\:bg-yellow-300{background-color:var(--color-yellow-300)}.dark .dark\:bg-yellow-400{background-color:var(--color-yellow-400)}.dark .dark\:bg-yellow-500{background-color:var(--color-yellow-500)}.dark .dark\:bg-yellow-600{background-color:var(--color-yellow-600)}.dark .dark\:bg-yellow-700{background-color:var(--color-yellow-700)}.dark .dark\:bg-yellow-800{background-color:var(--color-yellow-800)}.dark .dark\:bg-yellow-900{background-color:var(--color-yellow-900)}.dark .dark\:bg-yellow-900\/20{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/20{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .dark\:bg-yellow-900\/30{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/30{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .dark\:bg-yellow-900\/40{background-color:#733e0a66}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/40{background-color:color-mix(in oklab,var(--color-yellow-900) 40%,transparent)}}.dark .dark\:bg-yellow-950{background-color:var(--color-yellow-950)}.dark .dark\:from-blue-900{--tw-gradient-from:var(--color-blue-900);--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))}.dark .dark\:from-gray-50{--tw-gradient-from:var(--color-gray-50);--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))}.dark .dark\:from-gray-100{--tw-gradient-from:var(--color-gray-100);--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))}.dark .dark\:from-gray-200{--tw-gradient-from:var(--color-gray-200);--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))}.dark .dark\:from-gray-300{--tw-gradient-from:var(--color-gray-300);--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))}.dark .dark\:from-gray-400{--tw-gradient-from:var(--color-gray-400);--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))}.dark .dark\:from-gray-500{--tw-gradient-from:var(--color-gray-500);--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))}.dark .dark\:from-gray-600{--tw-gradient-from:var(--color-gray-600);--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))}.dark .dark\:from-gray-700{--tw-gradient-from:var(--color-gray-700);--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))}.dark .dark\:from-gray-800{--tw-gradient-from:var(--color-gray-800);--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))}.dark .dark\:from-gray-900{--tw-gradient-from:var(--color-gray-900);--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))}.dark .dark\:to-gray-50{--tw-gradient-to:var(--color-gray-50);--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))}.dark .dark\:to-gray-100{--tw-gradient-to:var(--color-gray-100);--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))}.dark .dark\:to-gray-200{--tw-gradient-to:var(--color-gray-200);--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))}.dark .dark\:to-gray-300{--tw-gradient-to:var(--color-gray-300);--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))}.dark .dark\:to-gray-400{--tw-gradient-to:var(--color-gray-400);--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))}.dark .dark\:to-gray-500{--tw-gradient-to:var(--color-gray-500);--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))}.dark .dark\:to-gray-600{--tw-gradient-to:var(--color-gray-600);--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))}.dark .dark\:to-gray-700{--tw-gradient-to:var(--color-gray-700);--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))}.dark .dark\:to-gray-800{--tw-gradient-to:var(--color-gray-800);--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))}.dark .dark\:to-gray-900{--tw-gradient-to:var(--color-gray-900);--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))}.dark .dark\:to-indigo-900{--tw-gradient-to:var(--color-indigo-900);--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))}.dark .dark\:text-center{text-align:center}.dark .dark\:text-justify{text-align:justify}.dark .dark\:text-left{text-align:left}.dark .dark\:text-right{text-align:right}.dark .dark\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.dark .dark\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.dark .dark\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.dark .dark\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.dark .dark\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.dark .dark\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.dark .dark\:font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.dark .dark\:font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.dark .dark\:font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.dark .dark\:whitespace-normal{white-space:normal}.dark .dark\:whitespace-nowrap{white-space:nowrap}.dark .dark\:whitespace-pre-wrap{white-space:pre-wrap}.dark .dark\:text-amber-50{color:var(--color-amber-50)}.dark .dark\:text-amber-100{color:var(--color-amber-100)}.dark .dark\:text-amber-200{color:var(--color-amber-200)}.dark .dark\:text-amber-300{color:var(--color-amber-300)}.dark .dark\:text-amber-400{color:var(--color-amber-400)}.dark .dark\:text-amber-500{color:var(--color-amber-500)}.dark .dark\:text-amber-600{color:var(--color-amber-600)}.dark .dark\:text-amber-700{color:var(--color-amber-700)}.dark .dark\:text-amber-800{color:var(--color-amber-800)}.dark .dark\:text-amber-900{color:var(--color-amber-900)}.dark .dark\:text-black{color:var(--color-black)}.dark .dark\:text-blue-50{color:var(--color-blue-50)}.dark .dark\:text-blue-100{color:var(--color-blue-100)}.dark .dark\:text-blue-200{color:var(--color-blue-200)}.dark .dark\:text-blue-300{color:var(--color-blue-300)}.dark .dark\:text-blue-400{color:var(--color-blue-400)}.dark .dark\:text-blue-500{color:var(--color-blue-500)}.dark .dark\:text-blue-600{color:var(--color-blue-600)}.dark .dark\:text-blue-700{color:var(--color-blue-700)}.dark .dark\:text-blue-800{color:var(--color-blue-800)}.dark .dark\:text-blue-900{color:var(--color-blue-900)}.dark .dark\:text-cyan-50{color:var(--color-cyan-50)}.dark .dark\:text-cyan-100{color:var(--color-cyan-100)}.dark .dark\:text-cyan-200{color:var(--color-cyan-200)}.dark .dark\:text-cyan-300{color:var(--color-cyan-300)}.dark .dark\:text-cyan-400{color:var(--color-cyan-400)}.dark .dark\:text-cyan-500{color:var(--color-cyan-500)}.dark .dark\:text-cyan-600{color:var(--color-cyan-600)}.dark .dark\:text-cyan-700{color:var(--color-cyan-700)}.dark .dark\:text-cyan-800{color:var(--color-cyan-800)}.dark .dark\:text-cyan-900{color:var(--color-cyan-900)}.dark .dark\:text-emerald-50{color:var(--color-emerald-50)}.dark .dark\:text-emerald-100{color:var(--color-emerald-100)}.dark .dark\:text-emerald-200{color:var(--color-emerald-200)}.dark .dark\:text-emerald-300{color:var(--color-emerald-300)}.dark .dark\:text-emerald-400{color:var(--color-emerald-400)}.dark .dark\:text-emerald-500{color:var(--color-emerald-500)}.dark .dark\:text-emerald-600{color:var(--color-emerald-600)}.dark .dark\:text-emerald-700{color:var(--color-emerald-700)}.dark .dark\:text-emerald-800{color:var(--color-emerald-800)}.dark .dark\:text-emerald-900{color:var(--color-emerald-900)}.dark .dark\:text-gray-50{color:var(--color-gray-50)}.dark .dark\:text-gray-100{color:var(--color-gray-100)}.dark .dark\:text-gray-200{color:var(--color-gray-200)}.dark .dark\:text-gray-300{color:var(--color-gray-300)}.dark .dark\:text-gray-400{color:var(--color-gray-400)}.dark .dark\:text-gray-500{color:var(--color-gray-500)}.dark .dark\:text-gray-600{color:var(--color-gray-600)}.dark .dark\:text-gray-700{color:var(--color-gray-700)}.dark .dark\:text-gray-800{color:var(--color-gray-800)}.dark .dark\:text-gray-900{color:var(--color-gray-900)}.dark .dark\:text-green-50{color:var(--color-green-50)}.dark .dark\:text-green-100{color:var(--color-green-100)}.dark .dark\:text-green-200{color:var(--color-green-200)}.dark .dark\:text-green-300{color:var(--color-green-300)}.dark .dark\:text-green-400{color:var(--color-green-400)}.dark .dark\:text-green-500{color:var(--color-green-500)}.dark .dark\:text-green-600{color:var(--color-green-600)}.dark .dark\:text-green-700{color:var(--color-green-700)}.dark .dark\:text-green-800{color:var(--color-green-800)}.dark .dark\:text-green-900{color:var(--color-green-900)}.dark .dark\:text-indigo-50{color:var(--color-indigo-50)}.dark .dark\:text-indigo-100{color:var(--color-indigo-100)}.dark .dark\:text-indigo-200{color:var(--color-indigo-200)}.dark .dark\:text-indigo-300{color:var(--color-indigo-300)}.dark .dark\:text-indigo-400{color:var(--color-indigo-400)}.dark .dark\:text-indigo-500{color:var(--color-indigo-500)}.dark .dark\:text-indigo-600{color:var(--color-indigo-600)}.dark .dark\:text-indigo-700{color:var(--color-indigo-700)}.dark .dark\:text-indigo-800{color:var(--color-indigo-800)}.dark .dark\:text-indigo-900{color:var(--color-indigo-900)}.dark .dark\:text-orange-50{color:var(--color-orange-50)}.dark .dark\:text-orange-100{color:var(--color-orange-100)}.dark .dark\:text-orange-200{color:var(--color-orange-200)}.dark .dark\:text-orange-300{color:var(--color-orange-300)}.dark .dark\:text-orange-400{color:var(--color-orange-400)}.dark .dark\:text-orange-500{color:var(--color-orange-500)}.dark .dark\:text-orange-600{color:var(--color-orange-600)}.dark .dark\:text-orange-700{color:var(--color-orange-700)}.dark .dark\:text-orange-800{color:var(--color-orange-800)}.dark .dark\:text-orange-900{color:var(--color-orange-900)}.dark .dark\:text-pink-50{color:var(--color-pink-50)}.dark .dark\:text-pink-100{color:var(--color-pink-100)}.dark .dark\:text-pink-200{color:var(--color-pink-200)}.dark .dark\:text-pink-300{color:var(--color-pink-300)}.dark .dark\:text-pink-400{color:var(--color-pink-400)}.dark .dark\:text-pink-500{color:var(--color-pink-500)}.dark .dark\:text-pink-600{color:var(--color-pink-600)}.dark .dark\:text-pink-700{color:var(--color-pink-700)}.dark .dark\:text-pink-800{color:var(--color-pink-800)}.dark .dark\:text-pink-900{color:var(--color-pink-900)}.dark .dark\:text-purple-50{color:var(--color-purple-50)}.dark .dark\:text-purple-100{color:var(--color-purple-100)}.dark .dark\:text-purple-200{color:var(--color-purple-200)}.dark .dark\:text-purple-300{color:var(--color-purple-300)}.dark .dark\:text-purple-400{color:var(--color-purple-400)}.dark .dark\:text-purple-500{color:var(--color-purple-500)}.dark .dark\:text-purple-600{color:var(--color-purple-600)}.dark .dark\:text-purple-700{color:var(--color-purple-700)}.dark .dark\:text-purple-800{color:var(--color-purple-800)}.dark .dark\:text-purple-900{color:var(--color-purple-900)}.dark .dark\:text-red-50{color:var(--color-red-50)}.dark .dark\:text-red-100{color:var(--color-red-100)}.dark .dark\:text-red-200{color:var(--color-red-200)}.dark .dark\:text-red-300{color:var(--color-red-300)}.dark .dark\:text-red-400{color:var(--color-red-400)}.dark .dark\:text-red-500{color:var(--color-red-500)}.dark .dark\:text-red-600{color:var(--color-red-600)}.dark .dark\:text-red-700{color:var(--color-red-700)}.dark .dark\:text-red-800{color:var(--color-red-800)}.dark .dark\:text-red-900{color:var(--color-red-900)}.dark .dark\:text-slate-50{color:var(--color-slate-50)}.dark .dark\:text-slate-100{color:var(--color-slate-100)}.dark .dark\:text-slate-200{color:var(--color-slate-200)}.dark .dark\:text-slate-300{color:var(--color-slate-300)}.dark .dark\:text-slate-400{color:var(--color-slate-400)}.dark .dark\:text-slate-500{color:var(--color-slate-500)}.dark .dark\:text-slate-600{color:var(--color-slate-600)}.dark .dark\:text-slate-700{color:var(--color-slate-700)}.dark .dark\:text-slate-800{color:var(--color-slate-800)}.dark .dark\:text-slate-900{color:var(--color-slate-900)}.dark .dark\:text-teal-50{color:var(--color-teal-50)}.dark .dark\:text-teal-100{color:var(--color-teal-100)}.dark .dark\:text-teal-200{color:var(--color-teal-200)}.dark .dark\:text-teal-300{color:var(--color-teal-300)}.dark .dark\:text-teal-400{color:var(--color-teal-400)}.dark .dark\:text-teal-500{color:var(--color-teal-500)}.dark .dark\:text-teal-600{color:var(--color-teal-600)}.dark .dark\:text-teal-700{color:var(--color-teal-700)}.dark .dark\:text-teal-800{color:var(--color-teal-800)}.dark .dark\:text-teal-900{color:var(--color-teal-900)}.dark .dark\:text-white{color:var(--color-white)}.dark .dark\:text-yellow-50{color:var(--color-yellow-50)}.dark .dark\:text-yellow-100{color:var(--color-yellow-100)}.dark .dark\:text-yellow-200{color:var(--color-yellow-200)}.dark .dark\:text-yellow-300{color:var(--color-yellow-300)}.dark .dark\:text-yellow-400{color:var(--color-yellow-400)}.dark .dark\:text-yellow-500{color:var(--color-yellow-500)}.dark .dark\:text-yellow-600{color:var(--color-yellow-600)}.dark .dark\:text-yellow-700{color:var(--color-yellow-700)}.dark .dark\:text-yellow-800{color:var(--color-yellow-800)}.dark .dark\:text-yellow-900{color:var(--color-yellow-900)}.dark .dark\:italic{font-style:italic}.dark .dark\:underline{text-decoration-line:underline}.dark .dark\:placeholder-gray-400::placeholder{color:var(--color-gray-400)}.dark .dark\:placeholder-gray-500::placeholder{color:var(--color-gray-500)}.dark .dark\:placeholder-gray-600::placeholder{color:var(--color-gray-600)}.dark .dark\:opacity-0{opacity:0}.dark .dark\:opacity-25{opacity:.25}.dark .dark\:opacity-50{opacity:.5}.dark .dark\:opacity-75{opacity:.75}.dark .dark\:opacity-100{opacity:1}.dark .dark\: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)}.dark .dark\:shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\: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)}.dark .dark\:shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}.dark .dark\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\: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)}.dark .dark\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.dark .dark\:shadow-purple-900\/50{--tw-shadow-color:#59168b80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:shadow-purple-900\/50{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-purple-900) 50%, transparent) var(--tw-shadow-alpha), transparent)}}.dark .dark\:ring-blue-50{--tw-ring-color:var(--color-blue-50)}.dark .dark\:ring-blue-100{--tw-ring-color:var(--color-blue-100)}.dark .dark\:ring-blue-200{--tw-ring-color:var(--color-blue-200)}.dark .dark\:ring-blue-300{--tw-ring-color:var(--color-blue-300)}.dark .dark\:ring-blue-400{--tw-ring-color:var(--color-blue-400)}.dark .dark\:ring-blue-500{--tw-ring-color:var(--color-blue-500)}.dark .dark\:ring-blue-600{--tw-ring-color:var(--color-blue-600)}.dark .dark\:ring-blue-700{--tw-ring-color:var(--color-blue-700)}.dark .dark\:ring-blue-800{--tw-ring-color:var(--color-blue-800)}.dark .dark\:ring-blue-900{--tw-ring-color:var(--color-blue-900)}.dark .dark\:ring-gray-50{--tw-ring-color:var(--color-gray-50)}.dark .dark\:ring-gray-100{--tw-ring-color:var(--color-gray-100)}.dark .dark\:ring-gray-200{--tw-ring-color:var(--color-gray-200)}.dark .dark\:ring-gray-300{--tw-ring-color:var(--color-gray-300)}.dark .dark\:ring-gray-400{--tw-ring-color:var(--color-gray-400)}.dark .dark\:ring-gray-500{--tw-ring-color:var(--color-gray-500)}.dark .dark\:ring-gray-600{--tw-ring-color:var(--color-gray-600)}.dark .dark\:ring-gray-700{--tw-ring-color:var(--color-gray-700)}.dark .dark\:ring-gray-800{--tw-ring-color:var(--color-gray-800)}.dark .dark\:ring-gray-900{--tw-ring-color:var(--color-gray-900)}.dark .dark\:ring-offset-gray-800{--tw-ring-offset-color:var(--color-gray-800)}.dark .dark\:ring-offset-gray-900{--tw-ring-offset-color:var(--color-gray-900)}@media(hover:hover){.dark .dark\:group-hover\:bg-gray-700:is(:where(.group):hover *){background-color:var(--color-gray-700)}.dark .dark\:group-hover\:bg-gray-800:is(:where(.group):hover *){background-color:var(--color-gray-800)}.dark .dark\:group-hover\:text-gray-100:is(:where(.group):hover *){color:var(--color-gray-100)}.dark .dark\:group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}}.dark .dark\:selection\:bg-blue-800 ::selection{background-color:var(--color-blue-800)}.dark .dark\:selection\:bg-blue-800::selection{background-color:var(--color-blue-800)}.dark .dark\:selection\:text-blue-100 ::selection{color:var(--color-blue-100)}.dark .dark\:selection\:text-blue-100::selection{color:var(--color-blue-100)}.dark .dark\:odd\:bg-gray-800:nth-child(odd){background-color:var(--color-gray-800)}.dark .dark\:odd\:bg-gray-900:nth-child(odd){background-color:var(--color-gray-900)}.dark .dark\:even\:bg-gray-800:nth-child(2n){background-color:var(--color-gray-800)}.dark .dark\:even\:bg-gray-900:nth-child(2n){background-color:var(--color-gray-900)}@media(hover:hover){.dark .dark\:hover\:border-amber-50:hover{border-color:var(--color-amber-50)}.dark .dark\:hover\:border-amber-100:hover{border-color:var(--color-amber-100)}.dark .dark\:hover\:border-amber-200:hover{border-color:var(--color-amber-200)}.dark .dark\:hover\:border-amber-300:hover{border-color:var(--color-amber-300)}.dark .dark\:hover\:border-amber-400:hover{border-color:var(--color-amber-400)}.dark .dark\:hover\:border-amber-500:hover{border-color:var(--color-amber-500)}.dark .dark\:hover\:border-amber-600:hover{border-color:var(--color-amber-600)}.dark .dark\:hover\:border-amber-700:hover{border-color:var(--color-amber-700)}.dark .dark\:hover\:border-amber-800:hover{border-color:var(--color-amber-800)}.dark .dark\:hover\:border-amber-900:hover{border-color:var(--color-amber-900)}.dark .dark\:hover\:border-blue-50:hover{border-color:var(--color-blue-50)}.dark .dark\:hover\:border-blue-100:hover{border-color:var(--color-blue-100)}.dark .dark\:hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.dark .dark\:hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.dark .dark\:hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.dark .dark\:hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.dark .dark\:hover\:border-blue-600:hover{border-color:var(--color-blue-600)}.dark .dark\:hover\:border-blue-700:hover{border-color:var(--color-blue-700)}.dark .dark\:hover\:border-blue-800:hover{border-color:var(--color-blue-800)}.dark .dark\:hover\:border-blue-900:hover{border-color:var(--color-blue-900)}.dark .dark\:hover\:border-gray-50:hover{border-color:var(--color-gray-50)}.dark .dark\:hover\:border-gray-100:hover{border-color:var(--color-gray-100)}.dark .dark\:hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.dark .dark\:hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.dark .dark\:hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.dark .dark\:hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.dark .dark\:hover\:border-gray-600:hover{border-color:var(--color-gray-600)}.dark .dark\:hover\:border-gray-700:hover{border-color:var(--color-gray-700)}.dark .dark\:hover\:border-gray-800:hover{border-color:var(--color-gray-800)}.dark .dark\:hover\:border-gray-900:hover{border-color:var(--color-gray-900)}.dark .dark\:hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.dark .dark\:hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.dark .dark\:hover\:bg-amber-200:hover{background-color:var(--color-amber-200)}.dark .dark\:hover\:bg-amber-300:hover{background-color:var(--color-amber-300)}.dark .dark\:hover\:bg-amber-400:hover{background-color:var(--color-amber-400)}.dark .dark\:hover\:bg-amber-500:hover{background-color:var(--color-amber-500)}.dark .dark\:hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.dark .dark\:hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.dark .dark\:hover\:bg-amber-800:hover{background-color:var(--color-amber-800)}.dark .dark\:hover\:bg-amber-900:hover{background-color:var(--color-amber-900)}.dark .dark\:hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.dark .dark\:hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.dark .dark\:hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.dark .dark\:hover\:bg-blue-300:hover{background-color:var(--color-blue-300)}.dark .dark\:hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.dark .dark\:hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.dark .dark\:hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.dark .dark\:hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.dark .dark\:hover\:bg-blue-800:hover{background-color:var(--color-blue-800)}.dark .dark\:hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.dark .dark\:hover\:bg-blue-900\/30:hover{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-blue-900\/30:hover{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .dark\:hover\:bg-blue-900\/50:hover{background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-blue-900\/50:hover{background-color:color-mix(in oklab,var(--color-blue-900) 50%,transparent)}}.dark .dark\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.dark .dark\:hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.dark .dark\:hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.dark .dark\:hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.dark .dark\:hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.dark .dark\:hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.dark .dark\:hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.dark .dark\:hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.dark .dark\:hover\:bg-gray-700\/30:hover{background-color:#3641534d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-700\/30:hover{background-color:color-mix(in oklab,var(--color-gray-700) 30%,transparent)}}.dark .dark\:hover\:bg-gray-700\/50:hover{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-700\/50:hover{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.dark .dark\:hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.dark .dark\:hover\:bg-gray-800\/50:hover{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-800\/50:hover{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.dark .dark\:hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.dark .dark\:hover\:bg-green-50:hover{background-color:var(--color-green-50)}.dark .dark\:hover\:bg-green-100:hover{background-color:var(--color-green-100)}.dark .dark\:hover\:bg-green-200:hover{background-color:var(--color-green-200)}.dark .dark\:hover\:bg-green-300:hover{background-color:var(--color-green-300)}.dark .dark\:hover\:bg-green-400:hover{background-color:var(--color-green-400)}.dark .dark\:hover\:bg-green-500:hover{background-color:var(--color-green-500)}.dark .dark\:hover\:bg-green-600:hover{background-color:var(--color-green-600)}.dark .dark\:hover\:bg-green-700:hover{background-color:var(--color-green-700)}.dark .dark\:hover\:bg-green-800:hover{background-color:var(--color-green-800)}.dark .dark\:hover\:bg-green-900:hover{background-color:var(--color-green-900)}.dark .dark\:hover\:bg-green-900\/30:hover{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-green-900\/30:hover{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .dark\:hover\:bg-indigo-50:hover{background-color:var(--color-indigo-50)}.dark .dark\:hover\:bg-indigo-100:hover{background-color:var(--color-indigo-100)}.dark .dark\:hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.dark .dark\:hover\:bg-indigo-300:hover{background-color:var(--color-indigo-300)}.dark .dark\:hover\:bg-indigo-400:hover{background-color:var(--color-indigo-400)}.dark .dark\:hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.dark .dark\:hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.dark .dark\:hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.dark .dark\:hover\:bg-indigo-800:hover{background-color:var(--color-indigo-800)}.dark .dark\:hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.dark .dark\:hover\:bg-indigo-900\/30:hover{background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-indigo-900\/30:hover{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .dark\:hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.dark .dark\:hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.dark .dark\:hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.dark .dark\:hover\:bg-orange-300:hover{background-color:var(--color-orange-300)}.dark .dark\:hover\:bg-orange-400:hover{background-color:var(--color-orange-400)}.dark .dark\:hover\:bg-orange-500:hover{background-color:var(--color-orange-500)}.dark .dark\:hover\:bg-orange-600:hover{background-color:var(--color-orange-600)}.dark .dark\:hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.dark .dark\:hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.dark .dark\:hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.dark .dark\:hover\:bg-orange-900\/30:hover{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-orange-900\/30:hover{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.dark .dark\:hover\:bg-pink-50:hover{background-color:var(--color-pink-50)}.dark .dark\:hover\:bg-pink-100:hover{background-color:var(--color-pink-100)}.dark .dark\:hover\:bg-pink-200:hover{background-color:var(--color-pink-200)}.dark .dark\:hover\:bg-pink-300:hover{background-color:var(--color-pink-300)}.dark .dark\:hover\:bg-pink-400:hover{background-color:var(--color-pink-400)}.dark .dark\:hover\:bg-pink-500:hover{background-color:var(--color-pink-500)}.dark .dark\:hover\:bg-pink-600:hover{background-color:var(--color-pink-600)}.dark .dark\:hover\:bg-pink-700:hover{background-color:var(--color-pink-700)}.dark .dark\:hover\:bg-pink-800:hover{background-color:var(--color-pink-800)}.dark .dark\:hover\:bg-pink-900:hover{background-color:var(--color-pink-900)}.dark .dark\:hover\:bg-purple-50:hover{background-color:var(--color-purple-50)}.dark .dark\:hover\:bg-purple-100:hover{background-color:var(--color-purple-100)}.dark .dark\:hover\:bg-purple-200:hover{background-color:var(--color-purple-200)}.dark .dark\:hover\:bg-purple-300:hover{background-color:var(--color-purple-300)}.dark .dark\:hover\:bg-purple-400:hover{background-color:var(--color-purple-400)}.dark .dark\:hover\:bg-purple-500:hover{background-color:var(--color-purple-500)}.dark .dark\:hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.dark .dark\:hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.dark .dark\:hover\:bg-purple-800:hover{background-color:var(--color-purple-800)}.dark .dark\:hover\:bg-purple-900:hover{background-color:var(--color-purple-900)}.dark .dark\:hover\:bg-purple-900\/30:hover{background-color:#59168b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-purple-900\/30:hover{background-color:color-mix(in oklab,var(--color-purple-900) 30%,transparent)}}.dark .dark\:hover\:bg-red-50:hover{background-color:var(--color-red-50)}.dark .dark\:hover\:bg-red-100:hover{background-color:var(--color-red-100)}.dark .dark\:hover\:bg-red-200:hover{background-color:var(--color-red-200)}.dark .dark\:hover\:bg-red-300:hover{background-color:var(--color-red-300)}.dark .dark\:hover\:bg-red-400:hover{background-color:var(--color-red-400)}.dark .dark\:hover\:bg-red-500:hover{background-color:var(--color-red-500)}.dark .dark\:hover\:bg-red-600:hover{background-color:var(--color-red-600)}.dark .dark\:hover\:bg-red-700:hover{background-color:var(--color-red-700)}.dark .dark\:hover\:bg-red-800:hover{background-color:var(--color-red-800)}.dark .dark\:hover\:bg-red-900:hover{background-color:var(--color-red-900)}.dark .dark\:hover\:bg-red-900\/20:hover{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-red-900\/20:hover{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .dark\:hover\:bg-red-900\/30:hover{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-red-900\/30:hover{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .dark\:hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.dark .dark\:hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.dark .dark\:hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.dark .dark\:hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.dark .dark\:hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}.dark .dark\:hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}.dark .dark\:hover\:bg-slate-600:hover{background-color:var(--color-slate-600)}.dark .dark\:hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.dark .dark\:hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.dark .dark\:hover\:bg-slate-900:hover{background-color:var(--color-slate-900)}.dark .dark\:hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.dark .dark\:hover\:bg-yellow-500:hover{background-color:var(--color-yellow-500)}.dark .dark\:hover\:bg-yellow-800:hover{background-color:var(--color-yellow-800)}.dark .dark\:hover\:bg-yellow-900\/30:hover{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-yellow-900\/30:hover{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .dark\:hover\:text-black:hover{color:var(--color-black)}.dark .dark\:hover\:text-blue-50:hover{color:var(--color-blue-50)}.dark .dark\:hover\:text-blue-100:hover{color:var(--color-blue-100)}.dark .dark\:hover\:text-blue-200:hover{color:var(--color-blue-200)}.dark .dark\:hover\:text-blue-300:hover{color:var(--color-blue-300)}.dark .dark\:hover\:text-blue-400:hover{color:var(--color-blue-400)}.dark .dark\:hover\:text-blue-500:hover{color:var(--color-blue-500)}.dark .dark\:hover\:text-blue-600:hover{color:var(--color-blue-600)}.dark .dark\:hover\:text-blue-700:hover{color:var(--color-blue-700)}.dark .dark\:hover\:text-blue-800:hover{color:var(--color-blue-800)}.dark .dark\:hover\:text-blue-900:hover{color:var(--color-blue-900)}.dark .dark\:hover\:text-gray-50:hover{color:var(--color-gray-50)}.dark .dark\:hover\:text-gray-100:hover{color:var(--color-gray-100)}.dark .dark\:hover\:text-gray-200:hover{color:var(--color-gray-200)}.dark .dark\:hover\:text-gray-300:hover{color:var(--color-gray-300)}.dark .dark\:hover\:text-gray-400:hover{color:var(--color-gray-400)}.dark .dark\:hover\:text-gray-500:hover{color:var(--color-gray-500)}.dark .dark\:hover\:text-gray-600:hover{color:var(--color-gray-600)}.dark .dark\:hover\:text-gray-700:hover{color:var(--color-gray-700)}.dark .dark\:hover\:text-gray-800:hover{color:var(--color-gray-800)}.dark .dark\:hover\:text-gray-900:hover{color:var(--color-gray-900)}.dark .dark\:hover\:text-green-50:hover{color:var(--color-green-50)}.dark .dark\:hover\:text-green-100:hover{color:var(--color-green-100)}.dark .dark\:hover\:text-green-200:hover{color:var(--color-green-200)}.dark .dark\:hover\:text-green-300:hover{color:var(--color-green-300)}.dark .dark\:hover\:text-green-400:hover{color:var(--color-green-400)}.dark .dark\:hover\:text-green-500:hover{color:var(--color-green-500)}.dark .dark\:hover\:text-green-600:hover{color:var(--color-green-600)}.dark .dark\:hover\:text-green-700:hover{color:var(--color-green-700)}.dark .dark\:hover\:text-green-800:hover{color:var(--color-green-800)}.dark .dark\:hover\:text-green-900:hover{color:var(--color-green-900)}.dark .dark\:hover\:text-purple-200:hover{color:var(--color-purple-200)}.dark .dark\:hover\:text-purple-300:hover{color:var(--color-purple-300)}.dark .dark\:hover\:text-red-50:hover{color:var(--color-red-50)}.dark .dark\:hover\:text-red-100:hover{color:var(--color-red-100)}.dark .dark\:hover\:text-red-200:hover{color:var(--color-red-200)}.dark .dark\:hover\:text-red-300:hover{color:var(--color-red-300)}.dark .dark\:hover\:text-red-400:hover{color:var(--color-red-400)}.dark .dark\:hover\:text-red-500:hover{color:var(--color-red-500)}.dark .dark\:hover\:text-red-600:hover{color:var(--color-red-600)}.dark .dark\:hover\:text-red-700:hover{color:var(--color-red-700)}.dark .dark\:hover\:text-red-800:hover{color:var(--color-red-800)}.dark .dark\:hover\:text-red-900:hover{color:var(--color-red-900)}.dark .dark\:hover\:text-slate-800:hover{color:var(--color-slate-800)}.dark .dark\:hover\:text-white:hover{color:var(--color-white)}}.dark .dark\:focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.dark .dark\:focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.dark .dark\:focus\:border-gray-600:focus{border-color:var(--color-gray-600)}.dark .dark\:focus\:border-gray-700:focus{border-color:var(--color-gray-700)}.dark .dark\:focus\:bg-gray-700:focus{background-color:var(--color-gray-700)}.dark .dark\:focus\:bg-gray-800:focus{background-color:var(--color-gray-800)}.dark .dark\:focus\:bg-gray-900:focus{background-color:var(--color-gray-900)}.dark .dark\:focus\:text-gray-100:focus{color:var(--color-gray-100)}.dark .dark\:focus\:text-gray-200:focus{color:var(--color-gray-200)}.dark .dark\:focus\:text-gray-300:focus{color:var(--color-gray-300)}.dark .dark\:focus\:text-white:focus{color:var(--color-white)}.dark .dark\:focus\:ring-amber-400:focus{--tw-ring-color:var(--color-amber-400)}.dark .dark\:focus\:ring-amber-500:focus{--tw-ring-color:var(--color-amber-500)}.dark .dark\:focus\:ring-amber-600:focus{--tw-ring-color:var(--color-amber-600)}.dark .dark\:focus\:ring-blue-400:focus{--tw-ring-color:var(--color-blue-400)}.dark .dark\:focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.dark .dark\:focus\:ring-blue-600:focus{--tw-ring-color:var(--color-blue-600)}.dark .dark\:focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.dark .dark\:focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.dark .dark\:focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.dark .dark\:focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.dark .dark\:focus\:ring-gray-700:focus{--tw-ring-color:var(--color-gray-700)}.dark .dark\:focus\:ring-indigo-400:focus{--tw-ring-color:var(--color-indigo-400)}.dark .dark\:focus\:ring-red-400:focus{--tw-ring-color:var(--color-red-400)}.dark .dark\:focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.dark .dark\:focus\:ring-slate-500:focus{--tw-ring-color:var(--color-slate-500)}.dark .dark\:focus\:ring-offset-gray-800:focus{--tw-ring-offset-color:var(--color-gray-800)}.dark .dark\:focus\:ring-offset-gray-900:focus{--tw-ring-offset-color:var(--color-gray-900)}.dark .dark\:active\:bg-gray-600:active{background-color:var(--color-gray-600)}.dark .dark\:active\:bg-gray-700:active{background-color:var(--color-gray-700)}.dark .dark\:active\:bg-gray-800:active{background-color:var(--color-gray-800)}.dark .dark\:disabled\:bg-gray-700:disabled{background-color:var(--color-gray-700)}.dark .dark\:disabled\:bg-gray-800:disabled{background-color:var(--color-gray-800)}.dark .dark\:disabled\:bg-gray-900:disabled{background-color:var(--color-gray-900)}.dark .dark\:disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.dark .dark\:disabled\:text-gray-600:disabled{color:var(--color-gray-600)}@media(hover:hover){.dark .dark\:disabled\:hover\:bg-indigo-500:disabled:hover{background-color:var(--color-indigo-500)}}.\[\&_table\]\:w-full table{width:100%}.\[\&_table\]\:border-collapse table{border-collapse:collapse}.\[\&_table\]\:border table{border-style:var(--tw-border-style);border-width:1px}.\[\&_table\]\:border-gray-300 table{border-color:var(--color-gray-300)}.dark .dark\:\[\&_table\]\:border-gray-600 table{border-color:var(--color-gray-600)}.\[\&_td\]\:border td{border-style:var(--tw-border-style);border-width:1px}.\[\&_td\]\:border-gray-300 td{border-color:var(--color-gray-300)}.\[\&_td\]\:px-4 td{padding-inline:calc(var(--spacing) * 4)}.\[\&_td\]\:py-2 td{padding-block:calc(var(--spacing) * 2)}.dark .dark\:\[\&_td\]\:border-gray-600 td{border-color:var(--color-gray-600)}.\[\&_th\]\:border th{border-style:var(--tw-border-style);border-width:1px}.\[\&_th\]\:border-gray-300 th{border-color:var(--color-gray-300)}.\[\&_th\]\:bg-gray-100 th{background-color:var(--color-gray-100)}.\[\&_th\]\:px-4 th{padding-inline:calc(var(--spacing) * 4)}.\[\&_th\]\:py-2 th{padding-block:calc(var(--spacing) * 2)}.\[\&_th\]\:text-left th{text-align:left}.dark .dark\:\[\&_th\]\:border-gray-600 th{border-color:var(--color-gray-600)}.dark .dark\:\[\&_th\]\:bg-gray-800 th{background-color:var(--color-gray-800)}.\[\&_tr\:hover\]\:bg-gray-50 tr:hover{background-color:var(--color-gray-50)}.dark .dark\:\[\&_tr\:hover\]\:bg-gray-700\/50 tr:hover{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:\[\&_tr\:hover\]\:bg-gray-700\/50 tr:hover{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}}.dashboard-content{max-width:80rem;margin:0 auto}.dashboard-stats-grid{gap:var(--spacing-5);margin-bottom:var(--spacing-8);grid-template-columns:1fr;display:grid}@media(min-width:768px){.dashboard-stats-grid{grid-template-columns:repeat(2,1fr)}}@media(min-width:1024px){.dashboard-stats-grid{grid-template-columns:repeat(4,1fr)}}.dashboard-main-grid{gap:var(--spacing-6);grid-template-columns:1fr;display:grid}@media(min-width:1024px){.dashboard-main-grid{grid-template-columns:repeat(5,1fr)}}.dashboard-column-left{gap:var(--spacing-6);flex-direction:column;display:flex}@media(min-width:1024px){.dashboard-column-left{grid-column:span 3}}.dashboard-column-right{gap:var(--spacing-6);flex-direction:column;display:flex}@media(min-width:1024px){.dashboard-column-right{grid-column:span 2}}.progress-bar-container{background-color:var(--color-slate-100);border-radius:var(--radius-full);width:100%;height:.5rem;overflow:hidden}.dark .progress-bar-container{background-color:var(--color-slate-700)}.progress-bar-fill{border-radius:var(--radius-full);height:.5rem;transition:width var(--transition-normal) var(--ease-in-out)}.progress-bar-emerald{background-color:var(--color-emerald-500)}.progress-bar-blue{background-color:var(--color-blue-500)}.progress-bar-slate{background-color:var(--color-slate-600)}.dark .progress-bar-slate{background-color:var(--color-slate-500)}.module-list{gap:var(--spacing-3);flex-direction:column;display:flex}.module-item{padding:var(--spacing-2) 0;justify-content:space-between;align-items:center;display:flex}.module-info{flex-direction:column;display:flex}.module-name{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .module-name{color:#fff}.module-version{font-size:var(--text-xs);color:var(--color-slate-500)}.dark .module-version{color:var(--color-slate-400)}.module-badge-active{padding:var(--spacing-1) var(--spacing-2);font-size:var(--text-xs);font-weight:var(--font-medium);background-color:var(--color-green-100);color:var(--color-green-700);border-radius:var(--radius-full)}.dark .module-badge-active{background-color:var(--color-green-900);color:var(--color-green-400)}.module-badge-inactive{padding:var(--spacing-1) var(--spacing-2);font-size:var(--text-xs);font-weight:var(--font-medium);background-color:var(--color-slate-100);color:var(--color-slate-600);border-radius:var(--radius-full)}.dark .module-badge-inactive{background-color:var(--color-slate-700);color:var(--color-slate-400)}.activity-list{gap:var(--spacing-4);flex-direction:column;display:flex}.activity-item{align-items:flex-start;gap:var(--spacing-3);display:flex}.activity-dot{width:.5rem;height:.5rem;margin-top:var(--spacing-2);border-radius:var(--radius-full);background-color:var(--color-blue-500);flex-shrink:0}.activity-content{flex:1;min-width:0}.activity-title{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .activity-title{color:#fff}.activity-desc{font-size:var(--text-sm);color:var(--color-slate-500)}.dark .activity-desc{color:var(--color-slate-400)}.activity-time{font-size:var(--text-xs);color:var(--color-slate-400);white-space:nowrap}.dark .activity-time{color:var(--color-slate-500)}.alert-list{gap:var(--spacing-3);flex-direction:column;display:flex}.alert-item{align-items:flex-start;gap:var(--spacing-3);padding:var(--spacing-3);border-radius:var(--radius-lg);display:flex}.alert-bg-info{background-color:var(--color-blue-50)}.dark .alert-bg-info{background-color:var(--color-slate-800)}.alert-bg-warning{background-color:var(--color-yellow-50)}.dark .alert-bg-warning{background-color:var(--color-slate-800)}.alert-bg-error{background-color:var(--color-red-50)}.dark .alert-bg-error{background-color:var(--color-slate-800)}.alert-bg-success{background-color:var(--color-green-50)}.dark .alert-bg-success{background-color:var(--color-slate-800)}.alert-dot{width:.5rem;height:.5rem;margin-top:var(--spacing-2);border-radius:var(--radius-full);flex-shrink:0}.alert-dot-info{background-color:var(--color-blue-500)}.alert-dot-warning{background-color:var(--color-yellow-500)}.alert-dot-error{background-color:var(--color-red-500)}.alert-dot-success{background-color:var(--color-green-500)}.alert-content{flex:1}.alert-title{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .alert-title{color:#fff}.alert-time{font-size:var(--text-xs);color:var(--color-slate-500);margin-top:var(--spacing-1)}.dark .alert-time{color:var(--color-slate-400)}.text-subtitle{font-size:var(--text-sm);color:var(--color-slate-500)}.dark .text-subtitle{color:var(--color-slate-400)}.section-margin{margin-bottom:var(--spacing-6)}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-between-mb{margin-bottom:var(--spacing-2);justify-content:space-between;align-items:center;display:flex}.resource-label{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-700)}.dark .resource-label{color:var(--color-slate-300)}.resource-value{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-700)}.dark .resource-value{color:var(--color-slate-300)}.icon-stat{width:1rem;height:1rem}.icon-trend{width:.875rem;height:.875rem}@media(max-width:640px){.input-split{flex-wrap:wrap}.input-split input{flex:1;min-width:0}.page-header{flex-direction:column;align-items:flex-start;gap:1rem}.page-actions{justify-content:flex-start;width:100%}}@media(min-width:640px){.responsive-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1024px){.responsive-grid,.grid-cols-1-lg-3{grid-template-columns:repeat(3,minmax(0,1fr))}.col-span-1-lg{grid-column:span 1/span 1}.col-span-2-lg{grid-column:span 2/span 2}}@media(max-width:480px){.responsive-grid{grid-template-columns:1fr!important}}@media(max-width:768px){.table-mobile-cards{display:block}.table-mobile-cards>*+*{margin-top:1rem}.table-desktop{display:none!important}}@media(max-width:640px){.modal{max-width:calc(100% - 2rem);max-height:calc(100vh - 2rem);margin-left:1rem;margin-right:1rem;overflow-y:auto}.modal-overlay{align-items:flex-start;padding:2rem 1rem 1rem}.modal-footer{flex-direction:column;gap:.5rem}.modal-footer .btn,.card-account{width:100%}}.sidebar-scrollbar{scrollbar-width:thin;scrollbar-color:transparent transparent;transition:scrollbar-color .3s}.sidebar-scrollbar:hover{scrollbar-color:#d1d5db transparent}.dark .sidebar-scrollbar:hover{scrollbar-color:#4b5563 transparent}.sidebar-scrollbar::-webkit-scrollbar{width:6px}.sidebar-scrollbar::-webkit-scrollbar-track{background:0 0}.sidebar-scrollbar::-webkit-scrollbar-thumb{background:0 0;border-radius:3px;transition:background .3s}.sidebar-scrollbar:hover::-webkit-scrollbar-thumb{background:#d1d5db}.dark .sidebar-scrollbar:hover::-webkit-scrollbar-thumb{background:#4b5563}.sidebar-scrollbar::-webkit-scrollbar-thumb:hover{background:#9ca3af}.dark .sidebar-scrollbar::-webkit-scrollbar-thumb:hover{background:#6b7280}@property --tw-border-spacing-x{syntax:"";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"";inherits:false;initial-value:0}@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-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@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-pan-x{syntax:"*";inherits:false}@property --tw-pan-y{syntax:"*";inherits:false}@property --tw-pinch-zoom{syntax:"*";inherits:false}@property --tw-scroll-snap-strictness{syntax:"*";inherits:false;initial-value:proximity}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@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-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{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-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}.yarl__fullsize{width:100%;height:100%}.yarl__relative{position:relative}.yarl__portal{position:fixed;inset:0;opacity:0;overflow:hidden;z-index:var(--yarl__portal_zindex,9999);transition:opacity var(--yarl__fade_animation_duration,.25s) var(--yarl__fade_animation_timing_function,ease);pointer-events:auto}.yarl__portal_open{opacity:1}.yarl__container{position:absolute;inset:0;overflow:hidden;background-color:var(--yarl__container_background_color,var(--yarl__color_backdrop,#000));outline:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;touch-action:var(--yarl__controller_touch_action,none);overscroll-behavior:var(--yarl__controller_overscroll_behavior,contain)}.yarl__carousel{display:flex;flex:0 0 auto;height:100%;align-content:center;justify-content:center;align-items:stretch;width:calc(100% + (var(--yarl__carousel_slides_count) - 1)*(100% + var(--yarl__carousel_spacing_px, 0)*1px + var(--yarl__carousel_spacing_percent, 0)*1%));transform:translate(var(--yarl__swipe_offset,0),var(--yarl__pull_offset,0));opacity:var(--yarl__pull_opacity,1)}.yarl__carousel_with_slides{-moz-column-gap:calc(var(--yarl__carousel_spacing_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_spacing_percent, 0)*1%);column-gap:calc(var(--yarl__carousel_spacing_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_spacing_percent, 0)*1%)}.yarl__flex_center{display:flex;justify-content:center;align-content:center;align-items:center}.yarl__slide{flex:1;position:relative;overflow:hidden;padding:calc(var(--yarl__carousel_padding_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_padding_percent, 0)*1%)}[dir=rtl] .yarl__slide{--yarl__direction:-1}.yarl__slide_image{max-width:100%;max-height:100%;-o-object-fit:contain;object-fit:contain;touch-action:var(--yarl__controller_touch_action,none);-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-touch-callout:none}.yarl__slide_image_cover{width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.yarl__slide_image_loading{opacity:0}@media screen and (min-width:800px){.yarl__slide_wrapper:not(.yarl__slide_wrapper_interactive) .yarl__slide_image{-webkit-transform:translateZ(0);-webkit-backface-visibility:hidden;-webkit-transform-style:preserve-3d}}.yarl__slide_placeholder{position:absolute;top:50%;left:50%;transform:translate(-50%) translateY(-50%);line-height:0}.yarl__slide_loading{color:var(--yarl__slide_icon_loading_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));animation:yarl__delayed_fadein 1s linear}.yarl__slide_loading line{animation:yarl__stroke_opacity 1s linear infinite}.yarl__slide_loading line:first-of-type{animation-delay:-1.875s}.yarl__slide_loading line:nth-of-type(2){animation-delay:-1.75s}.yarl__slide_loading line:nth-of-type(3){animation-delay:-1.625s}.yarl__slide_loading line:nth-of-type(4){animation-delay:-1.5s}.yarl__slide_loading line:nth-of-type(5){animation-delay:-1.375s}.yarl__slide_loading line:nth-of-type(6){animation-delay:-1.25s}.yarl__slide_loading line:nth-of-type(7){animation-delay:-1.125s}.yarl__slide_loading line:nth-of-type(8){animation-delay:-1s}.yarl__slide_error{width:var(--yarl__slide_icon_error_size,48px);height:var(--yarl__slide_icon_error_size,48px);color:var(--yarl__slide_icon_error_color,red)}@media(prefers-reduced-motion){.yarl__portal,.yarl__slide{transition:unset}.yarl__slide_loading,.yarl__slide_loading line{animation:unset}}.yarl__toolbar{position:absolute;inset:0 0 auto auto;display:flex;justify-content:flex-end;padding:var(--yarl__toolbar_padding,8px)}[dir=rtl] .yarl__toolbar{inset:0 auto auto 0}.yarl__icon{width:var(--yarl__icon_size,32px);height:var(--yarl__icon_size,32px)}.yarl__button{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--yarl__button_background_color,transparent);border:var(--yarl__button_border,0);margin:var(--yarl__button_margin,0);outline:none;line-height:0;padding:var(--yarl__button_padding,8px);color:var(--yarl__color_button,hsla(0,0%,100%,.8));filter:var(--yarl__button_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));-webkit-tap-highlight-color:transparent}.yarl__button:focus{color:var(--yarl__color_button_active,#fff)}.yarl__button:focus:not(:focus-visible){color:var(--yarl__color_button,hsla(0,0%,100%,.8))}.yarl__button:focus-visible{color:var(--yarl__color_button_active,#fff)}@media(hover:hover){.yarl__button:focus-visible:hover,.yarl__button:focus:hover,.yarl__button:hover{color:var(--yarl__color_button_active,#fff)}}.yarl__button:disabled{color:var(--yarl__color_button_disabled,hsla(0,0%,100%,.4));cursor:default}.yarl__navigation_next,.yarl__navigation_prev{position:absolute;top:50%;transform:translateY(-50%);padding:var(--yarl__navigation_button_padding,24px 16px)}.yarl__navigation_prev{left:0}[dir=rtl] .yarl__navigation_prev{left:unset;right:0;transform:translateY(-50%) rotate(180deg)}.yarl__navigation_next{right:0}[dir=rtl] .yarl__navigation_next{left:0;right:unset;transform:translateY(-50%) rotate(180deg)}.yarl__no_scroll{height:100%;overflow:hidden;overscroll-behavior:none}@keyframes yarl__delayed_fadein{0%{opacity:0}80%{opacity:0}to{opacity:1}}@keyframes yarl__stroke_opacity{0%{stroke-opacity:1}to{stroke-opacity:.125}}.yarl__counter{position:var(--yarl__counter_position,absolute);top:var(--yarl__counter_top,0);left:var(--yarl__counter_left,0);bottom:var(--yarl__counter_bottom,unset);right:var(--yarl__counter_right,unset);color:var(--yarl__counter_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));filter:var(--yarl__counter_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));margin:var(--yarl__counter_margin,var(--yarl__toolbar_padding,8px));padding:var(--yarl__counter_padding,var(--yarl__button_padding,8px));line-height:var(--yarl__counter_line_height,var(--yarl__icon_size,32px));-webkit-user-select:var(--yarl__counter_user_select,none);-moz-user-select:var(--yarl__counter_user_select,none);user-select:var(--yarl__counter_user_select,none)}[dir=rtl] .yarl__counter{left:var(--yarl__counter_rtl_left,unset);right:var(--yarl__counter_rtl_right,0)}.yarl__thumbnails{display:flex;height:100%}.yarl__thumbnails_bottom,.yarl__thumbnails_end .yarl__thumbnails_track,.yarl__thumbnails_start .yarl__thumbnails_track,.yarl__thumbnails_top{flex-direction:column}.yarl__thumbnails_wrapper{flex:1;position:relative}.yarl__thumbnails_container{flex:0 0 auto;background-color:var(--yarl__thumbnails_container_background_color,var(--yarl__color_backdrop,#000));padding:var(--yarl__thumbnails_container_padding,16px);position:relative;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-touch-callout:none}.yarl__thumbnails_vignette{position:absolute;pointer-events:none;--yarl__thumbnails_vignette_size:12%}@media(min-width:1200px){.yarl__thumbnails_vignette{--yarl__thumbnails_vignette_size:8%}}@media(min-width:2000px){.yarl__thumbnails_vignette{--yarl__thumbnails_vignette_size:5%}}.yarl__thumbnails_bottom .yarl__thumbnails_vignette,.yarl__thumbnails_top .yarl__thumbnails_vignette{height:100%;left:0;right:0;background:linear-gradient(to right,var(--yarl__color_backdrop,#000) 0,transparent var(--yarl__thumbnails_vignette_size,12%) calc(100% - var(--yarl__thumbnails_vignette_size, 12%)),var(--yarl__color_backdrop,#000) 100%)}.yarl__thumbnails_end .yarl__thumbnails_vignette,.yarl__thumbnails_start .yarl__thumbnails_vignette{width:100%;top:0;bottom:0;background:linear-gradient(to bottom,var(--yarl__color_backdrop,#000) 0,transparent var(--yarl__thumbnails_vignette_size,12%) calc(100% - var(--yarl__thumbnails_vignette_size, 12%)),var(--yarl__color_backdrop,#000) 100%)}.yarl__thumbnails_track{outline:none;gap:var(--yarl__thumbnails_thumbnail_gap,16px)}.yarl__thumbnails_thumbnail{flex:0 0 auto;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:var(--yarl__thumbnails_thumbnail_background,#000);border-width:var(--yarl__thumbnails_thumbnail_border,1px);border-style:var(--yarl__thumbnails_thumbnail_border_style,solid);border-color:var(--yarl__thumbnails_thumbnail_border_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));border-radius:var(--yarl__thumbnails_thumbnail_border_radius,4px);-webkit-tap-highlight-color:transparent;position:relative;overflow:hidden;padding:var(--yarl__thumbnails_thumbnail_padding,4px);outline:none;width:var(--yarl__thumbnails_thumbnail_width,120px);height:var(--yarl__thumbnails_thumbnail_height,80px);box-sizing:content-box}.yarl__thumbnails_thumbnail_active{border-color:var(--yarl__thumbnails_thumbnail_active_border_color,var(--yarl__color_button_active,#fff))}.yarl__thumbnails_thumbnail_fadein{opacity:0;animation:yarl__thumbnails_thumbnail_fadein var(--yarl__thumbnails_thumbnail_fadein_duration,.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadein_delay,0s) forwards}.yarl__thumbnails_thumbnail_fadeout{animation:yarl__thumbnails_thumbnail_fadeout var(--yarl__thumbnails_thumbnail_fadeout_duration,.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadeout_delay,0s) forwards;cursor:unset}.yarl__thumbnails_thumbnail_placeholder{visibility:hidden;cursor:unset}.yarl__thumbnails_thumbnail:focus{box-shadow:var(--yarl__thumbnails_thumbnail_focus_box_shadow,#000 0 0 0 2px,var(--yarl__color_button,hsla(0,0%,100%,.8)) 0 0 0 4px)}.yarl__thumbnails_thumbnail:focus:not(:focus-visible){box-shadow:unset}.yarl__thumbnails_thumbnail:focus-visible{box-shadow:var(--yarl__thumbnails_thumbnail_focus_box_shadow,#000 0 0 0 2px,var(--yarl__color_button,hsla(0,0%,100%,.8)) 0 0 0 4px)}.yarl__thumbnails_thumbnail_icon{color:var(--yarl__thumbnails_thumbnail_icon_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));filter:var(--yarl__thumbnails_thumbnail_icon_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));position:absolute;top:50%;left:50%;transform:translate(-50%) translateY(-50%);width:var(--yarl__thumbnails_thumbnail_icon_size,32px);height:var(--yarl__thumbnails_thumbnail_icon_size,32px)}@keyframes yarl__thumbnails_thumbnail_fadein{0%{opacity:0}to{opacity:1}}@keyframes yarl__thumbnails_thumbnail_fadeout{0%{opacity:1}to{opacity:0}} +@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-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-pan-x:initial;--tw-pan-y:initial;--tw-pinch-zoom:initial;--tw-scroll-snap-strictness:proximity;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-x-reverse:0;--tw-border-style:solid;--tw-divide-y-reverse:0;--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-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction: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-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:"Pretendard", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-amber-950:oklch(27.9% .077 45.635);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-yellow-950:oklch(28.6% .066 53.813);--color-lime-50:oklch(98.6% .031 120.757);--color-lime-100:oklch(96.7% .067 122.328);--color-lime-200:oklch(93.8% .127 124.321);--color-lime-300:oklch(89.7% .196 126.665);--color-lime-400:oklch(84.1% .238 128.85);--color-lime-500:oklch(76.8% .233 130.85);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-700:oklch(53.2% .157 131.589);--color-lime-800:oklch(45.3% .124 130.933);--color-lime-900:oklch(40.5% .101 131.063);--color-lime-950:oklch(27.4% .072 132.109);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-emerald-950:oklch(26.2% .051 172.552);--color-teal-50:oklch(98.4% .014 180.72);--color-teal-100:oklch(95.3% .051 180.801);--color-teal-200:oklch(91% .096 180.426);--color-teal-300:oklch(85.5% .138 181.071);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-teal-800:oklch(43.7% .078 188.216);--color-teal-900:oklch(38.6% .063 188.416);--color-teal-950:oklch(27.7% .046 192.524);--color-cyan-50:oklch(98.4% .019 200.873);--color-cyan-100:oklch(95.6% .045 203.388);--color-cyan-200:oklch(91.7% .08 205.041);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-800:oklch(45% .085 224.283);--color-cyan-900:oklch(39.8% .07 227.392);--color-cyan-950:oklch(30.2% .056 229.695);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-100:oklch(95.1% .026 236.824);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-800:oklch(44.3% .11 240.79);--color-sky-900:oklch(39.1% .09 240.876);--color-sky-950:oklch(29.3% .066 243.157);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-blue-950:oklch(28.2% .091 267.935);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-200:oklch(87% .065 274.039);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-700:oklch(45.7% .24 277.023);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-indigo-950:oklch(25.7% .09 281.288);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-100:oklch(94.3% .029 294.588);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-violet-950:oklch(28.3% .141 291.089);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-200:oklch(90.2% .063 306.703);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-purple-950:oklch(29.1% .149 302.717);--color-fuchsia-50:oklch(97.7% .017 320.058);--color-fuchsia-100:oklch(95.2% .037 318.852);--color-fuchsia-200:oklch(90.3% .076 319.62);--color-fuchsia-300:oklch(83.3% .145 321.434);--color-fuchsia-400:oklch(74% .238 322.16);--color-fuchsia-500:oklch(66.7% .295 322.15);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-fuchsia-700:oklch(51.8% .253 323.949);--color-fuchsia-800:oklch(45.2% .211 324.591);--color-fuchsia-900:oklch(40.1% .17 325.612);--color-fuchsia-950:oklch(29.3% .136 325.661);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-100:oklch(94.8% .028 342.258);--color-pink-200:oklch(89.9% .061 343.231);--color-pink-300:oklch(82.3% .12 346.018);--color-pink-400:oklch(71.8% .202 349.761);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-600:oklch(59.2% .249 .584);--color-pink-700:oklch(52.5% .223 3.958);--color-pink-800:oklch(45.9% .187 3.815);--color-pink-900:oklch(40.8% .153 2.432);--color-pink-950:oklch(28.4% .109 3.907);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-rose-950:oklch(27.1% .105 12.094);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-gray-950:oklch(13% .028 261.692);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-100:oklch(97% 0 0);--color-neutral-200:oklch(92.2% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-neutral-950:oklch(14.5% 0 0);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-stone-950:oklch(14.7% .004 49.25);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--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: 1.5 ;--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: 1.2 ;--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--text-8xl:6rem;--text-8xl--line-height:1;--text-9xl:8rem;--text-9xl--line-height:1;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-normal:0em;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--drop-shadow-sm:0 1px 2px #00000026;--drop-shadow-md:0 3px 3px #0000001f;--drop-shadow-lg:0 4px 4px #00000026;--drop-shadow-xl:0 9px 7px #0000001a;--drop-shadow-2xl:0 25px 25px #00000026;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--blur-sm:8px;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--blur-2xl:40px;--blur-3xl:64px;--aspect-video:16 / 9;--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;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}body{color:var(--color-gray-900);font-size:.875rem;line-height:1.5}.dark :is(body){color:var(--color-gray-100)}}@layer components{.admin_layout_root{background-color:var(--color-gray-50);height:100vh;display:flex;overflow:hidden}.dark .admin_layout_root{background-color:var(--color-gray-900)}.mobile_overlay{inset:calc(var(--spacing) * 0);z-index:40;background-color:#00000080;position:fixed}.left_sidebar_area{inset-block:calc(var(--spacing) * 0);left:calc(var(--spacing) * 0);z-index:50;width:calc(var(--spacing) * 64);background-color:var(--color-white);flex-direction:column;display:flex;position:relative}.dark .left_sidebar_area{background-color:var(--color-gray-800)}.left_sidebar_area{transition:width .3s}.left_sidebar_area_collapsed{width:calc(var(--spacing) * 0);overflow:hidden}.left_sidebar_area_portable{left:calc(var(--spacing) * 0);z-index:50;width:calc(var(--spacing) * 64);background-color:var(--color-white);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;flex-direction:column;transition-duration:.3s;display:flex;position:fixed}.dark .left_sidebar_area_portable{background-color:var(--color-gray-800)}.left_sidebar_area_portable{top:4rem;bottom:0;transform:translate(-100%)}.left_sidebar_area_portable_open{transform:translate(0)}.sidebar_header{height:calc(var(--spacing) * 16);padding-inline:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.sidebar_logo{height:calc(var(--spacing) * 10);min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);flex:1;display:flex}.logo_image{max-height:calc(var(--spacing) * 10);object-fit:contain;width:auto}.admin_sidebar_menu{padding-top:calc(var(--spacing) * 4);box-shadow:inset 3px 2px 2px var(--tw-shadow-color,#0000000a);flex:1;overflow-y:auto}.admin_sidebar_menu_portable{padding-top:calc(var(--spacing) * 4);flex:1;overflow-y:auto}.admin_sidebar_empty{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 8);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-400)}.dark .admin_sidebar_empty{color:var(--color-gray-500)}.admin_sidebar_empty_icon{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));color:var(--color-gray-300)}.dark .admin_sidebar_empty_icon{color:var(--color-gray-600)}.sidebar_footer{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100);padding:calc(var(--spacing) * 3);flex-shrink:0}.dark .sidebar_footer{border-color:var(--color-gray-700)}.sidebar_view_site_link{height:calc(var(--spacing) * 9);align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-600);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));text-decoration-line:none;display:flex}@media(hover:hover){.sidebar_view_site_link:hover{background-color:var(--color-slate-100);color:var(--color-slate-900)}}.dark .sidebar_view_site_link{color:var(--color-slate-300)}@media(hover:hover){.dark .sidebar_view_site_link:hover{background-color:var(--color-slate-700);color:var(--color-white)}}.sidebar_view_site_icon{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));flex-shrink:0}.sidebar_view_site_label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.right_content_wrapper{min-width:calc(var(--spacing) * 0);flex-direction:column;flex:1;display:flex}.right_content_area{min-height:calc(var(--spacing) * 0);min-width:calc(var(--spacing) * 0);flex:1;overflow:auto}.main_header{z-index:50;height:calc(var(--spacing) * 16);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 8);flex-shrink:0;justify-content:space-between;align-items:center;display:flex;position:relative}.dark .main_header{background-color:var(--color-gray-800)}.main_header{box-shadow:0 1px 3px #00000014}.header_left{flex:1;align-items:center;display:flex}.header_right{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.menu_toggle_btn{margin-left:calc(var(--spacing) * -2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);color:var(--color-slate-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.menu_toggle_btn:hover{background-color:var(--color-slate-100);color:var(--color-slate-900)}}.dark .menu_toggle_btn{color:var(--color-slate-400)}@media(hover:hover){.dark .menu_toggle_btn:hover{background-color:var(--color-slate-700);color:var(--color-white)}}.menu_toggle_btn_icon{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5)}.user_avatar_section{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 3);display:flex;position:relative}.user_avatar{height:calc(var(--spacing) * 9);width:calc(var(--spacing) * 9);background-color:var(--color-gray-200);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex;overflow:hidden}.dark .user_avatar{background-color:var(--color-gray-600)}.user_avatar_img{object-fit:cover;width:100%;height:100%}.user_info{display:none}@media(min-width:48rem){.user_info{display:block}}.mobile_header_portable{z-index:50;height:calc(var(--spacing) * 16);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex;position:relative}.dark .mobile_header_portable{background-color:var(--color-gray-800)}.mobile_header_portable{box-shadow:0 1px 3px #00000014}.mobile_header_left{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.mobile_header_right{align-items:center;gap:calc(var(--spacing) * 2);flex-shrink:0;display:flex}.mobile_logo{min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);flex:1;display:flex}.mobile_logo_image{height:calc(var(--spacing) * 8);object-fit:contain;width:auto;max-width:100%}.main_content{min-height:fit-content}.layout_warnings{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2)}.text-primary{color:var(--color-gray-900)}.dark .text-primary{color:var(--color-white)}.text-secondary{color:var(--color-gray-600)}.dark .text-secondary{color:var(--color-gray-400)}.text-muted,.dark .text-muted{color:var(--color-gray-500)}.text-tertiary{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .text-tertiary{color:var(--color-gray-400)}.text-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .text-label{color:var(--color-gray-400)}.text-label-subtle{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .text-label-subtle{color:var(--color-gray-400)}.text-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .text-heading{color:var(--color-white)}.section-heading{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-heading{color:var(--color-white)}.section-title{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-title{color:var(--color-white)}.section-description{margin-bottom:calc(var(--spacing) * 6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .section-description{color:var(--color-gray-400)}.card-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .card-title{color:var(--color-white)}.card-title-aside{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);display:flex}.card-description{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .card-description{color:var(--color-gray-400)}.admin-card>.card-title{margin-bottom:calc(var(--spacing) * 4)}.admin-card>.card-title:has(+.card-description){margin-bottom:calc(var(--spacing) * 0)}.admin-card>.card-description{margin-bottom:calc(var(--spacing) * 4)}.card-header{margin-bottom:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);--tw-leading:1;line-height:1;display:flex}.card-header>*{--tw-leading:1;line-height:1}.text-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}.dark .text-body{color:var(--color-gray-300)}.icon-muted{color:var(--color-gray-400)}.dark .icon-muted{color:var(--color-gray-500)}.bg-success{background-color:var(--color-green-100)}.dark .bg-success{background-color:var(--color-green-900)}.bg-success-light{background-color:var(--color-green-50)}.dark .bg-success-light{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .bg-success-light{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.bg-success-solid{background-color:var(--color-green-600)}.dark .bg-success-solid{background-color:var(--color-green-500)}.bg-error{background-color:var(--color-red-100)}.dark .bg-error{background-color:var(--color-red-900)}.bg-error-light{background-color:var(--color-red-50)}.dark .bg-error-light{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .bg-error-light{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.bg-error-solid{background-color:var(--color-red-600)}.dark .bg-error-solid{background-color:var(--color-red-500)}.bg-warning{background-color:var(--color-yellow-100)}.dark .bg-warning{background-color:var(--color-yellow-900)}.bg-warning-light{background-color:var(--color-yellow-50)}.dark .bg-warning-light{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .bg-warning-light{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.bg-warning-solid{background-color:var(--color-yellow-600)}.dark .bg-warning-solid{background-color:var(--color-yellow-500)}.bg-info{background-color:var(--color-blue-100)}.dark .bg-info{background-color:var(--color-blue-900)}.bg-info-light{background-color:var(--color-blue-50)}.dark .bg-info-light{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .bg-info-light{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.bg-info-solid{background-color:var(--color-blue-600)}.dark .bg-info-solid{background-color:var(--color-blue-500)}.bg-ready{background-color:var(--color-cyan-100)}.dark .bg-ready{background-color:var(--color-cyan-900)}.bg-ready-light{background-color:var(--color-cyan-50)}.dark .bg-ready-light{background-color:#104e6433}@supports (color:color-mix(in lab,red,red)){.dark .bg-ready-light{background-color:color-mix(in oklab,var(--color-cyan-900) 20%,transparent)}}.bg-ready-solid{background-color:var(--color-cyan-600)}.dark .bg-ready-solid{background-color:var(--color-cyan-400)}.bg-progress{background-color:var(--color-indigo-100)}.dark .bg-progress{background-color:var(--color-indigo-900)}.bg-progress-light{background-color:var(--color-indigo-50)}.dark .bg-progress-light{background-color:#312c8533}@supports (color:color-mix(in lab,red,red)){.dark .bg-progress-light{background-color:color-mix(in oklab,var(--color-indigo-900) 20%,transparent)}}.bg-progress-solid{background-color:var(--color-indigo-600)}.dark .bg-progress-solid{background-color:var(--color-indigo-400)}.bg-complete{background-color:var(--color-pink-100)}.dark .bg-complete{background-color:var(--color-pink-900)}.bg-complete-light{background-color:var(--color-pink-50)}.dark .bg-complete-light{background-color:#86104333}@supports (color:color-mix(in lab,red,red)){.dark .bg-complete-light{background-color:color-mix(in oklab,var(--color-pink-900) 20%,transparent)}}.bg-complete-solid{background-color:var(--color-pink-600)}.dark .bg-complete-solid{background-color:var(--color-pink-400)}.bg-hold{background-color:var(--color-gray-100)}.dark .bg-hold{background-color:var(--color-gray-800)}.bg-hold-light{background-color:var(--color-gray-50)}.dark .bg-hold-light{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .bg-hold-light{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-hold-solid{background-color:var(--color-gray-500)}.dark .bg-hold-solid{background-color:var(--color-gray-400)}.text-success{color:var(--color-green-600)}.dark .text-success{color:var(--color-green-400)}.text-error{color:var(--color-red-600)}.dark .text-error{color:var(--color-red-400)}.text-warning{color:var(--color-yellow-600)}.dark .text-warning{color:var(--color-yellow-400)}.text-info{color:var(--color-blue-600)}.dark .text-info{color:var(--color-blue-400)}.text-ready{color:var(--color-cyan-600)}.dark .text-ready{color:var(--color-cyan-400)}.text-progress{color:var(--color-indigo-600)}.dark .text-progress{color:var(--color-indigo-400)}.text-complete{color:var(--color-pink-600)}.dark .text-complete{color:var(--color-pink-400)}.text-hold{color:var(--color-gray-500)}.dark .text-hold{color:var(--color-gray-400)}.text-success-light{color:var(--color-white)}.dark .text-success-light{color:var(--color-green-900)}.text-error-light{color:var(--color-white)}.dark .text-error-light{color:var(--color-red-900)}.text-warning-light{color:var(--color-white)}.dark .text-warning-light{color:var(--color-yellow-900)}.text-info-light{color:var(--color-white)}.dark .text-info-light{color:var(--color-blue-900)}.text-ready-light{color:var(--color-white)}.dark .text-ready-light{color:var(--color-cyan-900)}.text-progress-light{color:var(--color-white)}.dark .text-progress-light{color:var(--color-indigo-900)}.text-complete-light{color:var(--color-white)}.dark .text-complete-light{color:var(--color-pink-900)}.text-hold-light{color:var(--color-white)}.dark .text-hold-light{color:var(--color-gray-900)}.border-success{border-color:var(--color-green-200)}.dark .border-success{border-color:var(--color-green-800)}.border-error{border-color:var(--color-red-200)}.dark .border-error{border-color:var(--color-red-800)}.border-warning{border-color:var(--color-yellow-200)}.dark .border-warning{border-color:var(--color-yellow-800)}.border-info{border-color:var(--color-blue-200)}.dark .border-info{border-color:var(--color-blue-800)}.border-ready{border-color:var(--color-cyan-200)}.dark .border-ready{border-color:var(--color-cyan-800)}.border-progress{border-color:var(--color-indigo-200)}.dark .border-progress{border-color:var(--color-indigo-800)}.border-complete{border-color:var(--color-pink-200)}.dark .border-complete{border-color:var(--color-pink-800)}.border-hold{border-color:var(--color-gray-300)}.dark .border-hold{border-color:var(--color-gray-600)}.btn-success{background-color:var(--color-green-600);color:var(--color-white)}@media(hover:hover){.btn-success:hover{background-color:var(--color-green-700)}}.dark .btn-success{background-color:var(--color-green-500)}@media(hover:hover){.dark .btn-success:hover{background-color:var(--color-green-600)}}.btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media(hover:hover){.btn-danger:hover{background-color:var(--color-red-700)}}.dark .btn-danger{background-color:var(--color-red-500)}@media(hover:hover){.dark .btn-danger:hover{background-color:var(--color-red-600)}}.btn-warning{background-color:var(--color-yellow-600);color:var(--color-white)}@media(hover:hover){.btn-warning:hover{background-color:var(--color-yellow-700)}}.dark .btn-warning{background-color:var(--color-yellow-500)}@media(hover:hover){.dark .btn-warning:hover{background-color:var(--color-yellow-600)}}.btn-info{background-color:var(--color-blue-600);color:var(--color-white)}@media(hover:hover){.btn-info:hover{background-color:var(--color-blue-700)}}.dark .btn-info{background-color:var(--color-blue-500)}@media(hover:hover){.dark .btn-info:hover{background-color:var(--color-blue-600)}}.btn-neutral{background-color:var(--color-gray-600);color:var(--color-white)}@media(hover:hover){.btn-neutral:hover{background-color:var(--color-gray-700)}}.dark .btn-neutral{background-color:var(--color-gray-500)}@media(hover:hover){.dark .btn-neutral:hover{background-color:var(--color-gray-600)}}.btn-sm{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5)}.button{cursor:pointer;justify-content:center;align-items:center;display:inline-flex}.button:disabled{cursor:not-allowed}.btn{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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:.2s;transition-duration:.2s;display:inline-flex}.btn:disabled{cursor:not-allowed;opacity:.5}.btn-primary{background-color:var(--color-gray-900);color:var(--color-white)}@media(hover:hover){.btn-primary:hover{background-color:var(--color-gray-800)}}.dark .btn-primary{background-color:var(--color-white);color:var(--color-gray-900)}@media(hover:hover){.dark .btn-primary:hover{background-color:var(--color-gray-100)}}.btn-secondary{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-gray-700)}@media(hover:hover){.btn-secondary:hover{background-color:var(--color-gray-50)}}.dark .btn-secondary{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-300)}@media(hover:hover){.dark .btn-secondary:hover{background-color:var(--color-gray-700)}}.btn-outline{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);background-color:#0000}@media(hover:hover){.btn-outline:hover{background-color:var(--color-gray-50)}}.dark .btn-outline{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .btn-outline:hover{background-color:var(--color-gray-700)}}.btn-ghost{color:var(--color-gray-600);background-color:#0000}@media(hover:hover){.btn-ghost:hover{background-color:var(--color-gray-100)}}.dark .btn-ghost{color:var(--color-gray-400)}@media(hover:hover){.dark .btn-ghost:hover{background-color:var(--color-gray-700)}}.btn-icon{border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);color:var(--color-gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.btn-icon:hover{background-color:var(--color-gray-100)}}.dark .btn-icon{color:var(--color-gray-400)}@media(hover:hover){.dark .btn-icon:hover{background-color:var(--color-gray-700)}}.btn-preset{background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);border-radius:3.40282e38px}.dark .btn-preset{background-color:var(--color-gray-700);color:var(--color-gray-300)}.btn-date{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .btn-date{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-date.active{border-color:var(--color-gray-900);background-color:var(--color-gray-900);color:var(--color-white)}.dark .btn-date.active{border-color:var(--color-white);background-color:var(--color-white);color:var(--color-gray-900)}.btn-badge{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);border-radius:3.40282e38px}.dark .btn-badge{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-badge.active{border-color:var(--color-gray-900);color:var(--color-gray-900)}.dark .btn-badge.active{border-color:var(--color-white);color:var(--color-blue-400)}.btn-group{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);display:inline-flex;overflow:hidden}.dark .btn-group{border-color:var(--color-gray-700)}.btn-group-item{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.btn-group-item:hover{background-color:var(--color-gray-50)}}.dark .btn-group-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}@media(hover:hover){.dark .btn-group-item:hover{background-color:var(--color-gray-700)}}.btn-group-item:last-child{border-right-style:var(--tw-border-style);border-right-width:0}.btn-group-item.active{background-color:var(--color-gray-900);color:var(--color-white)}.dark .btn-group-item.active{background-color:var(--color-white);color:var(--color-gray-900)}.btn-badge-green{border-color:var(--color-green-500);background-color:var(--color-green-50);color:var(--color-green-600)}.dark .btn-badge-green{border-color:var(--color-green-400);background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-green{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .btn-badge-green{color:var(--color-green-400)}.btn-badge-blue{border-color:var(--color-blue-500);background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .btn-badge-blue{border-color:var(--color-blue-400);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-blue{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .btn-badge-blue{color:var(--color-blue-400)}.btn-badge-cyan{border-color:var(--color-cyan-500);background-color:var(--color-cyan-50);color:var(--color-cyan-600)}.dark .btn-badge-cyan{border-color:var(--color-cyan-400);background-color:#104e6433}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-cyan{background-color:color-mix(in oklab,var(--color-cyan-900) 20%,transparent)}}.dark .btn-badge-cyan{color:var(--color-cyan-400)}.btn-badge-indigo{border-color:var(--color-indigo-500);background-color:var(--color-indigo-50);color:var(--color-indigo-600)}.dark .btn-badge-indigo{border-color:var(--color-indigo-400);background-color:#312c8533}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-indigo{background-color:color-mix(in oklab,var(--color-indigo-900) 20%,transparent)}}.dark .btn-badge-indigo{color:var(--color-indigo-400)}.btn-badge-pink{border-color:var(--color-pink-500);background-color:var(--color-pink-50);color:var(--color-pink-600)}.dark .btn-badge-pink{border-color:var(--color-pink-400);background-color:#86104333}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-pink{background-color:color-mix(in oklab,var(--color-pink-900) 20%,transparent)}}.dark .btn-badge-pink{color:var(--color-pink-400)}.btn-badge-red{border-color:var(--color-red-500);background-color:var(--color-red-50);color:var(--color-red-600)}.dark .btn-badge-red{border-color:var(--color-red-400);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-red{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .btn-badge-red{color:var(--color-red-400)}.btn-badge-gray{border-color:var(--color-gray-300);background-color:var(--color-gray-50);color:var(--color-gray-600)}.dark .btn-badge-gray{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-400)}.btn-badge-yellow{border-color:var(--color-yellow-500);background-color:var(--color-yellow-50);color:var(--color-yellow-600)}.dark .btn-badge-yellow{border-color:var(--color-yellow-400);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-yellow{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .btn-badge-yellow{color:var(--color-yellow-400)}.btn-badge-orange{border-color:var(--color-orange-500);background-color:var(--color-orange-50);color:var(--color-orange-600)}.dark .btn-badge-orange{border-color:var(--color-orange-400);background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .btn-badge-orange{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .btn-badge-orange{color:var(--color-orange-400)}.input{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input::placeholder{color:var(--color-gray-400)}.input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .input{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .input::placeholder{color:var(--color-gray-500)}.input:disabled{cursor:not-allowed;background-color:var(--color-gray-100);color:var(--color-gray-400)}.dark .input:disabled{background-color:var(--color-gray-800);color:var(--color-gray-500)}.input-tag{border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input-tag:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .input-tag{background-color:var(--color-gray-700);color:var(--color-gray-100)}.input-tag{min-height:2.25rem}.textarea{border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.textarea::placeholder{color:var(--color-gray-400)}.textarea:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .textarea{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .textarea::placeholder{color:var(--color-gray-500)}.textarea:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .textarea:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.input-readonly{cursor:default;color:var(--color-gray-500)}.dark .input-readonly{color:var(--color-gray-400)}.input-w-xs{width:calc(var(--spacing) * 20)}.input-w-sm{width:calc(var(--spacing) * 32)}.input-w-md{width:calc(var(--spacing) * 48)}.input-w-lg{width:calc(var(--spacing) * 64)}.input-w-xl{width:calc(var(--spacing) * 80)}.input-w-auto{width:auto}.input-search-container{border-radius:var(--radius-lg);background-color:var(--color-gray-100);align-items:center;display:flex;overflow:hidden}.dark .input-search-container{background-color:var(--color-gray-700)}.input-search-container .input-search-icon{padding-left:calc(var(--spacing) * 3);color:var(--color-gray-400)}.dark :is(.input-search-container .input-search-icon){color:var(--color-gray-500)}.input-search-container input{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);background-color:#0000;flex:1}.input-search-container input::placeholder{color:var(--color-gray-400)}.input-search-container input:focus{--tw-outline-style:none;outline-style:none}.dark :is(.input-search-container input){color:var(--color-gray-100)}.dark :is(.input-search-container input)::placeholder{color:var(--color-gray-500)}.input-search-container .input-search-dropdown{cursor:pointer;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}@media(hover:hover){.input-search-container .input-search-dropdown:hover{background-color:var(--color-gray-50)}}.dark :is(.input-search-container .input-search-dropdown){border-color:var(--color-gray-700);color:var(--color-gray-400)}@media(hover:hover){.dark :is(.input-search-container .input-search-dropdown):hover{background-color:var(--color-gray-700)}}.select{height:calc(var(--spacing) * 9);cursor:pointer;appearance:none;border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select{background-color:var(--color-gray-700);color:var(--color-gray-100)}.select:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .select:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem}.dark .select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%239ca3af' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")}.select-composite{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.select-composite:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select-composite{background-color:var(--color-gray-700);color:var(--color-gray-100)}.select-composite:disabled{cursor:not-allowed;background-color:var(--color-gray-200);color:var(--color-gray-400)}.dark .select-composite:disabled{background-color:var(--color-gray-700);color:var(--color-gray-500)}.select-composite-sm{height:calc(var(--spacing) * 8);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.select-w-sm{width:calc(var(--spacing) * 32)}.select-w-md{width:calc(var(--spacing) * 48)}.select-w-lg{width:calc(var(--spacing) * 64)}.input-split{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.input-split input{border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.input-split input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark :is(.input-split input){background-color:var(--color-gray-700);color:var(--color-gray-100)}.input-split .input-split-separator{color:var(--color-gray-400)}.dark :is(.input-split .input-split-separator){color:var(--color-gray-500)}.tag-input-container{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.tag{align-items:center;gap:calc(var(--spacing) * 1);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);border-radius:3.40282e38px;display:inline-flex}.dark .tag{background-color:var(--color-gray-700);color:var(--color-gray-300)}.tag .tag-remove{cursor:pointer;color:var(--color-gray-400)}@media(hover:hover){.tag .tag-remove:hover{color:var(--color-gray-600)}}.dark :is(.tag .tag-remove){color:var(--color-gray-500)}@media(hover:hover){.dark :is(.tag .tag-remove):hover{color:var(--color-gray-300)}}:where(.form-group>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}.textarea-sm{min-height:5rem}.textarea-md{min-height:8rem}.textarea-lg{min-height:12rem}.input-error{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-500)}.input-error:focus{--tw-ring-color:var(--color-red-500)}.dark .input-error{border-color:var(--color-red-500)}.input-success{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-500)}.input-success:focus{--tw-ring-color:var(--color-green-500)}.dark .input-success{border-color:var(--color-green-500)}.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-500)}:is(.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea):focus{--tw-ring-color:var(--color-red-500)}.dark :is(.form-field-error .input,.form-field-error input[type],.form-field-error select,.form-field-error textarea){border-color:var(--color-red-500)}.form-error{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-500);display:block}.dark .form-error{color:var(--color-red-400)}.form-success{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-600)}.dark .form-success{color:var(--color-green-400)}.checkbox{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-gray-900);border-radius:.25rem}.checkbox:focus{--tw-ring-color:var(--color-gray-500)}.dark .checkbox{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.checkbox:checked{border-color:var(--color-gray-900);background-color:var(--color-gray-900)}.dark .checkbox:checked{border-color:var(--color-white);background-color:var(--color-white)}.radio{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-blue-600);flex-shrink:0}.radio:focus{--tw-ring-color:var(--color-blue-500)}.dark .radio{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.dark .radio:focus{--tw-ring-color:var(--color-blue-400)}.radio:checked{border-color:var(--color-blue-600)}.dark .radio:checked{border-color:var(--color-blue-400)}.toggle{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 11);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));border-radius:3.40282e38px;align-items:center;display:inline-flex;position:relative}.toggle-track{inset:calc(var(--spacing) * 0);background-color:var(--color-gray-200);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));border-radius:3.40282e38px;position:absolute}.dark .toggle-track{background-color:var(--color-gray-700)}.toggle.active .toggle-track{background-color:var(--color-blue-600)}.toggle-handle{left:calc(var(--spacing) * .5);height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);background-color:var(--color-white);--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);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.toggle.active .toggle-handle{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.badge{align-items:center;gap:calc(var(--spacing) * 1);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline-flex}.badge-green{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-200);background-color:var(--color-green-100);color:var(--color-green-700)}.dark .badge-green{border-color:var(--color-green-800);background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-green{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .badge-green{color:var(--color-green-400)}.badge-blue{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-200);background-color:var(--color-blue-100);color:var(--color-blue-700)}.dark .badge-blue{border-color:var(--color-blue-800);background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-blue{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .badge-blue{color:var(--color-blue-400)}.badge-cyan{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-cyan-200);background-color:var(--color-cyan-100);color:var(--color-cyan-700)}.dark .badge-cyan{border-color:var(--color-cyan-800);background-color:#104e644d}@supports (color:color-mix(in lab,red,red)){.dark .badge-cyan{background-color:color-mix(in oklab,var(--color-cyan-900) 30%,transparent)}}.dark .badge-cyan{color:var(--color-cyan-400)}.badge-indigo{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-indigo-200);background-color:var(--color-indigo-100);color:var(--color-indigo-700)}.dark .badge-indigo{border-color:var(--color-indigo-800);background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .badge-indigo{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .badge-indigo{color:var(--color-indigo-400)}.badge-pink{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-pink-200);background-color:var(--color-pink-100);color:var(--color-pink-700)}.dark .badge-pink{border-color:var(--color-pink-800);background-color:#8610434d}@supports (color:color-mix(in lab,red,red)){.dark .badge-pink{background-color:color-mix(in oklab,var(--color-pink-900) 30%,transparent)}}.dark .badge-pink{color:var(--color-pink-400)}.badge-red{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-100);color:var(--color-red-700)}.dark .badge-red{border-color:var(--color-red-800);background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-red{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .badge-red{color:var(--color-red-400)}.badge-gray{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);color:var(--color-gray-600)}.dark .badge-gray{border-color:var(--color-gray-700);background-color:var(--color-gray-800);color:var(--color-gray-400)}.badge-yellow{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-100);color:var(--color-yellow-700)}.dark .badge-yellow{border-color:var(--color-yellow-800);background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .badge-yellow{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .badge-yellow{color:var(--color-yellow-400)}.badge-status{align-items:center;gap:calc(var(--spacing) * 1.5);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;display:inline-flex}.badge-status-success{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .badge-status-success{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-success{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .badge-status-success{color:var(--color-green-400)}.badge-status-processing{background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .badge-status-processing{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-processing{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .badge-status-processing{color:var(--color-blue-400)}.badge-status-error{background-color:var(--color-red-50);color:var(--color-red-600)}.dark .badge-status-error{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .badge-status-error{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .badge-status-error{color:var(--color-red-400)}.badge-status-expired{background-color:var(--color-gray-50);color:var(--color-gray-500)}.dark .badge-status-expired{background-color:var(--color-gray-800);color:var(--color-gray-400)}.search-bar{gap:calc(var(--spacing) * 3);flex-direction:column;display:flex}@media(min-width:48rem){.search-bar{flex-flow:wrap;align-items:center}}.search-bar-select{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.search-bar-select::placeholder{color:var(--color-gray-400)}.search-bar-select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .search-bar-select{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .search-bar-select::placeholder{color:var(--color-gray-500)}@media(min-width:48rem){.search-bar-select{width:calc(var(--spacing) * 32);flex-shrink:0}}.search-bar-select-custom{width:100%}@media(min-width:48rem){.search-bar-select-custom{width:calc(var(--spacing) * 32);flex-shrink:0}}.search-bar-input{height:calc(var(--spacing) * 9);border-radius:var(--radius-lg);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.search-bar-input::placeholder{color:var(--color-gray-400)}.search-bar-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .search-bar-input{background-color:var(--color-gray-700);color:var(--color-gray-100)}.dark .search-bar-input::placeholder{color:var(--color-gray-500)}@media(min-width:48rem){.search-bar-input{min-width:calc(var(--spacing) * 64);flex:1}}.search-bar-reset{cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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:.2s;border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);background-color:#0000;transition-duration:.2s;display:inline-flex}@media(hover:hover){.search-bar-reset:hover{background-color:var(--color-gray-50)}}.dark .search-bar-reset{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .search-bar-reset:hover{background-color:var(--color-gray-700)}}.search-bar-reset{gap:calc(var(--spacing) * 1.5);flex-shrink:0;width:100%}@media(min-width:48rem){.search-bar-reset{width:auto}}.filter-grid{gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}.filter-row{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}@media(min-width:48rem){.filter-row{flex-direction:row;align-items:center}}.filter-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);display:block}.dark .filter-label{color:var(--color-gray-400)}@media(min-width:48rem){.filter-label{margin-bottom:calc(var(--spacing) * 0);width:calc(var(--spacing) * 14);flex-shrink:0;align-items:center;display:flex}}.filter-buttons{align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.dropdown{display:inline-block;position:relative}.dropdown-trigger{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--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)}@media(hover:hover){.dropdown-trigger:hover{background-color:var(--color-gray-50)}}.dropdown-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dropdown-trigger{color:var(--color-gray-900)}.dark .dropdown-trigger{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-white)}@media(hover:hover){.dark .dropdown-trigger:hover{background-color:var(--color-gray-700)}}.dark .dropdown-trigger:focus{--tw-ring-color:var(--color-blue-400)}.dropdown-trigger-text{cursor:pointer;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900);align-items:center;display:inline-flex}@media(hover:hover){.dropdown-trigger-text:hover{color:var(--color-blue-600)}}.dark .dropdown-trigger-text{color:var(--color-white)}@media(hover:hover){.dark .dropdown-trigger-text:hover{color:var(--color-blue-400)}}.dropdown-menu{z-index:50;width:calc(var(--spacing) * 56);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--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);position:absolute}.dark .dropdown-menu{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.dropdown-menu-bottom-left{top:100%;left:calc(var(--spacing) * 0);margin-top:calc(var(--spacing) * 2)}.dropdown-menu-bottom-right{top:100%;right:calc(var(--spacing) * 0);margin-top:calc(var(--spacing) * 2)}.dropdown-menu-top-left{bottom:100%;left:calc(var(--spacing) * 0);margin-bottom:calc(var(--spacing) * 2)}.dropdown-menu-top-right{right:calc(var(--spacing) * 0);margin-bottom:calc(var(--spacing) * 2);bottom:100%}.dropdown-item{cursor:pointer;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}@media(hover:hover){.dropdown-item:hover{background-color:var(--color-gray-100)}}.dark .dropdown-item{color:var(--color-gray-300)}@media(hover:hover){.dark .dropdown-item:hover{background-color:var(--color-gray-700)}}.dropdown-item-icon{color:var(--color-gray-400)}.dark .dropdown-item-icon{color:var(--color-gray-500)}.dropdown-item-disabled{cursor:not-allowed;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-400)}.dark .dropdown-item-disabled{color:var(--color-gray-500)}.dropdown-item-focused{background-color:var(--color-gray-100)}.dark .dropdown-item-focused{background-color:var(--color-gray-700)}.dropdown-divider{margin-block:calc(var(--spacing) * 1);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100)}.dark .dropdown-divider{border-color:var(--color-gray-700)}.pagination{justify-content:center;align-items:center;gap:calc(var(--spacing) * 2);flex-wrap:wrap;display:flex}.pagination-btn{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);color:var(--color-gray-700);border-radius:.25rem}@media(hover:hover){.pagination-btn:hover{background-color:var(--color-gray-100)}}.pagination-btn:disabled{cursor:not-allowed;opacity:.5}.dark .pagination-btn{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-gray-300)}@media(hover:hover){.dark .pagination-btn:hover{background-color:var(--color-gray-700)}}.pagination-btn-active{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));border-width:1px;border-color:var(--color-blue-500);background-color:var(--color-blue-500);color:var(--color-white);border-radius:.25rem}.dark .pagination-btn-active{border-color:var(--color-blue-600);background-color:var(--color-blue-600)}.pagination-ellipsis{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .pagination-ellipsis{color:var(--color-gray-400)}.dialog-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.dialog-title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .dialog-title{color:var(--color-white)}.dialog-close{color:var(--color-gray-400)}@media(hover:hover){.dialog-close:hover{color:var(--color-gray-600)}}.dialog-close:focus{--tw-outline-style:none;outline-style:none}.dark .dialog-close{color:var(--color-gray-500)}@media(hover:hover){.dark .dialog-close:hover{color:var(--color-gray-300)}}.dialog-body{margin-bottom:calc(var(--spacing) * 6);color:var(--color-gray-700)}.dark .dialog-body{color:var(--color-gray-300)}.dialog-footer{justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.dialog-btn-primary{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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))}.dialog-btn-primary:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-primary:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-primary{background-color:var(--color-blue-600);color:var(--color-white)}@media(hover:hover){.dialog-btn-primary:hover{background-color:var(--color-blue-700)}}.dialog-btn-primary:focus{--tw-ring-color:var(--color-blue-500)}.dialog-btn-secondary{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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))}.dialog-btn-secondary:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-secondary:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-secondary{background-color:var(--color-gray-200);color:var(--color-gray-800)}@media(hover:hover){.dialog-btn-secondary:hover{background-color:var(--color-gray-300)}}.dialog-btn-secondary:focus{--tw-ring-color:var(--color-gray-500)}.dark .dialog-btn-secondary{background-color:var(--color-gray-700);color:var(--color-gray-200)}@media(hover:hover){.dark .dialog-btn-secondary:hover{background-color:var(--color-gray-600)}}.dialog-btn-danger{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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))}.dialog-btn-danger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-danger:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-danger{background-color:var(--color-red-600);color:var(--color-white)}@media(hover:hover){.dialog-btn-danger:hover{background-color:var(--color-red-700)}}.dialog-btn-danger:focus{--tw-ring-color:var(--color-red-500)}.dialog-btn-ghost{border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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))}.dialog-btn-ghost:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dialog-btn-ghost:disabled{cursor:not-allowed;opacity:.5}.dialog-btn-ghost{color:var(--color-gray-700);background-color:#0000}@media(hover:hover){.dialog-btn-ghost:hover{background-color:var(--color-gray-100)}}.dialog-btn-ghost:focus{--tw-ring-color:var(--color-gray-500)}.dark .dialog-btn-ghost{color:var(--color-gray-300)}@media(hover:hover){.dark .dialog-btn-ghost:hover{background-color:var(--color-gray-700)}}.alert-error{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-900);display:flex}.dark .alert-error{border-color:var(--color-red-800);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-error{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .alert-error{color:var(--color-red-100)}.alert-icon-info{margin-top:calc(var(--spacing) * .5);color:var(--color-blue-600);flex-shrink:0}.dark .alert-icon-info{color:var(--color-blue-400)}.alert-icon-success{margin-top:calc(var(--spacing) * .5);color:var(--color-green-600);flex-shrink:0}.dark .alert-icon-success{color:var(--color-green-400)}.alert-icon-warning{margin-top:calc(var(--spacing) * .5);color:var(--color-yellow-600);flex-shrink:0}.dark .alert-icon-warning{color:var(--color-yellow-400)}.alert-icon-error{margin-top:calc(var(--spacing) * .5);color:var(--color-red-600);flex-shrink:0}.dark .alert-icon-error{color:var(--color-red-400)}.alert-text-info{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-800);flex:1}.dark .alert-text-info{color:var(--color-blue-200)}.alert-text-success{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-800);flex:1}.dark .alert-text-success{color:var(--color-green-200)}.alert-text-warning{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-800);flex:1}.dark .alert-text-warning{color:var(--color-yellow-200)}.alert-text-error{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-800);flex:1}.dark .alert-text-error{color:var(--color-red-200)}.alert-dismiss{cursor:pointer;border-style:var(--tw-border-style);padding:calc(var(--spacing) * 0);background-color:#0000;border-width:0;flex-shrink:0}@media(hover:hover){.alert-dismiss:hover{opacity:.7}}.table-container{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);overflow:hidden}.dark .table-container{border-color:var(--color-gray-700)}.table{width:100%}.table thead{background-color:var(--color-gray-50)}.dark :is(.table thead){background-color:var(--color-gray-800)}.table th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-600)}.dark :is(.table th){border-color:var(--color-gray-700);color:var(--color-gray-400)}.table th.sortable{cursor:pointer}@media(hover:hover){.table th.sortable:hover{background-color:var(--color-gray-100)}.dark :is(.table th.sortable):hover{background-color:var(--color-gray-700)}}.table tbody tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);background-color:var(--color-white)}.dark :is(.table tbody tr){border-color:var(--color-gray-800);background-color:var(--color-gray-900)}.table tbody tr:hover{background-color:var(--color-gray-50)}.dark :is(.table tbody tr:hover){background-color:var(--color-gray-800)}.table tbody tr.selected{background-color:var(--color-blue-50)}.dark :is(.table tbody tr.selected){background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark :is(.table tbody tr.selected){background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.table td{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark :is(.table td){color:var(--color-gray-100)}.table-actions{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;display:flex}.dark .table-actions{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.log-table{width:100%}.log-table th{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500)}.dark :is(.log-table th){border-color:var(--color-gray-700);color:var(--color-gray-400)}.log-table td{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark :is(.log-table td){border-color:var(--color-gray-800);color:var(--color-gray-100)}.log-table tbody tr:last-child td{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.log-table-actor{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);align-items:center;display:inline-flex}.dark .log-table-actor{border-color:var(--color-gray-600);background-color:var(--color-gray-700);color:var(--color-gray-300)}.log-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .log-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.log-card-header{margin-bottom:calc(var(--spacing) * 2);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);display:flex}.log-card-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .log-card-body{color:var(--color-gray-100)}.modal-overlay{inset:calc(var(--spacing) * 0);z-index:50;background-color:#00000080;justify-content:center;align-items:center;display:flex;position:fixed}@supports (color:color-mix(in lab,red,red)){.modal-overlay{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.modal-wrapper{inset:calc(var(--spacing) * 0);justify-content:center;align-items:center;display:flex;position:fixed}.modal-backdrop{inset:calc(var(--spacing) * 0);background-color:#00000080;position:absolute}@supports (color:color-mix(in lab,red,red)){.modal-backdrop{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.modal-container{border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);max-width:calc(100vw - 2rem);max-height:90vh;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:relative;overflow:hidden}.dark .modal-container{background-color:var(--color-gray-800)}.modal-header-row{padding:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.modal-title{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .modal-title{color:var(--color-white)}.modal-close-btn{color:var(--color-gray-400);margin-left:auto}@media(hover:hover){.modal-close-btn:hover{color:var(--color-gray-600)}}.modal-close-btn:focus{--tw-outline-style:none;outline-style:none}.dark .modal-close-btn{color:var(--color-gray-500)}@media(hover:hover){.dark .modal-close-btn:hover{color:var(--color-gray-300)}}.modal-body-content{max-height:calc(90vh - 140px);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4);overflow-y:auto}.modal{margin-inline:calc(var(--spacing) * 4);width:100%;max-width:var(--container-md);border-radius:var(--radius-xl);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.dark .modal{background-color:var(--color-gray-800)}.modal-lg{max-width:var(--container-lg)}.modal-xl{max-width:var(--container-xl)}.modal-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;display:flex}.modal-close{cursor:pointer;border-radius:var(--radius-lg);padding:calc(var(--spacing) * 1);color:var(--color-gray-400)}@media(hover:hover){.modal-close:hover{background-color:var(--color-gray-100);color:var(--color-gray-600)}}.dark .modal-close{color:var(--color-gray-500)}@media(hover:hover){.dark .modal-close:hover{background-color:var(--color-gray-700);color:var(--color-gray-300)}}.modal-body{padding-block:calc(var(--spacing) * 4)}.modal-footer{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.danger-confirm-icon{margin-inline:auto;margin-bottom:calc(var(--spacing) * 4);height:calc(var(--spacing) * 12);width:calc(var(--spacing) * 12);background-color:var(--color-red-100);border-radius:3.40282e38px;justify-content:center;align-items:center;display:flex}.dark .danger-confirm-icon{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .danger-confirm-icon{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.danger-confirm-icon svg,.danger-confirm-icon .icon{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6);color:var(--color-red-600)}.dark :is(.danger-confirm-icon svg,.danger-confirm-icon .icon){color:var(--color-red-400)}.danger-confirm-title{margin-bottom:calc(var(--spacing) * 2);text-align:center;font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .danger-confirm-title{color:var(--color-white)}.danger-confirm-message{margin-bottom:calc(var(--spacing) * 4);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .danger-confirm-message{color:var(--color-gray-400)}.danger-confirm-warning{margin-bottom:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-50);padding:calc(var(--spacing) * 4)}.dark .danger-confirm-warning{border-color:var(--color-yellow-700);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .danger-confirm-warning{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.danger-confirm-warning-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-yellow-800)}.dark .danger-confirm-warning-title{color:var(--color-yellow-300)}.danger-confirm-warning-list{list-style-type:disc;list-style-position:inside}:where(.danger-confirm-warning-list>: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)))}.danger-confirm-warning-list{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-700)}.dark .danger-confirm-warning-list{color:var(--color-yellow-400)}.danger-confirm-alert{text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-600)}.dark .danger-confirm-alert{color:var(--color-red-400)}.danger-confirm-footer{margin-top:calc(var(--spacing) * 6);justify-content:center;gap:calc(var(--spacing) * 3);display:flex}.card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);width:100%;padding:calc(var(--spacing) * 6)}.dark .card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card{margin-bottom:calc(var(--spacing) * 6);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .admin-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card{flex-direction:column;display:flex}.admin-card-clickable{margin-bottom:calc(var(--spacing) * 2);cursor:pointer;border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5)}@media(hover:hover){.admin-card-clickable:hover{border-color:var(--color-gray-300)}}.dark .admin-card-clickable{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}@media(hover:hover){.dark .admin-card-clickable:hover{border-color:var(--color-gray-600)}}.admin-card-selected{margin-bottom:calc(var(--spacing) * 2);cursor:pointer;border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-500);background-color:var(--color-blue-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2.5)}.dark .admin-card-selected{border-color:var(--color-blue-500);background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .admin-card-selected{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.card-clickable{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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);transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));overflow:hidden}@media(hover:hover){.card-clickable:hover{--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)}}.dark .card-clickable{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.card-image{height:calc(var(--spacing) * 48);object-fit:cover;width:100%}.card-content{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));white-space:pre-line;color:var(--color-gray-600)}.dark .card-content{color:var(--color-gray-300)}.card-section-title{margin-inline:calc(var(--spacing) * -6);margin-bottom:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 6);padding-bottom:calc(var(--spacing) * 4);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .card-section-title{border-color:var(--color-gray-700);color:var(--color-white)}.card-account{border-radius:var(--radius-lg);background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4);position:relative}.dark .card-account{background-color:var(--color-gray-900)}.card-account-default{top:calc(var(--spacing) * 3);left:calc(var(--spacing) * 3);position:absolute}.card-account-delete{top:calc(var(--spacing) * 3);right:calc(var(--spacing) * 3);position:absolute}.admin-card-accordion{margin-bottom:calc(var(--spacing) * 6);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .admin-card-accordion{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.admin-card-accordion{flex-direction:column;display:flex}.admin-card-accordion>.card-header{cursor:pointer}.admin-card-accordion-icon{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;margin-left:auto;transition-duration:.2s}.dark .admin-card-accordion-icon{color:var(--color-gray-500)}.admin-card-accordion>.row-stack{margin-top:calc(var(--spacing) * 6)}.admin-card-accordion-icon.open{rotate:180deg}.tabs{border-radius:var(--radius-lg);background-color:var(--color-gray-100);padding:calc(var(--spacing) * 1);display:inline-flex}.dark .tabs{background-color:var(--color-gray-800)}.tab{cursor:pointer;border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.tab:hover{color:var(--color-gray-700)}}.dark .tab{color:var(--color-gray-400)}@media(hover:hover){.dark .tab:hover{color:var(--color-gray-300)}}.tab.active{background-color:var(--color-white);color:var(--color-gray-900);--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)}.dark .tab.active{background-color:var(--color-gray-700);color:var(--color-white)}.toast{right:calc(var(--spacing) * 4);bottom:calc(var(--spacing) * 4);z-index:50;position:fixed}.toast-item{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);background-color:var(--color-gray-900);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);color:var(--color-white);--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);display:flex}.dark .toast-item{background-color:var(--color-white);color:var(--color-gray-900)}.toast-icon{flex-shrink:0}.drag-item{align-items:center;gap:calc(var(--spacing) * 4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4);display:flex}@media(hover:hover){.drag-item:hover{--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)}}.dark .drag-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.drag-handle{cursor:grab;color:var(--color-gray-400)}.dark .drag-handle{color:var(--color-gray-500)}.drag-handle:active{cursor:grabbing}.table-mobile-cards{display:none}.excel-card{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .excel-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.excel-card-header{margin-bottom:calc(var(--spacing) * 4);justify-content:space-between;align-items:flex-start;gap:calc(var(--spacing) * 3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-bottom:calc(var(--spacing) * 3);display:flex}.dark .excel-card-header{border-color:var(--color-gray-700)}.excel-card-title{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .excel-card-title{color:var(--color-white)}:where(.excel-card-body>: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)))}.excel-card-row{padding-block:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));justify-content:space-between;align-items:center;display:flex}.excel-card-label{color:var(--color-gray-500);flex-shrink:0}.dark .excel-card-label{color:var(--color-gray-400)}.excel-card-value{text-align:right;color:var(--color-gray-900)}.dark .excel-card-value{color:var(--color-white)}.excel-card-footer{margin-top:calc(var(--spacing) * 4);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-100);padding-top:calc(var(--spacing) * 3);justify-content:space-between;align-items:center;display:flex}.dark .excel-card-footer{border-color:var(--color-gray-700)}.excel-card-creator{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700)}.dark .excel-card-creator{color:var(--color-gray-300)}.excel-card-creator-label{margin-right:calc(var(--spacing) * 2);color:var(--color-gray-500)}.dark .excel-card-creator-label{color:var(--color-gray-400)}.select-custom{display:inline-block;position:relative}.select-trigger{height:calc(var(--spacing) * 9);border-radius:var(--radius-xl);background-color:var(--color-gray-100);width:100%;padding-inline:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);cursor:pointer;justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;display:flex}.select-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .select-trigger{background-color:var(--color-gray-700);color:var(--color-gray-200)}.select-trigger:disabled{cursor:not-allowed;opacity:.5}.select-trigger span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.select-trigger-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;transition-duration:.2s}.dark .select-trigger-icon{color:var(--color-gray-500)}.select-trigger-icon.open{rotate:180deg}.select-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100%;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute;overflow:hidden}.dark .select-dropdown{border-color:var(--color-gray-600);background-color:var(--color-gray-800)}.select-dropdown.hidden{display:none}.select-dropdown-inner{max-height:calc(var(--spacing) * 60);padding-block:calc(var(--spacing) * 2);overflow:auto}.select-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);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));align-items:center;display:flex}@media(hover:hover){.select-option:hover{background-color:var(--color-gray-100)}}.dark .select-option{color:var(--color-gray-200)}@media(hover:hover){.dark .select-option:hover{background-color:var(--color-gray-700)}}.select-option.selected{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-600)}.dark .select-option.selected{color:var(--color-blue-400)}.select-option:disabled{cursor:not-allowed;opacity:.5}.select-option-check{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-blue-600);flex-shrink:0;margin-left:auto}.dark .select-option-check{color:var(--color-blue-400)}.multi-select{display:inline-block;position:relative}.multi-select-trigger{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);color:var(--color-gray-700);cursor:pointer;background-color:#0000;display:inline-flex}@media(hover:hover){.multi-select-trigger:hover{background-color:var(--color-gray-50)}}.dark .multi-select-trigger{border-color:var(--color-gray-600);color:var(--color-gray-300)}@media(hover:hover){.dark .multi-select-trigger:hover{background-color:var(--color-gray-700)}}.multi-select-count{color:var(--color-gray-500)}.dark .multi-select-count{color:var(--color-gray-400)}.multi-select-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);min-width:calc(var(--spacing) * 64);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--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);position:absolute}.dark .multi-select-dropdown{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.multi-select-dropdown.hidden{display:none}.multi-select-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .multi-select-header{border-color:var(--color-gray-700);color:var(--color-white)}.multi-select-hint{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--color-gray-500)}.dark .multi-select-hint{color:var(--color-gray-400)}.multi-select-items{max-height:calc(var(--spacing) * 80);padding-block:calc(var(--spacing) * 2);overflow:auto}.multi-select-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);display:flex}@media(hover:hover){.multi-select-item:hover{background-color:var(--color-gray-50)}}.dark .multi-select-item{color:var(--color-gray-300)}@media(hover:hover){.dark .multi-select-item:hover{background-color:var(--color-gray-700)}}.multi-select-item.disabled{cursor:not-allowed;opacity:.5}.multi-select-item.disabled:hover,.dark .multi-select-item.disabled:hover{background-color:#0000}.select-ghost{display:inline-block;position:relative}.select-ghost-trigger{align-items:center;gap:calc(var(--spacing) * 1);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);cursor:pointer;border-radius:var(--radius-lg);color:var(--color-gray-700);background-color:#0000;display:inline-flex}@media(hover:hover){.select-ghost-trigger:hover{background-color:var(--color-gray-100)}}.dark .select-ghost-trigger{color:var(--color-gray-300)}@media(hover:hover){.dark .select-ghost-trigger:hover{background-color:var(--color-gray-700)}}.select-ghost-dropdown{z-index:50;margin-top:calc(var(--spacing) * 1);min-width:calc(var(--spacing) * 32);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding-block:calc(var(--spacing) * 1);--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);position:absolute}.dark .select-ghost-dropdown{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.select-ghost-dropdown.hidden{display:none}.select-ghost-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);justify-content:space-between;align-items:center;display:flex}@media(hover:hover){.select-ghost-option:hover{background-color:var(--color-gray-50)}}.dark .select-ghost-option{color:var(--color-gray-300)}@media(hover:hover){.dark .select-ghost-option:hover{background-color:var(--color-gray-700)}}.select-ghost-option.selected{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-blue-600)}.dark .select-ghost-option.selected{color:var(--color-blue-400)}.select-ghost-check{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-blue-600)}.dark .select-ghost-check{color:var(--color-blue-400)}.searchable-dropdown{display:inline-block;position:relative}.searchable-dropdown-trigger{border-radius:var(--radius-xl);background-color:var(--color-gray-100);min-width:200px;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);cursor:pointer;justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:0;display:flex}.searchable-dropdown-trigger:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;outline-style:none}.dark .searchable-dropdown-trigger{background-color:var(--color-gray-700);color:var(--color-gray-200)}.searchable-dropdown-trigger:disabled{cursor:not-allowed;opacity:.5}.searchable-dropdown-trigger-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;flex-shrink:0;transition-duration:.2s}.dark .searchable-dropdown-trigger-icon{color:var(--color-gray-500)}.searchable-dropdown-trigger-icon.open{rotate:180deg}.searchable-dropdown-dropdown{z-index:50;margin-top:calc(var(--spacing) * 2);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100%;min-width:280px;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:absolute;overflow:hidden}.dark .searchable-dropdown-dropdown{border-color:var(--color-gray-600);background-color:var(--color-gray-800)}.searchable-dropdown-dropdown.hidden{display:none}.searchable-dropdown-search{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding:calc(var(--spacing) * 3)}.dark .searchable-dropdown-search{border-color:var(--color-gray-700)}.searchable-dropdown-search-input{border-radius:var(--radius-lg);background-color:var(--color-gray-50);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.searchable-dropdown-search-input::placeholder{color:var(--color-gray-400)}.searchable-dropdown-search-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;border-color:#0000;outline-style:none}.dark .searchable-dropdown-search-input{border-color:var(--color-gray-600);background-color:var(--color-gray-700);color:var(--color-white)}.dark .searchable-dropdown-search-input::placeholder{color:var(--color-gray-500)}.searchable-dropdown-items{max-height:calc(var(--spacing) * 80);padding-block:calc(var(--spacing) * 2);overflow:auto}.searchable-dropdown-option{cursor:pointer;width:100%;padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);text-align:left;align-items:flex-start;gap:calc(var(--spacing) * 3);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));display:flex}@media(hover:hover){.searchable-dropdown-option:hover{background-color:var(--color-gray-100)}.dark .searchable-dropdown-option:hover{background-color:var(--color-gray-700)}}.searchable-dropdown-option:disabled{cursor:not-allowed;opacity:.5}.searchable-dropdown-check-area{margin-top:calc(var(--spacing) * .5);height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);flex-shrink:0}.searchable-dropdown-check{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);color:var(--color-blue-600)}.dark .searchable-dropdown-check{color:var(--color-blue-400)}.searchable-dropdown-option-content{min-width:calc(var(--spacing) * 0);flex:1}.searchable-dropdown-option-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .searchable-dropdown-option-label{color:var(--color-blue-400)}.searchable-dropdown-option.selected .searchable-dropdown-option-label{color:var(--color-blue-600)}.dark :is(.searchable-dropdown-option.selected .searchable-dropdown-option-label){color:var(--color-blue-400)}.searchable-dropdown-option-description{margin-top:calc(var(--spacing) * .5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .searchable-dropdown-option-description{color:var(--color-gray-400)}.searchable-dropdown-message{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 6);text-align:center;color:var(--color-gray-500)}.dark .searchable-dropdown-message{color:var(--color-gray-400)}.stat-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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);transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.stat-card:hover{--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)}}.dark .stat-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.stats-icon{border-radius:var(--radius-lg);padding:calc(var(--spacing) * 3)}.stats-icon-blue{background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .stats-icon-blue{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-blue{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .stats-icon-blue{color:var(--color-blue-400)}.stats-icon-green{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .stats-icon-green{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-green{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .stats-icon-green{color:var(--color-green-400)}.stats-icon-purple{background-color:var(--color-purple-50);color:var(--color-purple-600)}.dark .stats-icon-purple{background-color:#59168b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-purple{background-color:color-mix(in oklab,var(--color-purple-900) 20%,transparent)}}.dark .stats-icon-purple{color:var(--color-purple-400)}.stats-icon-orange{background-color:var(--color-orange-50);color:var(--color-orange-600)}.dark .stats-icon-orange{background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .stats-icon-orange{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .stats-icon-orange{color:var(--color-orange-400)}.stats-change{align-items:center;gap:calc(var(--spacing) * 1);border-radius:var(--radius-md);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 1);display:flex}.stats-trend-up{background-color:var(--color-green-50);color:var(--color-green-600)}.dark .stats-trend-up{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .stats-trend-up{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .stats-trend-up{color:var(--color-green-400)}.stats-trend-down{background-color:var(--color-red-50);color:var(--color-red-600)}.dark .stats-trend-down{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .stats-trend-down{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .stats-trend-down{color:var(--color-red-400)}.stats-trend-neutral{background-color:var(--color-gray-50);color:var(--color-gray-600)}.dark .stats-trend-neutral{background-color:var(--color-gray-700);color:var(--color-gray-400)}.stats-value-lg{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-gray-900)}.dark .stats-value-lg{color:var(--color-white)}.stats-label{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .stats-label{color:var(--color-gray-400)}.stats-sublabel{align-items:center;gap:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:flex}.dark .stats-sublabel{color:var(--color-gray-400)}:where(.tree>: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)))}.tree-item{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);display:flex}@media(hover:hover){.tree-item:hover{background-color:var(--color-gray-50)}.dark .tree-item:hover{background-color:var(--color-gray-800)}}.tree-item.selected{background-color:var(--color-blue-50)}.dark .tree-item.selected{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tree-item.selected{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.tree-item-content{min-width:calc(var(--spacing) * 0);align-items:center;gap:calc(var(--spacing) * 2);flex:1;display:flex}.tree-item-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);color:var(--color-gray-400);flex-shrink:0}.dark .tree-item-icon{color:var(--color-gray-500)}.tree-item-label{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-700);overflow:hidden}.dark .tree-item-label{color:var(--color-gray-300)}.tree-expand-btn{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6);color:var(--color-gray-400);border-radius:.25rem;justify-content:center;align-items:center;display:flex}@media(hover:hover){.tree-expand-btn:hover{background-color:var(--color-gray-100)}}.dark .tree-expand-btn{color:var(--color-gray-500)}@media(hover:hover){.dark .tree-expand-btn:hover{background-color:var(--color-gray-700)}}.tree-expand-btn.hidden{visibility:hidden}.tree-children{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-children{border-color:var(--color-gray-700)}.tree-level-1{margin-left:calc(var(--spacing) * 0);border-left-style:var(--tw-border-style);padding-left:calc(var(--spacing) * 0);border-left-width:0}.tree-level-2{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-2{border-color:var(--color-gray-700)}.tree-level-3{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-3{border-color:var(--color-gray-700)}.tree-level-4{margin-left:calc(var(--spacing) * 6);border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-gray-200);padding-left:calc(var(--spacing) * 4)}.dark .tree-level-4{border-color:var(--color-gray-700)}.category-item{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900);justify-content:space-between;align-items:center;display:flex}.dark .category-item{color:var(--color-white)}@media(hover:hover){.category-item:hover{background-color:var(--color-gray-50)}.dark .category-item:hover{background-color:var(--color-gray-700)}}.category-item[data-selected=true]{background-color:var(--color-blue-600);color:var(--color-white)}.category-item[data-selected=true]:hover{background-color:var(--color-blue-700)}.category-item-mobile{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .category-item-mobile{color:var(--color-white)}.category-item-mobile[data-selected=true]{background-color:var(--color-blue-50)}.dark .category-item-mobile[data-selected=true]{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .category-item-mobile[data-selected=true]{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.stats-card{height:calc(var(--spacing) * 36);border-radius:var(--radius-2xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-100);background-color:var(--color-white);padding:calc(var(--spacing) * 5);flex-direction:column;justify-content:space-between;display:flex}.dark .stats-card{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}.dashboard-panel{border-radius:var(--radius-3xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-100);background-color:var(--color-white);padding:calc(var(--spacing) * 6);--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)}.dark .dashboard-panel{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}.stats-value{margin-bottom:calc(var(--spacing) * 2);--tw-leading:1;--tw-font-weight:var(--font-weight-bold);font-size:2rem;line-height:1;font-weight:var(--font-weight-bold);color:var(--color-slate-900)}.dark .stats-value{color:var(--color-white)}.stats-trend{align-items:center;gap:calc(var(--spacing) * 1);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);color:var(--color-emerald-500);display:flex}.status-active{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-green-100);color:var(--color-green-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-active{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .status-active{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .status-active{color:var(--color-green-400)}.status-inactive{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-gray-100);color:var(--color-gray-600);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-inactive{background-color:var(--color-gray-700);color:var(--color-gray-400)}.status-pending{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-yellow-100);color:var(--color-yellow-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-pending{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .status-pending{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .status-pending{color:var(--color-yellow-400)}.status-error{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);background-color:var(--color-red-100);color:var(--color-red-700);border-radius:3.40282e38px;align-items:center;display:inline-flex}.dark .status-error{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .status-error{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .status-error{color:var(--color-red-400)}.list-item{align-items:center;gap:calc(var(--spacing) * 3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-100);padding-block:calc(var(--spacing) * 3);display:flex}.dark .list-item{border-color:var(--color-gray-700)}.list-item:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.list-item-hover{margin-inline:calc(var(--spacing) * -3);align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);display:flex}@media(hover:hover){.list-item-hover:hover{background-color:var(--color-gray-50)}.dark .list-item-hover:hover{background-color:var(--color-gray-800)}}.empty-state{padding-block:calc(var(--spacing) * 12);text-align:center;flex-direction:column;justify-content:center;align-items:center;display:flex}.empty-state-icon{margin-bottom:calc(var(--spacing) * 4);height:calc(var(--spacing) * 16);width:calc(var(--spacing) * 16);color:var(--color-gray-300)}.dark .empty-state-icon{color:var(--color-gray-600)}.empty-state-title{margin-bottom:calc(var(--spacing) * 2);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .empty-state-title{color:var(--color-white)}.empty-state-description{margin-bottom:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .empty-state-description{color:var(--color-gray-400)}.page-header{margin-bottom:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.page-header-padded{margin-bottom:calc(var(--spacing) * 6);padding-inline:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.page-header-fullbleed{margin-inline:calc(var(--spacing) * -6);margin-bottom:calc(var(--spacing) * 6);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;display:flex}.dark .page-header-fullbleed{background-color:var(--color-gray-900)}.page-actions{align-items:center;gap:calc(var(--spacing) * 3);display:flex}.section-card{margin-top:calc(var(--spacing) * 6);scroll-margin-top:calc(var(--spacing) * 32);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6)}.dark .section-card{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.section-header{margin-bottom:calc(var(--spacing) * 6);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-header{color:var(--color-white)}.info-row{margin-bottom:calc(var(--spacing) * 4);align-items:center;gap:calc(var(--spacing) * 2);display:flex}.action-buttons{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.form-label{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900);display:block}.dark .form-label{color:var(--color-white)}.form-label-muted{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-500);display:block}.dark .form-label-muted{color:var(--color-gray-400)}.form-hint{margin-top:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .form-hint{color:var(--color-gray-400)}.text-value{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-900)}.dark .text-value{color:var(--color-white)}.text-link{cursor:pointer;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-600)}@media(hover:hover){.text-link:hover{color:var(--color-blue-700)}}.dark .text-link{color:var(--color-blue-400)}@media(hover:hover){.dark .text-link:hover{color:var(--color-blue-300)}}.icon-xs{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3)}.icon-sm{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4)}.icon-md{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5)}.icon-lg{height:calc(var(--spacing) * 6);width:calc(var(--spacing) * 6)}.icon-xl{height:calc(var(--spacing) * 8);width:calc(var(--spacing) * 8)}.icon-2xl{height:calc(var(--spacing) * 10);width:calc(var(--spacing) * 10)}.icon-action{cursor:pointer;color:var(--color-gray-400)}@media(hover:hover){.icon-action:hover{color:var(--color-gray-600)}}.dark .icon-action{color:var(--color-gray-500)}@media(hover:hover){.dark .icon-action:hover{color:var(--color-gray-300)}}.page-container{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 6)}.dark .page-container{background-color:var(--color-gray-900)}.content-area{max-width:var(--container-7xl);margin-inline:auto}.content-panel{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 6)}.dark .content-panel{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.flex-row-center{align-items:center;display:flex}.flex-row-between{justify-content:space-between;align-items:center;display:flex}.flex-row-end{justify-content:flex-end;align-items:center;display:flex}.flex-col-start{flex-direction:column;align-items:flex-start;display:flex}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-center{align-items:center;display:flex}.flex-end{justify-content:flex-end;align-items:center;display:flex}.flex-wrap-center{flex-wrap:wrap;align-items:center;display:flex}.inline-flex-center{align-items:center;display:inline-flex}.row-stack{flex-direction:column;display:flex}:where(.row-stack>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-color:var(--color-gray-200)}:where(.dark .row-stack>:not(:last-child)){border-color:var(--color-gray-700)}.row-stack>*{padding-block:calc(var(--spacing) * 2)}.row-stack>:first-child{padding-top:calc(var(--spacing) * 0)}.row-stack>:last-child{padding-bottom:calc(var(--spacing) * 0)}.stack-flush{gap:calc(var(--spacing) * 0);flex-direction:column;display:flex}.stack-tight{gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.card-stack{margin-top:calc(var(--spacing) * 3);gap:calc(var(--spacing) * 2);flex-direction:column;display:flex}.card-stack-item{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-white);padding:calc(var(--spacing) * 4)}.dark .card-stack-item{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.card-stack-item{flex-direction:column;display:flex}.option-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);overflow:hidden}.dark .option-card{border-color:var(--color-gray-700);background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .option-card{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.option-card>*{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3)}.option-card>*+*{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200)}.dark :is(.option-card>*+*){border-color:var(--color-gray-700)}.alert-info{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-blue-200);background-color:var(--color-blue-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-blue-900);display:flex}.dark .alert-info{border-color:var(--color-blue-800);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .alert-info{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .alert-info{color:var(--color-blue-100)}.alert-warning{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-yellow-200);background-color:var(--color-yellow-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-yellow-900);display:flex}.dark .alert-warning{border-color:var(--color-yellow-800);background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-warning{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .alert-warning{color:var(--color-yellow-100)}.alert-danger{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-red-200);background-color:var(--color-red-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-900);display:flex}.dark .alert-danger{border-color:var(--color-red-800);background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .alert-danger{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .alert-danger{color:var(--color-red-100)}.alert-success{align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-green-200);background-color:var(--color-green-50);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-green-900);display:flex}.dark .alert-success{border-color:var(--color-green-800);background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .alert-success{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .alert-success{color:var(--color-green-100)}.alert-info:has(+.row-stack),.alert-warning:has(+.row-stack),.alert-danger:has(+.row-stack),.alert-success:has(+.row-stack),.alert-error:has(+.row-stack),.alert-info:has(+.toggle-group),.alert-warning:has(+.toggle-group),.alert-danger:has(+.toggle-group),.alert-success:has(+.toggle-group),.alert-error:has(+.toggle-group),.alert-info:has(+.grid-2col-responsive),.alert-warning:has(+.grid-2col-responsive),.alert-danger:has(+.grid-2col-responsive),.alert-success:has(+.grid-2col-responsive),.alert-error:has(+.grid-2col-responsive){margin-bottom:calc(var(--spacing) * 4)}.row-stack>.alert-info,.row-stack>.alert-warning,.row-stack>.alert-danger,.row-stack>.alert-success,.row-stack>.alert-error{padding-top:calc(var(--spacing) * 3);padding-bottom:calc(var(--spacing) * 3)}.row-stack>*+.alert-info,.row-stack>*+.alert-warning,.row-stack>*+.alert-danger,.row-stack>*+.alert-success,.row-stack>*+.alert-error{margin-top:calc(var(--spacing) * 3)}.sub-tab-nav{gap:calc(var(--spacing) * 1);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 6);display:flex}@media(min-width:40rem){.sub-tab-nav{padding-inline:calc(var(--spacing) * 8)}}@media(min-width:64rem){.sub-tab-nav{padding-inline:calc(var(--spacing) * 10)}}.dark .sub-tab-nav{background-color:var(--color-gray-800)}.section-header{margin-bottom:calc(var(--spacing) * 6)}.section-header>h3{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark :is(.section-header>h3){color:var(--color-white)}.section-header>p{margin-top:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark :is(.section-header>p){color:var(--color-gray-400)}.toggle-group{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);overflow:hidden}.dark .toggle-group{border-color:var(--color-gray-700)}.section-header-row{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);background-color:var(--color-gray-50);display:flex}.dark .section-header-row{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .section-header-row{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.section-header-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200)}.dark .section-header-row{border-color:var(--color-gray-700)}.section-header-row{cursor:pointer;-webkit-user-select:none;user-select:none}.list-row-hover{background-color:var(--color-white)}.dark .list-row-hover{background-color:var(--color-gray-800)}@media(hover:hover){.list-row-hover:hover{background-color:var(--color-gray-50)}.dark .list-row-hover:hover{background-color:var(--color-gray-700)}}.list-row-hover{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2.5);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))}.text-hint{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-400)}.dark .text-hint{color:var(--color-gray-500)}.text-info-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-blue-700)}.dark .text-info-soft{color:var(--color-blue-300)}.text-warning-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-yellow-700)}.dark .text-warning-soft{color:var(--color-yellow-300)}.text-danger-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-700)}.dark .text-danger-soft{color:var(--color-red-300)}.text-success-soft{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-green-700)}.dark .text-success-soft{color:var(--color-green-300)}.checkbox-aligned{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);cursor:pointer;border-color:var(--color-gray-300);color:var(--color-gray-900);border-radius:.25rem}.checkbox-aligned:focus{--tw-ring-color:var(--color-gray-500)}.dark .checkbox-aligned{border-color:var(--color-gray-600);background-color:var(--color-gray-800);color:var(--color-blue-400)}.checkbox-aligned{margin-top:calc(var(--spacing) * .5);flex-shrink:0}:where(.divider-list>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-color:var(--color-gray-200)}:where(.dark .divider-list>:not(:last-child)){border-color:var(--color-gray-700)}.modal-footer-buttons{margin-top:calc(var(--spacing) * 6);justify-content:flex-end;gap:calc(var(--spacing) * 3);display:flex}.spinner-sm{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);animation:var(--animate-spin)}.text-required{margin-left:calc(var(--spacing) * 1);color:var(--color-red-500)}.dark .text-required{color:var(--color-red-400)}.form-label-block{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-700);display:block}.dark .form-label-block{color:var(--color-gray-300)}.form-sub-label{margin-bottom:calc(var(--spacing) * 1);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500);display:block}.dark .form-sub-label{color:var(--color-gray-400)}.text-field-hint{margin-top:calc(var(--spacing) * .5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--color-gray-500)}.dark .text-field-hint{color:var(--color-gray-400)}.section-heading-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}.dark .section-heading-md{color:var(--color-white)}.grid-3col-responsive{align-items:flex-start;gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media(min-width:64rem){.grid-3col-responsive{grid-template-columns:repeat(3,minmax(0,1fr))}}.text-error-soft{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-red-700)}.dark .text-error-soft{color:var(--color-red-300)}.text-error-strong{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-red-800)}.dark .text-error-strong{color:var(--color-red-200)}.row-content-indent{padding-left:calc(var(--spacing) * 6)}.flex-start{align-items:flex-start;display:flex}.inline-clickable{cursor:pointer;align-items:center;gap:calc(var(--spacing) * 2);display:inline-flex}.grid-2col{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.empty-state-cell{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 4);text-align:center}.form-error-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-red-500)}.dark .form-error-xs{color:var(--color-red-400)}.text-page-heading{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-gray-900)}.dark .text-page-heading{color:var(--color-white)}.text-table-header{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider);color:var(--color-gray-500);text-transform:uppercase}.dark .text-table-header{color:var(--color-gray-400)}.panel-header-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4)}.dark .panel-header-row{border-color:var(--color-gray-700)}.grid-2col-responsive{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media(min-width:48rem){.grid-2col-responsive{grid-template-columns:repeat(2,minmax(0,1fr))}}.panel-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);padding-block:calc(var(--spacing) * 3)}.dark .panel-header{border-color:var(--color-gray-700)}.gap-sm{gap:calc(var(--spacing) * 2)}.gap-md{gap:calc(var(--spacing) * 4)}.gap-lg{gap:calc(var(--spacing) * 6)}.mb-sm{margin-bottom:calc(var(--spacing) * 2)}.mb-md{margin-bottom:calc(var(--spacing) * 4)}.mb-lg{margin-bottom:calc(var(--spacing) * 6)}.surface{background-color:var(--color-white)}.dark .surface{background-color:var(--color-gray-800)}.surface-alt{background-color:var(--color-gray-50)}.dark .surface-alt{background-color:var(--color-gray-900)}.card-border{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.dark .card-border{border-color:var(--color-gray-700)}.card-border-xl{border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200)}.dark .card-border-xl{border-color:var(--color-gray-700)}.border-light{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300)}.dark .border-light{border-color:var(--color-gray-600)}.disabled-state:disabled{cursor:not-allowed;opacity:.5}.focus-ring:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.dark .focus-ring:focus{--tw-ring-offset-color:var(--color-gray-800)}.focus-ring-inset:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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);--tw-ring-color:var(--color-blue-500);--tw-outline-style:none;--tw-ring-inset:inset;outline-style:none}@media(hover:hover){.hover-bg:hover{background-color:var(--color-gray-100)}.dark .hover-bg:hover{background-color:var(--color-gray-700)}.hover-bg-light:hover{background-color:var(--color-gray-50)}.dark .hover-bg-light:hover{background-color:var(--color-gray-800)}}.btn-base{border-radius:var(--radius-lg);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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:.2s;justify-content:center;align-items:center;transition-duration:.2s;display:inline-flex}.btn-base:disabled{cursor:not-allowed;opacity:.5}.btn-size-sm{padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.btn-size-md{padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.btn-size-lg{padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 3);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.badge-base{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-size-sm{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5)}.badge-size-md{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * 1)}.shadow-card{--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-dropdown{--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)}@media(hover:hover){.shadow-hover:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}}.page-title{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-gray-900)}@media(min-width:40rem){.page-title{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}}.dark .page-title{color:var(--color-white)}.page-description{margin-top:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600)}.dark .page-description{color:var(--color-gray-400)}.page-action-primary{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));background-color:var(--color-blue-600);color:var(--color-white);display:flex}@media(hover:hover){.page-action-primary:hover{background-color:var(--color-blue-700)}}.page-action-secondary{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));background-color:var(--color-gray-200);color:var(--color-gray-900);display:flex}@media(hover:hover){.page-action-secondary:hover{background-color:var(--color-gray-300)}}.dark .page-action-secondary{background-color:var(--color-gray-700);color:var(--color-gray-200)}@media(hover:hover){.dark .page-action-secondary:hover{background-color:var(--color-gray-600)}}.page-action-danger{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));background-color:var(--color-red-600);color:var(--color-white);display:flex}@media(hover:hover){.page-action-danger:hover{background-color:var(--color-red-700)}}.page-action-ghost{align-items:center;gap:calc(var(--spacing) * 2);border-radius:var(--radius-lg);padding:calc(var(--spacing) * 2);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));color:var(--color-gray-500);display:flex}@media(hover:hover){.page-action-ghost:hover{background-color:var(--color-gray-100);color:var(--color-gray-700)}}.dark .page-action-ghost{color:var(--color-gray-400)}@media(hover:hover){.dark .page-action-ghost:hover{background-color:var(--color-gray-800);color:var(--color-gray-200)}}.tab-container{margin-bottom:calc(var(--spacing) * 6);gap:calc(var(--spacing) * 6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);display:flex}.dark .tab-container{border-color:var(--color-gray-700)}.tab-btn{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 1);padding-block:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px;margin-bottom:-1px;display:flex}@media(hover:hover){.tab-btn:hover{color:var(--color-gray-900)}}.dark .tab-btn{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn:hover{color:var(--color-gray-200)}}.tab-btn-active{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 1);padding-block:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);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));border-bottom-width:2px;border-color:var(--color-blue-600);color:var(--color-blue-600);margin-bottom:-1px;display:flex}.dark .tab-btn-active{color:var(--color-blue-400)}.tab-badge{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);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);background-color:var(--color-gray-100);color:var(--color-gray-600);border-radius:3.40282e38px}.dark .tab-badge{background-color:var(--color-gray-700);color:var(--color-gray-400)}.tab-badge-active{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);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);background-color:var(--color-blue-100);color:var(--color-blue-600);border-radius:3.40282e38px}.dark .tab-badge-active{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tab-badge-active{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .tab-badge-active{color:var(--color-blue-400)}.sticky-tab-nav{top:calc(var(--spacing) * 0);z-index:40;margin-inline:calc(var(--spacing) * -6);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 6);position:sticky}.dark .sticky-tab-nav{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-tab-nav-responsive{top:calc(var(--spacing) * 0);z-index:40;margin-inline:calc(var(--spacing) * -4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 4);position:sticky}@media(min-width:40rem){.sticky-tab-nav-responsive{margin-inline:calc(var(--spacing) * -6);padding-inline:calc(var(--spacing) * 6)}}@media(min-width:64rem){.sticky-tab-nav-responsive{margin-inline:calc(var(--spacing) * -8);padding-inline:calc(var(--spacing) * 8)}}.dark .sticky-tab-nav-responsive{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-section-header{top:calc(var(--spacing) * 0);z-index:40;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);position:sticky}.dark .sticky-section-header{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.sticky-footer-buttons{bottom:calc(var(--spacing) * 0);z-index:10;margin-top:calc(var(--spacing) * 6);justify-content:flex-end;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);display:flex;position:sticky}.dark .sticky-footer-buttons{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.sticky-footer-buttons-between{bottom:calc(var(--spacing) * 0);z-index:10;margin-top:calc(var(--spacing) * 6);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);display:flex;position:sticky}.dark .sticky-footer-buttons-between{border-color:var(--color-gray-700);background-color:var(--color-gray-800)}.footer-buttons-between{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-50);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 3);display:flex}.dark .footer-buttons-between{border-color:var(--color-gray-700);background-color:var(--color-gray-900)}.tab-content{padding-top:calc(var(--spacing) * 6)}.admin-page-content{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 6)}.dark .admin-page-content{background-color:var(--color-gray-900)}.admin-page-content-compact{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 4)}.dark .admin-page-content-compact{background-color:var(--color-gray-900)}.admin-page-content-responsive{background-color:var(--color-gray-50);min-height:100vh;padding:calc(var(--spacing) * 4)}@media(min-width:40rem){.admin-page-content-responsive{padding:calc(var(--spacing) * 6)}}@media(min-width:64rem){.admin-page-content-responsive{padding:calc(var(--spacing) * 8)}}.dark .admin-page-content-responsive{background-color:var(--color-gray-900)}.admin-page-content-responsive-fluid{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4)}@media(min-width:40rem){.admin-page-content-responsive-fluid{padding:calc(var(--spacing) * 6)}}@media(min-width:64rem){.admin-page-content-responsive-fluid{padding:calc(var(--spacing) * 8)}}.dark .admin-page-content-responsive-fluid{background-color:var(--color-gray-900)}.admin-page-content-fluid{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 6)}.dark .admin-page-content-fluid{background-color:var(--color-gray-900)}.admin-page-content-fluid-compact{background-color:var(--color-gray-50);padding:calc(var(--spacing) * 4)}.dark .admin-page-content-fluid-compact{background-color:var(--color-gray-900)}.admin-page-content-viewport{background-color:var(--color-gray-50);height:calc(100vh - 64px);padding:calc(var(--spacing) * 6);overflow:hidden}.dark .admin-page-content-viewport{background-color:var(--color-gray-900)}.admin-page-content-viewport-compact{background-color:var(--color-gray-50);height:calc(100vh - 64px);padding:calc(var(--spacing) * 4);overflow:hidden}.dark .admin-page-content-viewport-compact{background-color:var(--color-gray-900)}.bulk-actions-bar{margin-bottom:calc(var(--spacing) * 4);align-items:center;gap:calc(var(--spacing) * 3);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-200);background-color:var(--color-gray-100);padding:calc(var(--spacing) * 3);display:flex}.dark .bulk-actions-bar{border-color:var(--color-gray-600);background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .bulk-actions-bar{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}:where(.admin-sidebar-menu-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}.admin-sidebar-menu-list{padding-inline:calc(var(--spacing) * 4);padding-bottom:calc(var(--spacing) * 4)}.admin-sidebar-item{border-radius:var(--radius-lg);width:100%;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);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));justify-content:space-between;align-items:center;display:flex}.admin-sidebar-subitem{border-radius:var(--radius-lg);width:100%;padding-block:6px;padding-right:calc(var(--spacing) * 3);--tw-font-weight:var(--font-weight-medium);font-size:13px;font-weight:var(--font-weight-medium);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));justify-content:space-between;align-items:center;padding-left:30px;display:flex}.admin-sidebar-item-active{background-color:var(--color-slate-100);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.dark .admin-sidebar-item-active{background-color:var(--color-slate-700);color:var(--color-white)}.admin-sidebar-item-active-parent{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.dark .admin-sidebar-item-active-parent{color:var(--color-white)}.admin-sidebar-item-default{color:var(--color-slate-500)}@media(hover:hover){.admin-sidebar-item-default:hover{background-color:var(--color-slate-50);color:var(--color-slate-900)}}.dark .admin-sidebar-item-default{color:var(--color-slate-400)}@media(hover:hover){.dark .admin-sidebar-item-default:hover{background-color:var(--color-slate-800);color:var(--color-white)}}.admin-sidebar-item-disabled{cursor:default;color:var(--color-slate-400)}.dark .admin-sidebar-item-disabled{color:var(--color-slate-500)}.admin-sidebar-icon-box{flex-shrink:0;justify-content:center;align-items:center;width:18px;height:18px;display:flex}.admin-sidebar-chevron-trail{align-items:center;gap:calc(var(--spacing) * 1);display:flex}.admin-sidebar-item-content{align-items:center;gap:calc(var(--spacing) * 2);display:flex}.admin-sidebar-module-icon{margin-right:calc(var(--spacing) * 2);height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);color:var(--color-slate-400);opacity:.6}.dark .admin-sidebar-module-icon{color:var(--color-slate-500)}.admin-sidebar-tree-line-vertical{top:calc(var(--spacing) * 0);bottom:calc(var(--spacing) * 0);z-index:0;border-left-style:var(--tw-border-style);border-left-width:1px;border-color:var(--color-slate-300);position:absolute;left:12px}.dark .admin-sidebar-tree-line-vertical{border-color:var(--color-slate-600)}.admin-sidebar-tree-line-curve{top:calc(var(--spacing) * 0);z-index:10;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-left-style:var(--tw-border-style);width:14px;height:50%;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));border-left-width:1px;border-color:var(--color-slate-300);border-bottom-left-radius:10px;position:absolute;left:12px}@media(hover:hover){.admin-sidebar-tree-line-curve:is(:where(.group):hover *){border-color:var(--color-blue-400)}}.dark .admin-sidebar-tree-line-curve{border-color:var(--color-slate-600)}@media(hover:hover){.dark .admin-sidebar-tree-line-curve:is(:where(.group):hover *){border-color:var(--color-blue-500)}}.admin-sidebar-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 4);justify-content:space-between;align-items:center;display:flex}.dark .admin-sidebar-header{border-color:var(--color-slate-700)}.admin-sidebar-collapse-btn{border-radius:var(--radius-md);padding:calc(var(--spacing) * 2);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.admin-sidebar-collapse-btn:hover{background-color:var(--color-slate-100)}.dark .admin-sidebar-collapse-btn:hover{background-color:var(--color-slate-700)}}.tab-nav-default{gap:calc(var(--spacing) * 2);display:flex}.tab-nav-underline{gap:calc(var(--spacing) * 0);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-gray-200);display:flex}.dark .tab-nav-underline{border-color:var(--color-gray-700)}.tab-btn-base{align-items:center;gap:calc(var(--spacing) * 2);padding-inline:calc(var(--spacing) * 4);padding-block:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:flex}.tab-btn-default{border-bottom-style:var(--tw-border-style);color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px}@media(hover:hover){.tab-btn-default:hover{background-color:var(--color-gray-50);color:var(--color-gray-900)}}.dark .tab-btn-default{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-default:hover{background-color:var(--color-gray-700);color:var(--color-gray-200)}}.tab-btn-default-active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-blue-600);background-color:var(--color-blue-50);color:var(--color-blue-600)}.dark .tab-btn-default-active{border-color:var(--color-blue-400);background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .tab-btn-default-active{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .tab-btn-default-active{color:var(--color-blue-400)}.tab-btn-underline{border-bottom-style:var(--tw-border-style);color:var(--color-gray-600);border-color:#0000;border-bottom-width:2px}@media(hover:hover){.tab-btn-underline:hover{border-color:var(--color-gray-300);color:var(--color-gray-900)}}.dark .tab-btn-underline{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-underline:hover{border-color:var(--color-gray-600);color:var(--color-gray-200)}}.tab-btn-underline-active{border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-blue-600);color:var(--color-blue-600)}.dark .tab-btn-underline-active{border-color:var(--color-blue-400);color:var(--color-blue-400)}.tab-btn-pills{border-radius:var(--radius-lg);color:var(--color-gray-600)}@media(hover:hover){.tab-btn-pills:hover{background-color:var(--color-gray-100)}}.dark .tab-btn-pills{color:var(--color-gray-400)}@media(hover:hover){.dark .tab-btn-pills:hover{background-color:var(--color-gray-700)}}.tab-btn-pills-active{border-radius:var(--radius-lg);background-color:var(--color-blue-600);color:var(--color-white)}.dark .tab-btn-pills-active{background-color:var(--color-blue-500)}.tab-btn-disabled{cursor:not-allowed;color:var(--color-gray-400);opacity:.5}.dark .tab-btn-disabled{color:var(--color-gray-600)}.toggle-container{align-items:flex-start;column-gap:calc(var(--spacing) * 2);flex-wrap:wrap;width:auto;display:inline-flex}.toggle-switch-wrapper{flex-shrink:0;align-items:center;display:inline-flex;position:relative}.toggle-switch-track{background-color:var(--color-gray-200);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));border-radius:3.40282e38px;position:relative}.toggle-switch-track:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + 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);--tw-ring-color:var(--color-blue-300);--tw-outline-style:none;outline-style:none}.toggle-switch-track:is(:where(.peer):checked~*){background-color:var(--color-blue-600)}.dark .toggle-switch-track{background-color:var(--color-gray-700)}.dark .toggle-switch-track:is(:where(.peer):focus~*){--tw-ring-color:var(--color-blue-800)}.dark .toggle-switch-track:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.toggle-switch-track:after{top:calc(var(--spacing) * .5);left:calc(var(--spacing) * .5);--tw-content:"";border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-gray-300);background-color:var(--color-white);content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:3.40282e38px;position:absolute}.dark .toggle-switch-track:after{content:var(--tw-content);border-color:var(--color-gray-600)}.toggle-switch-track:is(:where(.peer):checked~*):after{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y);content:var(--tw-content);border-color:var(--color-white)}.toggle-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-gray-900)}.dark .toggle-label{color:var(--color-white)}.toggle-description{margin-top:calc(var(--spacing) * 1);width:100%;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-gray-500)}.dark .toggle-description{color:var(--color-gray-400)}.admin-footer{margin-top:calc(var(--spacing) * 6);padding-inline:calc(var(--spacing) * 6);padding-block:calc(var(--spacing) * 4)}.admin-footer-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 4);flex-direction:column;display:flex}@media(min-width:48rem){.admin-footer-row{flex-direction:row}}.admin-footer-group{align-items:center;gap:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);display:flex}.dark .admin-footer-group{color:var(--color-gray-400)}.admin-footer-link-hover{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))}@media(hover:hover){.admin-footer-link-hover:hover{color:var(--color-blue-600)}.dark .admin-footer-link-hover:hover{color:var(--color-blue-400)}}.admin-footer-link{align-items:center;gap:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-600);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));display:flex}@media(hover:hover){.admin-footer-link:hover{color:var(--color-blue-600)}}.dark .admin-footer-link{color:var(--color-gray-400)}@media(hover:hover){.dark .admin-footer-link:hover{color:var(--color-blue-400)}}.responsive-grid{gap:calc(var(--spacing) * 4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.grid-cols-1-lg-3{gap:calc(var(--spacing) * 6);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.col-span-1-lg,.col-span-2-lg{grid-column:span 1/span 1}.modal-description{margin-top:calc(var(--spacing) * 1);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-gray-500)}.dark .modal-description{color:var(--color-gray-400)}.toast-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.visible\!{visibility:visible!important}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing) * 0)}.inset-1{inset:calc(var(--spacing) * 1)}.inset-2{inset:calc(var(--spacing) * 2)}.inset-3{inset:calc(var(--spacing) * 3)}.inset-4{inset:calc(var(--spacing) * 4)}.inset-5{inset:calc(var(--spacing) * 5)}.inset-6{inset:calc(var(--spacing) * 6)}.inset-8{inset:calc(var(--spacing) * 8)}.inset-auto{inset:auto}.inset-x-0{inset-inline:calc(var(--spacing) * 0)}.inset-x-1{inset-inline:calc(var(--spacing) * 1)}.inset-x-2{inset-inline:calc(var(--spacing) * 2)}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.inset-x-4{inset-inline:calc(var(--spacing) * 4)}.inset-x-auto{inset-inline:auto}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.inset-y-1{inset-block:calc(var(--spacing) * 1)}.inset-y-2{inset-block:calc(var(--spacing) * 2)}.inset-y-3{inset-block:calc(var(--spacing) * 3)}.inset-y-4{inset-block:calc(var(--spacing) * 4)}.inset-y-auto{inset-block:auto}.-top-1{top:calc(var(--spacing) * -1)}.-top-2{top:calc(var(--spacing) * -2)}.-top-3{top:calc(var(--spacing) * -3)}.-top-4{top:calc(var(--spacing) * -4)}.top-0{top:calc(var(--spacing) * 0)}.top-1{top:calc(var(--spacing) * 1)}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-1\/2{top:50%}.top-1\/3{top:33.3333%}.top-1\/4{top:25%}.top-2{top:calc(var(--spacing) * 2)}.top-2\/3{top:66.6667%}.top-3{top:calc(var(--spacing) * 3)}.top-3\/4{top:75%}.top-4{top:calc(var(--spacing) * 4)}.top-5{top:calc(var(--spacing) * 5)}.top-6{top:calc(var(--spacing) * 6)}.top-8{top:calc(var(--spacing) * 8)}.top-10{top:calc(var(--spacing) * 10)}.top-12{top:calc(var(--spacing) * 12)}.top-16{top:calc(var(--spacing) * 16)}.top-auto{top:auto}.top-full{top:100%}.-right-1{right:calc(var(--spacing) * -1)}.-right-2{right:calc(var(--spacing) * -2)}.-right-3{right:calc(var(--spacing) * -3)}.-right-4{right:calc(var(--spacing) * -4)}.right-0{right:calc(var(--spacing) * 0)}.right-1{right:calc(var(--spacing) * 1)}.right-1\/2{right:50%}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.right-4{right:calc(var(--spacing) * 4)}.right-5{right:calc(var(--spacing) * 5)}.right-6{right:calc(var(--spacing) * 6)}.right-8{right:calc(var(--spacing) * 8)}.right-10{right:calc(var(--spacing) * 10)}.right-12{right:calc(var(--spacing) * 12)}.right-16{right:calc(var(--spacing) * 16)}.right-auto{right:auto}.right-full{right:100%}.-bottom-1{bottom:calc(var(--spacing) * -1)}.-bottom-2{bottom:calc(var(--spacing) * -2)}.-bottom-3{bottom:calc(var(--spacing) * -3)}.-bottom-4{bottom:calc(var(--spacing) * -4)}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-1{bottom:calc(var(--spacing) * 1)}.bottom-1\/2{bottom:50%}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-3{bottom:calc(var(--spacing) * 3)}.bottom-4{bottom:calc(var(--spacing) * 4)}.bottom-5{bottom:calc(var(--spacing) * 5)}.bottom-6{bottom:calc(var(--spacing) * 6)}.bottom-8{bottom:calc(var(--spacing) * 8)}.bottom-10{bottom:calc(var(--spacing) * 10)}.bottom-12{bottom:calc(var(--spacing) * 12)}.bottom-16{bottom:calc(var(--spacing) * 16)}.bottom-auto{bottom:auto}.bottom-full{bottom:100%}.-left-1{left:calc(var(--spacing) * -1)}.-left-2{left:calc(var(--spacing) * -2)}.-left-3{left:calc(var(--spacing) * -3)}.-left-4{left:calc(var(--spacing) * -4)}.left-0{left:calc(var(--spacing) * 0)}.left-1{left:calc(var(--spacing) * 1)}.left-1\.5{left:calc(var(--spacing) * 1.5)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing) * 2)}.left-3{left:calc(var(--spacing) * 3)}.left-4{left:calc(var(--spacing) * 4)}.left-5{left:calc(var(--spacing) * 5)}.left-6{left:calc(var(--spacing) * 6)}.left-8{left:calc(var(--spacing) * 8)}.left-10{left:calc(var(--spacing) * 10)}.left-12{left:calc(var(--spacing) * 12)}.left-16{left:calc(var(--spacing) * 16)}.left-auto{left:auto}.left-full{left:100%}.isolate{isolation:isolate}.isolation-auto{isolation:auto}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[999\]{z-index:999}.z-\[9999\]{z-index:9999}.z-auto{z-index:auto}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-first{order:-9999}.order-last{order:9999}.order-none{order:0}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-4{grid-column:span 4/span 4}.col-span-5{grid-column:span 5/span 5}.col-span-6{grid-column:span 6/span 6}.col-span-7{grid-column:span 7/span 7}.col-span-8{grid-column:span 8/span 8}.col-span-9{grid-column:span 9/span 9}.col-span-10{grid-column:span 10/span 10}.col-span-11{grid-column:span 11/span 11}.col-span-12{grid-column:span 12/span 12}.col-span-full{grid-column:1/-1}.col-start-1{grid-column-start:1}.col-start-2{grid-column-start:2}.col-start-3{grid-column-start:3}.col-start-4{grid-column-start:4}.col-start-5{grid-column-start:5}.col-start-6{grid-column-start:6}.col-start-7{grid-column-start:7}.col-start-8{grid-column-start:8}.col-start-9{grid-column-start:9}.col-start-10{grid-column-start:10}.col-start-11{grid-column-start:11}.col-start-12{grid-column-start:12}.col-start-13{grid-column-start:13}.col-end-1{grid-column-end:1}.col-end-2{grid-column-end:2}.col-end-3{grid-column-end:3}.col-end-4{grid-column-end:4}.col-end-5{grid-column-end:5}.col-end-6{grid-column-end:6}.col-end-7{grid-column-end:7}.col-end-8{grid-column-end:8}.col-end-9{grid-column-end:9}.col-end-10{grid-column-end:10}.col-end-11{grid-column-end:11}.col-end-12{grid-column-end:12}.col-end-13{grid-column-end:13}.row-span-1{grid-row:span 1/span 1}.row-span-2{grid-row:span 2/span 2}.row-span-3{grid-row:span 3/span 3}.row-span-4{grid-row:span 4/span 4}.row-span-5{grid-row:span 5/span 5}.row-span-6{grid-row:span 6/span 6}.row-span-full{grid-row:1/-1}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.row-start-3{grid-row-start:3}.row-start-4{grid-row-start:4}.row-start-5{grid-row-start:5}.row-start-6{grid-row-start:6}.row-start-7{grid-row-start:7}.row-end-1{grid-row-end:1}.row-end-2{grid-row-end:2}.row-end-3{grid-row-end:3}.row-end-4{grid-row-end:4}.row-end-5{grid-row-end:5}.row-end-6{grid-row-end:6}.row-end-7{grid-row-end:7}.float-end{float:inline-end}.float-left{float:left}.float-none{float:none}.float-right{float:right}.float-start{float:inline-start}.clear-both{clear:both}.clear-end{clear:inline-end}.clear-left{clear:left}.clear-none{clear:none}.clear-right{clear:right}.clear-start{clear:inline-start}.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)!important}.\!m-1{margin:calc(var(--spacing) * 1)!important}.\!m-2{margin:calc(var(--spacing) * 2)!important}.\!m-3{margin:calc(var(--spacing) * 3)!important}.\!m-4{margin:calc(var(--spacing) * 4)!important}.\!m-5{margin:calc(var(--spacing) * 5)!important}.\!m-6{margin:calc(var(--spacing) * 6)!important}.\!m-8{margin:calc(var(--spacing) * 8)!important}.\!m-10{margin:calc(var(--spacing) * 10)!important}.\!m-12{margin:calc(var(--spacing) * 12)!important}.\!m-16{margin:calc(var(--spacing) * 16)!important}.\!m-auto{margin:auto!important}.-m-1{margin:calc(var(--spacing) * -1)}.-m-2{margin:calc(var(--spacing) * -2)}.-m-3{margin:calc(var(--spacing) * -3)}.-m-4{margin:calc(var(--spacing) * -4)}.-m-5{margin:calc(var(--spacing) * -5)}.-m-6{margin:calc(var(--spacing) * -6)}.-m-8{margin:calc(var(--spacing) * -8)}.m-0{margin:calc(var(--spacing) * 0)}.m-1{margin:calc(var(--spacing) * 1)}.m-2{margin:calc(var(--spacing) * 2)}.m-3{margin:calc(var(--spacing) * 3)}.m-4{margin:calc(var(--spacing) * 4)}.m-5{margin:calc(var(--spacing) * 5)}.m-6{margin:calc(var(--spacing) * 6)}.m-8{margin:calc(var(--spacing) * 8)}.m-10{margin:calc(var(--spacing) * 10)}.m-12{margin:calc(var(--spacing) * 12)}.m-16{margin:calc(var(--spacing) * 16)}.m-auto{margin:auto}.\!mx-0{margin-inline:calc(var(--spacing) * 0)!important}.\!mx-1{margin-inline:calc(var(--spacing) * 1)!important}.\!mx-2{margin-inline:calc(var(--spacing) * 2)!important}.\!mx-3{margin-inline:calc(var(--spacing) * 3)!important}.\!mx-4{margin-inline:calc(var(--spacing) * 4)!important}.\!mx-5{margin-inline:calc(var(--spacing) * 5)!important}.\!mx-6{margin-inline:calc(var(--spacing) * 6)!important}.\!mx-8{margin-inline:calc(var(--spacing) * 8)!important}.\!mx-10{margin-inline:calc(var(--spacing) * 10)!important}.\!mx-12{margin-inline:calc(var(--spacing) * 12)!important}.\!mx-16{margin-inline:calc(var(--spacing) * 16)!important}.\!mx-auto{margin-inline:auto!important}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.-mx-2{margin-inline:calc(var(--spacing) * -2)}.-mx-3{margin-inline:calc(var(--spacing) * -3)}.-mx-4{margin-inline:calc(var(--spacing) * -4)}.-mx-5{margin-inline:calc(var(--spacing) * -5)}.-mx-6{margin-inline:calc(var(--spacing) * -6)}.-mx-8{margin-inline:calc(var(--spacing) * -8)}.mx-0{margin-inline:calc(var(--spacing) * 0)}.mx-1{margin-inline:calc(var(--spacing) * 1)}.mx-1\.5{margin-inline:calc(var(--spacing) * 1.5)}.mx-2{margin-inline:calc(var(--spacing) * 2)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-4{margin-inline:calc(var(--spacing) * 4)}.mx-5{margin-inline:calc(var(--spacing) * 5)}.mx-6{margin-inline:calc(var(--spacing) * 6)}.mx-8{margin-inline:calc(var(--spacing) * 8)}.mx-10{margin-inline:calc(var(--spacing) * 10)}.mx-12{margin-inline:calc(var(--spacing) * 12)}.mx-16{margin-inline:calc(var(--spacing) * 16)}.mx-auto{margin-inline:auto}.\!my-0{margin-block:calc(var(--spacing) * 0)!important}.\!my-1{margin-block:calc(var(--spacing) * 1)!important}.\!my-2{margin-block:calc(var(--spacing) * 2)!important}.\!my-3{margin-block:calc(var(--spacing) * 3)!important}.\!my-4{margin-block:calc(var(--spacing) * 4)!important}.\!my-5{margin-block:calc(var(--spacing) * 5)!important}.\!my-6{margin-block:calc(var(--spacing) * 6)!important}.\!my-8{margin-block:calc(var(--spacing) * 8)!important}.\!my-10{margin-block:calc(var(--spacing) * 10)!important}.\!my-12{margin-block:calc(var(--spacing) * 12)!important}.\!my-16{margin-block:calc(var(--spacing) * 16)!important}.\!my-auto{margin-block:auto!important}.-my-1{margin-block:calc(var(--spacing) * -1)}.-my-2{margin-block:calc(var(--spacing) * -2)}.-my-3{margin-block:calc(var(--spacing) * -3)}.-my-4{margin-block:calc(var(--spacing) * -4)}.-my-5{margin-block:calc(var(--spacing) * -5)}.-my-6{margin-block:calc(var(--spacing) * -6)}.-my-8{margin-block:calc(var(--spacing) * -8)}.my-0{margin-block:calc(var(--spacing) * 0)}.my-1{margin-block:calc(var(--spacing) * 1)}.my-2{margin-block:calc(var(--spacing) * 2)}.my-3{margin-block:calc(var(--spacing) * 3)}.my-4{margin-block:calc(var(--spacing) * 4)}.my-5{margin-block:calc(var(--spacing) * 5)}.my-6{margin-block:calc(var(--spacing) * 6)}.my-8{margin-block:calc(var(--spacing) * 8)}.my-10{margin-block:calc(var(--spacing) * 10)}.my-12{margin-block:calc(var(--spacing) * 12)}.my-16{margin-block:calc(var(--spacing) * 16)}.my-auto{margin-block:auto}.-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-5{margin-top:calc(var(--spacing) * -5)}.-mt-6{margin-top:calc(var(--spacing) * -6)}.-mt-8{margin-top:calc(var(--spacing) * -8)}.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-1\.5{margin-top:calc(var(--spacing) * 1.5)}.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-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-16{margin-top:calc(var(--spacing) * 16)}.mt-auto{margin-top:auto}.-mr-1{margin-right:calc(var(--spacing) * -1)}.-mr-2{margin-right:calc(var(--spacing) * -2)}.-mr-3{margin-right:calc(var(--spacing) * -3)}.-mr-4{margin-right:calc(var(--spacing) * -4)}.-mr-5{margin-right:calc(var(--spacing) * -5)}.-mr-6{margin-right:calc(var(--spacing) * -6)}.-mr-8{margin-right:calc(var(--spacing) * -8)}.mr-0{margin-right:calc(var(--spacing) * 0)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mr-4{margin-right:calc(var(--spacing) * 4)}.mr-5{margin-right:calc(var(--spacing) * 5)}.mr-6{margin-right:calc(var(--spacing) * 6)}.mr-8{margin-right:calc(var(--spacing) * 8)}.mr-10{margin-right:calc(var(--spacing) * 10)}.mr-12{margin-right:calc(var(--spacing) * 12)}.mr-16{margin-right:calc(var(--spacing) * 16)}.mr-auto{margin-right:auto}.-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-5{margin-bottom:calc(var(--spacing) * -5)}.-mb-6{margin-bottom:calc(var(--spacing) * -6)}.-mb-8{margin-bottom:calc(var(--spacing) * -8)}.-mb-px{margin-bottom:-1px}.mb-0{margin-bottom:calc(var(--spacing) * 0)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.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-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.mb-12{margin-bottom:calc(var(--spacing) * 12)}.mb-16{margin-bottom:calc(var(--spacing) * 16)}.mb-auto{margin-bottom:auto}.-ml-1{margin-left:calc(var(--spacing) * -1)}.-ml-2{margin-left:calc(var(--spacing) * -2)}.-ml-3{margin-left:calc(var(--spacing) * -3)}.-ml-4{margin-left:calc(var(--spacing) * -4)}.-ml-5{margin-left:calc(var(--spacing) * -5)}.-ml-6{margin-left:calc(var(--spacing) * -6)}.-ml-8{margin-left:calc(var(--spacing) * -8)}.ml-0{margin-left:calc(var(--spacing) * 0)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-3{margin-left:calc(var(--spacing) * 3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-5{margin-left:calc(var(--spacing) * 5)}.ml-6{margin-left:calc(var(--spacing) * 6)}.ml-8{margin-left:calc(var(--spacing) * 8)}.ml-10{margin-left:calc(var(--spacing) * 10)}.ml-12{margin-left:calc(var(--spacing) * 12)}.ml-16{margin-left:calc(var(--spacing) * 16)}.ml-auto{margin-left:auto}.box-border{box-sizing:border-box}.box-content{box-sizing:content-box}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.\!block{display:block!important}.\!flex{display:flex!important}.\!grid{display:grid!important}.\!hidden{display:none!important}.\!inline{display:inline!important}.\!inline-block{display:inline-block!important}.\!inline-flex{display:inline-flex!important}.block{display:block}.contents{display:contents}.flex{display:flex}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.inline-table{display:inline-table}.list-item{display:list-item}.table{display:table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-column{display:table-column}.table-column-group{display:table-column-group}.table-footer-group{display:table-footer-group}.table-header-group{display:table-header-group}.table-row{display:table-row}.table-row-group{display:table-row-group}.aspect-auto{aspect-ratio:auto}.aspect-square{aspect-ratio:1}.aspect-video{aspect-ratio:var(--aspect-video)}.\!h-0{height:calc(var(--spacing) * 0)!important}.\!h-1{height:calc(var(--spacing) * 1)!important}.\!h-2{height:calc(var(--spacing) * 2)!important}.\!h-3{height:calc(var(--spacing) * 3)!important}.\!h-4{height:calc(var(--spacing) * 4)!important}.\!h-5{height:calc(var(--spacing) * 5)!important}.\!h-6{height:calc(var(--spacing) * 6)!important}.\!h-8{height:calc(var(--spacing) * 8)!important}.\!h-10{height:calc(var(--spacing) * 10)!important}.\!h-12{height:calc(var(--spacing) * 12)!important}.\!h-16{height:calc(var(--spacing) * 16)!important}.\!h-20{height:calc(var(--spacing) * 20)!important}.\!h-24{height:calc(var(--spacing) * 24)!important}.\!h-32{height:calc(var(--spacing) * 32)!important}.\!h-40{height:calc(var(--spacing) * 40)!important}.\!h-48{height:calc(var(--spacing) * 48)!important}.\!h-56{height:calc(var(--spacing) * 56)!important}.\!h-64{height:calc(var(--spacing) * 64)!important}.\!h-72{height:calc(var(--spacing) * 72)!important}.\!h-80{height:calc(var(--spacing) * 80)!important}.\!h-96{height:calc(var(--spacing) * 96)!important}.\!h-auto{height:auto!important}.\!h-fit{height:fit-content!important}.\!h-full{height:100%!important}.\!h-max{height:max-content!important}.\!h-min{height:min-content!important}.\!h-px{height:1px!important}.\!h-screen{height:100vh!important}.h-0{height:calc(var(--spacing) * 0)}.h-1{height:calc(var(--spacing) * 1)}.h-1\/2{height:50%}.h-1\/3{height:33.3333%}.h-1\/4{height:25%}.h-1\/5{height:20%}.h-1\/6{height:16.6667%}.h-2{height:calc(var(--spacing) * 2)}.h-2\/3{height:66.6667%}.h-2\/4{height:50%}.h-2\/5{height:40%}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-3\/4{height:75%}.h-3\/5{height:60%}.h-4{height:calc(var(--spacing) * 4)}.h-4\/5{height:80%}.h-5{height:calc(var(--spacing) * 5)}.h-5\/6{height:83.3333%}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-20{height:calc(var(--spacing) * 20)}.h-24{height:calc(var(--spacing) * 24)}.h-32{height:calc(var(--spacing) * 32)}.h-40{height:calc(var(--spacing) * 40)}.h-48{height:calc(var(--spacing) * 48)}.h-56{height:calc(var(--spacing) * 56)}.h-64{height:calc(var(--spacing) * 64)}.h-72{height:calc(var(--spacing) * 72)}.h-80{height:calc(var(--spacing) * 80)}.h-96{height:calc(var(--spacing) * 96)}.h-\[42px\]{height:42px}.h-auto{height:auto}.h-fit{height:fit-content}.h-full{height:100%}.h-max{height:max-content}.h-min{height:min-content}.h-px{height:1px}.h-screen{height:100vh}.max-h-0{max-height:calc(var(--spacing) * 0)}.max-h-20{max-height:calc(var(--spacing) * 20)}.max-h-32{max-height:calc(var(--spacing) * 32)}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-56{max-height:calc(var(--spacing) * 56)}.max-h-60{max-height:calc(var(--spacing) * 60)}.max-h-64{max-height:calc(var(--spacing) * 64)}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[400px\]{max-height:400px}.max-h-\[500px\]{max-height:500px}.max-h-\[calc\(90vh-140px\)\]{max-height:calc(90vh - 140px)}.max-h-\[calc\(100vh-320px\)\]{max-height:calc(100vh - 320px)}.max-h-fit{max-height:fit-content}.max-h-full{max-height:100%}.max-h-max{max-height:max-content}.max-h-min{max-height:min-content}.max-h-none{max-height:none}.max-h-screen{max-height:100vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-12{min-height:calc(var(--spacing) * 12)}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-20{min-height:calc(var(--spacing) * 20)}.min-h-24{min-height:calc(var(--spacing) * 24)}.min-h-32{min-height:calc(var(--spacing) * 32)}.min-h-40{min-height:calc(var(--spacing) * 40)}.min-h-48{min-height:calc(var(--spacing) * 48)}.min-h-56{min-height:calc(var(--spacing) * 56)}.min-h-64{min-height:calc(var(--spacing) * 64)}.min-h-\[40px\]{min-height:40px}.min-h-\[42px\]{min-height:42px}.min-h-\[60px\]{min-height:60px}.min-h-\[60vh\]{min-height:60vh}.min-h-\[80px\]{min-height:80px}.min-h-\[200px\]{min-height:200px}.min-h-\[400px\]{min-height:400px}.min-h-fit{min-height:fit-content}.min-h-full{min-height:100%}.min-h-max{min-height:max-content}.min-h-min{min-height:min-content}.min-h-screen{min-height:100vh}.\!w-0{width:calc(var(--spacing) * 0)!important}.\!w-1{width:calc(var(--spacing) * 1)!important}.\!w-1\/2{width:50%!important}.\!w-1\/3{width:33.3333%!important}.\!w-1\/4{width:25%!important}.\!w-1\/5{width:20%!important}.\!w-1\/6{width:16.6667%!important}.\!w-2{width:calc(var(--spacing) * 2)!important}.\!w-2\/3{width:66.6667%!important}.\!w-2\/4{width:50%!important}.\!w-2\/5{width:40%!important}.\!w-3{width:calc(var(--spacing) * 3)!important}.\!w-3\/4{width:75%!important}.\!w-3\/5{width:60%!important}.\!w-4{width:calc(var(--spacing) * 4)!important}.\!w-4\/5{width:80%!important}.\!w-5{width:calc(var(--spacing) * 5)!important}.\!w-5\/6{width:83.3333%!important}.\!w-6{width:calc(var(--spacing) * 6)!important}.\!w-8{width:calc(var(--spacing) * 8)!important}.\!w-10{width:calc(var(--spacing) * 10)!important}.\!w-12{width:calc(var(--spacing) * 12)!important}.\!w-16{width:calc(var(--spacing) * 16)!important}.\!w-20{width:calc(var(--spacing) * 20)!important}.\!w-24{width:calc(var(--spacing) * 24)!important}.\!w-32{width:calc(var(--spacing) * 32)!important}.\!w-40{width:calc(var(--spacing) * 40)!important}.\!w-48{width:calc(var(--spacing) * 48)!important}.\!w-52{width:calc(var(--spacing) * 52)!important}.\!w-56{width:calc(var(--spacing) * 56)!important}.\!w-60{width:calc(var(--spacing) * 60)!important}.\!w-64{width:calc(var(--spacing) * 64)!important}.\!w-72{width:calc(var(--spacing) * 72)!important}.\!w-80{width:calc(var(--spacing) * 80)!important}.\!w-96{width:calc(var(--spacing) * 96)!important}.\!w-auto{width:auto!important}.\!w-fit{width:fit-content!important}.\!w-full{width:100%!important}.\!w-max{width:max-content!important}.\!w-min{width:min-content!important}.\!w-px{width:1px!important}.\!w-screen{width:100vw!important}.w-0{width:calc(var(--spacing) * 0)}.w-1{width:calc(var(--spacing) * 1)}.w-1\/2{width:50%}.w-1\/3{width:33.3333%}.w-1\/4{width:25%}.w-1\/5{width:20%}.w-1\/6{width:16.6667%}.w-2{width:calc(var(--spacing) * 2)}.w-2\/3{width:66.6667%}.w-2\/4{width:50%}.w-2\/5{width:40%}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-3\/4{width:75%}.w-3\/5{width:60%}.w-4{width:calc(var(--spacing) * 4)}.w-4\/5{width:80%}.w-5{width:calc(var(--spacing) * 5)}.w-5\/6{width:83.3333%}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-20{width:calc(var(--spacing) * 20)}.w-24{width:calc(var(--spacing) * 24)}.w-28{width:calc(var(--spacing) * 28)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-40{width:calc(var(--spacing) * 40)}.w-48{width:calc(var(--spacing) * 48)}.w-52{width:calc(var(--spacing) * 52)}.w-56{width:calc(var(--spacing) * 56)}.w-60{width:calc(var(--spacing) * 60)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-80{width:calc(var(--spacing) * 80)}.w-96{width:calc(var(--spacing) * 96)}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.w-max{width:max-content}.w-min{width:min-content}.w-px{width:1px}.w-screen{width:100vw}.\!max-w-0{max-width:calc(var(--spacing) * 0)!important}.\!max-w-2xl{max-width:var(--container-2xl)!important}.\!max-w-3xl{max-width:var(--container-3xl)!important}.\!max-w-4xl{max-width:var(--container-4xl)!important}.\!max-w-full{max-width:100%!important}.\!max-w-lg{max-width:var(--container-lg)!important}.\!max-w-md{max-width:var(--container-md)!important}.\!max-w-none{max-width:none!important}.\!max-w-sm{max-width:var(--container-sm)!important}.\!max-w-xl{max-width:var(--container-xl)!important}.\!max-w-xs{max-width:var(--container-xs)!important}.max-w-0{max-width:calc(var(--spacing) * 0)}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-\[200px\]{max-width:200px}.max-w-\[calc\(100vw-1rem\)\]{max-width:calc(100vw - 1rem)}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-fit{max-width:fit-content}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-max{max-width:max-content}.max-w-md{max-width:var(--container-md)}.max-w-min{max-width:min-content}.max-w-none{max-width:none}.max-w-prose{max-width:65ch}.max-w-screen-2xl{max-width:var(--breakpoint-2xl)}.max-w-screen-lg{max-width:var(--breakpoint-lg)}.max-w-screen-md{max-width:var(--breakpoint-md)}.max-w-screen-sm{max-width:var(--breakpoint-sm)}.max-w-screen-xl{max-width:var(--breakpoint-xl)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.\!min-w-0{min-width:calc(var(--spacing) * 0)!important}.\!min-w-4{min-width:calc(var(--spacing) * 4)!important}.\!min-w-8{min-width:calc(var(--spacing) * 8)!important}.\!min-w-12{min-width:calc(var(--spacing) * 12)!important}.\!min-w-16{min-width:calc(var(--spacing) * 16)!important}.\!min-w-20{min-width:calc(var(--spacing) * 20)!important}.\!min-w-24{min-width:calc(var(--spacing) * 24)!important}.\!min-w-32{min-width:calc(var(--spacing) * 32)!important}.\!min-w-40{min-width:calc(var(--spacing) * 40)!important}.\!min-w-48{min-width:calc(var(--spacing) * 48)!important}.\!min-w-56{min-width:calc(var(--spacing) * 56)!important}.\!min-w-64{min-width:calc(var(--spacing) * 64)!important}.\!min-w-fit{min-width:fit-content!important}.\!min-w-full{min-width:100%!important}.\!min-w-max{min-width:max-content!important}.\!min-w-min{min-width:min-content!important}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[20px\]{min-width:20px}.min-w-\[80px\]{min-width:80px}.min-w-\[120px\]{min-width:120px}.min-w-\[200px\]{min-width:200px}.min-w-\[280px\]{min-width:280px}.min-w-\[300px\]{min-width:300px}.min-w-fit{min-width:fit-content}.min-w-full{min-width:100%}.min-w-max{min-width:max-content}.min-w-min{min-width:min-content}.\!flex-1{flex:1!important}.\!flex-auto{flex:auto!important}.\!flex-initial{flex:0 auto!important}.\!flex-none{flex:none!important}.flex-1{flex:1}.flex-auto{flex:auto}.flex-initial{flex:0 auto}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-0{flex-basis:calc(var(--spacing) * 0)}.basis-1{flex-basis:calc(var(--spacing) * 1)}.basis-1\/2{flex-basis:50%}.basis-1\/3{flex-basis:33.3333%}.basis-1\/4{flex-basis:25%}.basis-2{flex-basis:calc(var(--spacing) * 2)}.basis-2\/3{flex-basis:66.6667%}.basis-3\/4{flex-basis:75%}.basis-auto{flex-basis:auto}.basis-full{flex-basis:100%}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.caption-bottom{caption-side:bottom}.caption-top{caption-side:top}.border-collapse{border-collapse:collapse}.border-separate{border-collapse:separate}.border-spacing-0{--tw-border-spacing-x:calc(var(--spacing) * 0);--tw-border-spacing-y:calc(var(--spacing) * 0);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-1{--tw-border-spacing-x:calc(var(--spacing) * 1);--tw-border-spacing-y:calc(var(--spacing) * 1);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-2{--tw-border-spacing-x:calc(var(--spacing) * 2);--tw-border-spacing-y:calc(var(--spacing) * 2);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-3{--tw-border-spacing-x:calc(var(--spacing) * 3);--tw-border-spacing-y:calc(var(--spacing) * 3);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-4{--tw-border-spacing-x:calc(var(--spacing) * 4);--tw-border-spacing-y:calc(var(--spacing) * 4);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-5{--tw-border-spacing-x:calc(var(--spacing) * 5);--tw-border-spacing-y:calc(var(--spacing) * 5);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-6{--tw-border-spacing-x:calc(var(--spacing) * 6);--tw-border-spacing-y:calc(var(--spacing) * 6);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.border-spacing-8{--tw-border-spacing-x:calc(var(--spacing) * 8);--tw-border-spacing-y:calc(var(--spacing) * 8);border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.origin-bottom{transform-origin:bottom}.origin-bottom-left{transform-origin:0 100%}.origin-bottom-right{transform-origin:100% 100%}.origin-center{transform-origin:50%}.origin-left{transform-origin:0}.origin-right{transform-origin:100%}.origin-top{transform-origin:top}.origin-top-left{transform-origin:0 0}.origin-top-right{transform-origin:100% 0}.-translate-x-1{--tw-translate-x:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-2{--tw-translate-x:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-3{--tw-translate-x:calc(var(--spacing) * -3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-4{--tw-translate-x:calc(var(--spacing) * -4);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-0{--tw-translate-x:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1{--tw-translate-x:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/2{--tw-translate-x: 50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/3{--tw-translate-x:calc(1 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-1\/4{--tw-translate-x: 25% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-2{--tw-translate-x:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-2\/3{--tw-translate-x:calc(2 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-3{--tw-translate-x:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-3\/4{--tw-translate-x: 75% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-4{--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-5{--tw-translate-x:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-6{--tw-translate-x:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-8{--tw-translate-x:calc(var(--spacing) * 8);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-10{--tw-translate-x:calc(var(--spacing) * 10);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-12{--tw-translate-x:calc(var(--spacing) * 12);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.-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: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-2{--tw-translate-y:calc(var(--spacing) * -2);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-3{--tw-translate-y:calc(var(--spacing) * -3);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-4{--tw-translate-y:calc(var(--spacing) * -4);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-full{--tw-translate-y:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-0{--tw-translate-y:calc(var(--spacing) * 0);translate:var(--tw-translate-x) var(--tw-translate-y)}.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: 50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/3{--tw-translate-y:calc(1 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1\/4{--tw-translate-y: 25% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2{--tw-translate-y:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2\/3{--tw-translate-y:calc(2 / 3 * 100%);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3{--tw-translate-y:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-3\/4{--tw-translate-y: 75% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-4{--tw-translate-y:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-5{--tw-translate-y:calc(var(--spacing) * 5);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-6{--tw-translate-y:calc(var(--spacing) * 6);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-8{--tw-translate-y:calc(var(--spacing) * 8);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-10{--tw-translate-y:calc(var(--spacing) * 10);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-12{--tw-translate-y:calc(var(--spacing) * 12);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-full{--tw-translate-y:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-50{--tw-scale-x:50%;--tw-scale-y:50%;--tw-scale-z:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-75{--tw-scale-x:75%;--tw-scale-y:75%;--tw-scale-z:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-90{--tw-scale-x:90%;--tw-scale-y:90%;--tw-scale-z:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-95{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-105{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-125{--tw-scale-x:125%;--tw-scale-y:125%;--tw-scale-z:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-150{--tw-scale-x:150%;--tw-scale-y:150%;--tw-scale-z:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-0{--tw-scale-x:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-50{--tw-scale-x:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-75{--tw-scale-x:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-90{--tw-scale-x:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-95{--tw-scale-x:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-100{--tw-scale-x:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-105{--tw-scale-x:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-110{--tw-scale-x:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-125{--tw-scale-x:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-x-150{--tw-scale-x:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-0{--tw-scale-y:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-50{--tw-scale-y:50%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-75{--tw-scale-y:75%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-90{--tw-scale-y:90%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-95{--tw-scale-y:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-100{--tw-scale-y:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-105{--tw-scale-y:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-110{--tw-scale-y:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-125{--tw-scale-y:125%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-y-150{--tw-scale-y:150%;scale:var(--tw-scale-x) var(--tw-scale-y)}.-rotate-1{rotate:-1deg}.-rotate-2{rotate:-2deg}.-rotate-3{rotate:-3deg}.-rotate-6{rotate:-6deg}.-rotate-12{rotate:-12deg}.-rotate-45{rotate:-45deg}.-rotate-90{rotate:-90deg}.-rotate-180{rotate:-180deg}.rotate-0{rotate:0deg}.rotate-1{rotate:1deg}.rotate-2{rotate:2deg}.rotate-3{rotate:3deg}.rotate-6{rotate:6deg}.rotate-12{rotate:12deg}.rotate-45{rotate:45deg}.rotate-90{rotate:90deg}.rotate-180{rotate:180deg}.-skew-x-1{--tw-skew-x:skewX( -1deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-2{--tw-skew-x:skewX( -2deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-3{--tw-skew-x:skewX( -3deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-6{--tw-skew-x:skewX( -6deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-x-12{--tw-skew-x:skewX( -12deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-0{--tw-skew-x:skewX(0deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-1{--tw-skew-x:skewX(1deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-2{--tw-skew-x:skewX(2deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-3{--tw-skew-x:skewX(3deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-6{--tw-skew-x:skewX(6deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-x-12{--tw-skew-x:skewX(12deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-1{--tw-skew-y:skewY( -1deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-2{--tw-skew-y:skewY( -2deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-3{--tw-skew-y:skewY( -3deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-6{--tw-skew-y:skewY( -6deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.-skew-y-12{--tw-skew-y:skewY( -12deg );transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-0{--tw-skew-y:skewY(0deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-1{--tw-skew-y:skewY(1deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-2{--tw-skew-y:skewY(2deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-3{--tw-skew-y:skewY(3deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-6{--tw-skew-y:skewY(6deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.skew-y-12{--tw-skew-y:skewY(12deg);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform,.transform-cpu{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform-gpu{transform:translateZ(0) var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform-none{transform:none}.animate-\[loading_1\.5s_ease-in-out_infinite\]{animation:1.5s ease-in-out infinite loading}.animate-bounce{animation:var(--animate-bounce)}.animate-none{animation:none}.animate-ping{animation:var(--animate-ping)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-alias{cursor:alias}.cursor-all-scroll{cursor:all-scroll}.cursor-auto{cursor:auto}.cursor-cell{cursor:cell}.cursor-col-resize{cursor:col-resize}.cursor-context-menu{cursor:context-menu}.cursor-copy{cursor:copy}.cursor-crosshair{cursor:crosshair}.cursor-default{cursor:default}.cursor-e-resize{cursor:e-resize}.cursor-ew-resize{cursor:ew-resize}.cursor-grab{cursor:grab}.cursor-grabbing{cursor:grabbing}.cursor-help{cursor:help}.cursor-move{cursor:move}.cursor-n-resize{cursor:n-resize}.cursor-ne-resize{cursor:ne-resize}.cursor-nesw-resize{cursor:nesw-resize}.cursor-no-drop{cursor:no-drop}.cursor-none{cursor:none}.cursor-not-allowed{cursor:not-allowed}.cursor-ns-resize{cursor:ns-resize}.cursor-nw-resize{cursor:nw-resize}.cursor-nwse-resize{cursor:nwse-resize}.cursor-pointer{cursor:pointer}.cursor-progress{cursor:progress}.cursor-row-resize{cursor:row-resize}.cursor-s-resize{cursor:s-resize}.cursor-se-resize{cursor:se-resize}.cursor-sw-resize{cursor:sw-resize}.cursor-text{cursor:text}.cursor-vertical-text{cursor:vertical-text}.cursor-w-resize{cursor:w-resize}.cursor-wait{cursor:wait}.cursor-zoom-in{cursor:zoom-in}.cursor-zoom-out{cursor:zoom-out}.touch-pan-left{--tw-pan-x:pan-left;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-right{--tw-pan-x:pan-right;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-x{--tw-pan-x:pan-x;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-down{--tw-pan-y:pan-down;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-up{--tw-pan-y:pan-up;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pan-y{--tw-pan-y:pan-y;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-pinch-zoom{--tw-pinch-zoom:pinch-zoom;touch-action:var(--tw-pan-x,) var(--tw-pan-y,) var(--tw-pinch-zoom,)}.touch-auto{touch-action:auto}.touch-manipulation{touch-action:manipulation}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.resize-x{resize:horizontal}.resize-y{resize:vertical}.snap-both{scroll-snap-type:both var(--tw-scroll-snap-strictness)}.snap-none{scroll-snap-type:none}.snap-x{scroll-snap-type:x var(--tw-scroll-snap-strictness)}.snap-y{scroll-snap-type:y var(--tw-scroll-snap-strictness)}.snap-mandatory{--tw-scroll-snap-strictness:mandatory}.snap-proximity{--tw-scroll-snap-strictness:proximity}.snap-align-none{scroll-snap-align:none}.snap-center{scroll-snap-align:center}.snap-end{scroll-snap-align:end}.snap-start{scroll-snap-align:start}.snap-always{scroll-snap-stop:always}.snap-normal{scroll-snap-stop:normal}.scroll-mt-44{scroll-margin-top:calc(var(--spacing) * 44)}.list-inside{list-style-position:inside}.list-outside{list-style-position:outside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.appearance-auto{appearance:auto}.appearance-none{appearance:none}.columns-1{columns:1}.columns-2{columns:2}.columns-2xl{columns:var(--container-2xl)}.columns-2xs{columns:var(--container-2xs)}.columns-3{columns:3}.columns-3xl{columns:var(--container-3xl)}.columns-3xs{columns:var(--container-3xs)}.columns-4{columns:4}.columns-4xl{columns:var(--container-4xl)}.columns-5{columns:5}.columns-5xl{columns:var(--container-5xl)}.columns-6{columns:6}.columns-6xl{columns:var(--container-6xl)}.columns-7{columns:7}.columns-7xl{columns:var(--container-7xl)}.columns-8{columns:8}.columns-9{columns:9}.columns-10{columns:10}.columns-11{columns:11}.columns-12{columns:12}.columns-auto{columns:auto}.columns-lg{columns:var(--container-lg)}.columns-md{columns:var(--container-md)}.columns-sm{columns:var(--container-sm)}.columns-xl{columns:var(--container-xl)}.columns-xs{columns:var(--container-xs)}.break-before-all{break-before:all}.break-before-auto{break-before:auto}.break-before-avoid{break-before:avoid}.break-before-avoid-page{break-before:avoid-page}.break-before-column{break-before:column}.break-before-left{break-before:left}.break-before-page{break-before:page}.break-before-right{break-before:right}.break-inside-auto{break-inside:auto}.break-inside-avoid{break-inside:avoid}.break-inside-avoid-column{break-inside:avoid-column}.break-inside-avoid-page{break-inside:avoid-page}.break-after-all{break-after:all}.break-after-auto{break-after:auto}.break-after-avoid{break-after:avoid}.break-after-avoid-page{break-after:avoid-page}.break-after-column{break-after:column}.break-after-left{break-after:left}.break-after-page{break-after:page}.break-after-right{break-after:right}.auto-cols-auto{grid-auto-columns:auto}.auto-cols-fr{grid-auto-columns:minmax(0,1fr)}.auto-cols-max{grid-auto-columns:max-content}.auto-cols-min{grid-auto-columns:min-content}.grid-flow-col{grid-auto-flow:column}.grid-flow-col-dense{grid-auto-flow:column dense}.grid-flow-dense{grid-auto-flow:dense}.grid-flow-row{grid-auto-flow:row}.grid-flow-row-dense{grid-auto-flow:dense}.auto-rows-auto{grid-auto-rows:auto}.auto-rows-fr{grid-auto-rows:minmax(0,1fr)}.auto-rows-max{grid-auto-rows:max-content}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-9{grid-template-columns:repeat(9,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.grid-cols-11{grid-template-columns:repeat(11,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.\!flex-col{flex-direction:column!important}.\!flex-row{flex-direction:row!important}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.\!flex-nowrap{flex-wrap:nowrap!important}.\!flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.flex-wrap-reverse{flex-wrap:wrap-reverse}.place-content-around{place-content:space-around}.place-content-baseline{place-content:baseline start}.place-content-between{place-content:space-between}.place-content-center{place-content:center}.place-content-end{place-content:end}.place-content-evenly{place-content:space-evenly}.place-content-start{place-content:start}.place-content-stretch{place-content:stretch}.place-items-baseline{place-items:baseline}.place-items-center{place-items:center}.place-items-end{place-items:end}.place-items-start{place-items:start}.place-items-stretch{place-items:stretch stretch}.content-around{align-content:space-around}.content-between{align-content:space-between}.content-center{align-content:center}.content-end{align-content:flex-end}.content-evenly{align-content:space-evenly}.content-start{align-content:flex-start}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-around{justify-content:space-around}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-evenly{justify-content:space-evenly}.justify-start{justify-content:flex-start}.\!gap-0{gap:calc(var(--spacing) * 0)!important}.\!gap-1{gap:calc(var(--spacing) * 1)!important}.\!gap-2{gap:calc(var(--spacing) * 2)!important}.\!gap-3{gap:calc(var(--spacing) * 3)!important}.\!gap-4{gap:calc(var(--spacing) * 4)!important}.\!gap-5{gap:calc(var(--spacing) * 5)!important}.\!gap-6{gap:calc(var(--spacing) * 6)!important}.\!gap-8{gap:calc(var(--spacing) * 8)!important}.\!gap-10{gap:calc(var(--spacing) * 10)!important}.\!gap-12{gap:calc(var(--spacing) * 12)!important}.\!gap-16{gap:calc(var(--spacing) * 16)!important}.gap-0{gap:calc(var(--spacing) * 0)}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-10{gap:calc(var(--spacing) * 10)}.gap-12{gap:calc(var(--spacing) * 12)}.gap-16{gap:calc(var(--spacing) * 16)}: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-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}: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-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * 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)))}.gap-x-0{column-gap:calc(var(--spacing) * 0)}.gap-x-1{column-gap:calc(var(--spacing) * 1)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-5{column-gap:calc(var(--spacing) * 5)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-x-8{column-gap:calc(var(--spacing) * 8)}.gap-x-10{column-gap:calc(var(--spacing) * 10)}.gap-x-12{column-gap:calc(var(--spacing) * 12)}:where(.-space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -1) * calc(1 - var(--tw-space-x-reverse)))}:where(.-space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 5) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.gap-y-0{row-gap:calc(var(--spacing) * 0)}.gap-y-1{row-gap:calc(var(--spacing) * 1)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}.gap-y-4{row-gap:calc(var(--spacing) * 4)}.gap-y-5{row-gap:calc(var(--spacing) * 5)}.gap-y-6{row-gap:calc(var(--spacing) * 6)}.gap-y-8{row-gap:calc(var(--spacing) * 8)}.gap-y-10{row-gap:calc(var(--spacing) * 10)}.gap-y-12{row-gap:calc(var(--spacing) * 12)}:where(.divide-x>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px * var(--tw-divide-x-reverse));border-inline-end-width:calc(1px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-0>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(0px * var(--tw-divide-x-reverse));border-inline-end-width:calc(0px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-2>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(2px * var(--tw-divide-x-reverse));border-inline-end-width:calc(2px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-4>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(4px * var(--tw-divide-x-reverse));border-inline-end-width:calc(4px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-x-8>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(8px * var(--tw-divide-x-reverse));border-inline-end-width:calc(8px * calc(1 - var(--tw-divide-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-0>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px * var(--tw-divide-y-reverse));border-bottom-width:calc(0px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-2>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(2px * var(--tw-divide-y-reverse));border-bottom-width:calc(2px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-4>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(4px * var(--tw-divide-y-reverse));border-bottom-width:calc(4px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-8>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(8px * var(--tw-divide-y-reverse));border-bottom-width:calc(8px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-y-reverse>:not(:last-child)){--tw-divide-y-reverse:1}:where(.divide-dashed>:not(:last-child)){--tw-border-style:dashed;border-style:dashed}:where(.divide-dotted>:not(:last-child)){--tw-border-style:dotted;border-style:dotted}:where(.divide-double>:not(:last-child)){--tw-border-style:double;border-style:double}:where(.divide-none>:not(:last-child)){--tw-border-style:none;border-style:none}:where(.divide-solid>:not(:last-child)){--tw-border-style:solid;border-style:solid}:where(.divide-black>:not(:last-child)){border-color:var(--color-black)}:where(.divide-current>:not(:last-child)){border-color:currentColor}:where(.divide-gray-50>:not(:last-child)){border-color:var(--color-gray-50)}:where(.divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}:where(.divide-gray-400>:not(:last-child)){border-color:var(--color-gray-400)}:where(.divide-gray-500>:not(:last-child)){border-color:var(--color-gray-500)}:where(.divide-gray-600>:not(:last-child)){border-color:var(--color-gray-600)}:where(.divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}:where(.divide-gray-800>:not(:last-child)){border-color:var(--color-gray-800)}:where(.divide-gray-900>:not(:last-child)){border-color:var(--color-gray-900)}:where(.divide-inherit>:not(:last-child)){border-color:inherit}:where(.divide-purple-100>:not(:last-child)){border-color:var(--color-purple-100)}:where(.divide-slate-50>:not(:last-child)){border-color:var(--color-slate-50)}:where(.divide-slate-100>:not(:last-child)){border-color:var(--color-slate-100)}:where(.divide-slate-200>:not(:last-child)){border-color:var(--color-slate-200)}:where(.divide-slate-300>:not(:last-child)){border-color:var(--color-slate-300)}:where(.divide-slate-400>:not(:last-child)){border-color:var(--color-slate-400)}:where(.divide-slate-500>:not(:last-child)){border-color:var(--color-slate-500)}:where(.divide-slate-600>:not(:last-child)){border-color:var(--color-slate-600)}:where(.divide-slate-700>:not(:last-child)){border-color:var(--color-slate-700)}:where(.divide-slate-800>:not(:last-child)){border-color:var(--color-slate-800)}:where(.divide-slate-900>:not(:last-child)){border-color:var(--color-slate-900)}:where(.divide-transparent>:not(:last-child)){border-color:#0000}:where(.divide-white>:not(:last-child)){border-color:var(--color-white)}.place-self-auto{place-self:auto}.place-self-center{place-self:center}.place-self-end{place-self:end}.place-self-start{place-self:start}.place-self-stretch{place-self:stretch stretch}.self-auto{align-self:auto}.self-baseline{align-self:baseline}.self-center{align-self:center}.self-end{align-self:flex-end}.self-start{align-self:flex-start}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-clip{overflow:clip}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-x-clip{overflow-x:clip}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-x-visible{overflow-x:visible}.overflow-y-auto{overflow-y:auto}.overflow-y-clip{overflow-y:clip}.overflow-y-hidden{overflow-y:hidden}.overflow-y-scroll{overflow-y:scroll}.overflow-y-visible{overflow-y:visible}.overscroll-auto{overscroll-behavior:auto}.overscroll-contain{overscroll-behavior:contain}.overscroll-none{overscroll-behavior:none}.overscroll-x-auto{overscroll-behavior-x:auto}.overscroll-x-contain{overscroll-behavior-x:contain}.overscroll-x-none{overscroll-behavior-x:none}.overscroll-y-auto{overscroll-behavior-y:auto}.overscroll-y-contain{overscroll-behavior-y:contain}.overscroll-y-none{overscroll-behavior-y:none}.scroll-auto{scroll-behavior:auto}.scroll-smooth{scroll-behavior:smooth}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-none{border-radius:0}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.rounded-t-2xl{border-top-left-radius:var(--radius-2xl);border-top-right-radius:var(--radius-2xl)}.rounded-t-3xl{border-top-left-radius:var(--radius-3xl);border-top-right-radius:var(--radius-3xl)}.rounded-t-full{border-top-left-radius:3.40282e38px;border-top-right-radius:3.40282e38px}.rounded-t-lg{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.rounded-t-md{border-top-left-radius:var(--radius-md);border-top-right-radius:var(--radius-md)}.rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.rounded-t-sm{border-top-left-radius:var(--radius-sm);border-top-right-radius:var(--radius-sm)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-l-2xl{border-top-left-radius:var(--radius-2xl);border-bottom-left-radius:var(--radius-2xl)}.rounded-l-3xl{border-top-left-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-l-full{border-top-left-radius:3.40282e38px;border-bottom-left-radius:3.40282e38px}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-l-md{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-l-sm{border-top-left-radius:var(--radius-sm);border-bottom-left-radius:var(--radius-sm)}.rounded-l-xl{border-top-left-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-tl{border-top-left-radius:.25rem}.rounded-tl-2xl{border-top-left-radius:var(--radius-2xl)}.rounded-tl-3xl{border-top-left-radius:var(--radius-3xl)}.rounded-tl-full{border-top-left-radius:3.40282e38px}.rounded-tl-lg{border-top-left-radius:var(--radius-lg)}.rounded-tl-md{border-top-left-radius:var(--radius-md)}.rounded-tl-none{border-top-left-radius:0}.rounded-tl-sm{border-top-left-radius:var(--radius-sm)}.rounded-tl-xl{border-top-left-radius:var(--radius-xl)}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-r-2xl{border-top-right-radius:var(--radius-2xl);border-bottom-right-radius:var(--radius-2xl)}.rounded-r-3xl{border-top-right-radius:var(--radius-3xl);border-bottom-right-radius:var(--radius-3xl)}.rounded-r-full{border-top-right-radius:3.40282e38px;border-bottom-right-radius:3.40282e38px}.rounded-r-lg{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.rounded-r-md{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.rounded-r-sm{border-top-right-radius:var(--radius-sm);border-bottom-right-radius:var(--radius-sm)}.rounded-r-xl{border-top-right-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl)}.rounded-tr{border-top-right-radius:.25rem}.rounded-tr-2xl{border-top-right-radius:var(--radius-2xl)}.rounded-tr-3xl{border-top-right-radius:var(--radius-3xl)}.rounded-tr-full{border-top-right-radius:3.40282e38px}.rounded-tr-lg{border-top-right-radius:var(--radius-lg)}.rounded-tr-md{border-top-right-radius:var(--radius-md)}.rounded-tr-none{border-top-right-radius:0}.rounded-tr-sm{border-top-right-radius:var(--radius-sm)}.rounded-tr-xl{border-top-right-radius:var(--radius-xl)}.rounded-b{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-b-2xl{border-bottom-right-radius:var(--radius-2xl);border-bottom-left-radius:var(--radius-2xl)}.rounded-b-3xl{border-bottom-right-radius:var(--radius-3xl);border-bottom-left-radius:var(--radius-3xl)}.rounded-b-full{border-bottom-right-radius:3.40282e38px;border-bottom-left-radius:3.40282e38px}.rounded-b-lg{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-b-md{border-bottom-right-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.rounded-b-none{border-bottom-right-radius:0;border-bottom-left-radius:0}.rounded-b-sm{border-bottom-right-radius:var(--radius-sm);border-bottom-left-radius:var(--radius-sm)}.rounded-b-xl{border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-br{border-bottom-right-radius:.25rem}.rounded-br-2xl{border-bottom-right-radius:var(--radius-2xl)}.rounded-br-3xl{border-bottom-right-radius:var(--radius-3xl)}.rounded-br-full{border-bottom-right-radius:3.40282e38px}.rounded-br-lg{border-bottom-right-radius:var(--radius-lg)}.rounded-br-md{border-bottom-right-radius:var(--radius-md)}.rounded-br-none{border-bottom-right-radius:0}.rounded-br-sm{border-bottom-right-radius:var(--radius-sm)}.rounded-br-xl{border-bottom-right-radius:var(--radius-xl)}.rounded-bl{border-bottom-left-radius:.25rem}.rounded-bl-2xl{border-bottom-left-radius:var(--radius-2xl)}.rounded-bl-3xl{border-bottom-left-radius:var(--radius-3xl)}.rounded-bl-full{border-bottom-left-radius:3.40282e38px}.rounded-bl-lg{border-bottom-left-radius:var(--radius-lg)}.rounded-bl-md{border-bottom-left-radius:var(--radius-md)}.rounded-bl-none{border-bottom-left-radius:0}.rounded-bl-sm{border-bottom-left-radius:var(--radius-sm)}.rounded-bl-xl{border-bottom-left-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-3{border-style:var(--tw-border-style);border-width:3px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-8{border-style:var(--tw-border-style);border-width:8px}.border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}.border-x-0{border-inline-style:var(--tw-border-style);border-inline-width:0}.border-x-2{border-inline-style:var(--tw-border-style);border-inline-width:2px}.border-y{border-block-style:var(--tw-border-style);border-block-width:1px}.border-y-0{border-block-style:var(--tw-border-style);border-block-width:0}.border-y-2{border-block-style:var(--tw-border-style);border-block-width:2px}.\!border-t-0{border-top-style:var(--tw-border-style)!important;border-top-width:0!important}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-t-0{border-top-style:var(--tw-border-style);border-top-width:0}.border-t-2{border-top-style:var(--tw-border-style);border-top-width:2px}.border-t-4{border-top-style:var(--tw-border-style);border-top-width:4px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-r-0{border-right-style:var(--tw-border-style);border-right-width:0}.border-r-2{border-right-style:var(--tw-border-style);border-right-width:2px}.border-r-4{border-right-style:var(--tw-border-style);border-right-width:4px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-b-4{border-bottom-style:var(--tw-border-style);border-bottom-width:4px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-dotted{--tw-border-style:dotted;border-style:dotted}.border-double{--tw-border-style:double;border-style:double}.border-hidden{--tw-border-style:hidden;border-style:hidden}.border-none{--tw-border-style:none;border-style:none}.border-solid{--tw-border-style:solid;border-style:solid}.border-amber-50{border-color:var(--color-amber-50)}.border-amber-100{border-color:var(--color-amber-100)}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-amber-400{border-color:var(--color-amber-400)}.border-amber-500{border-color:var(--color-amber-500)}.border-amber-600{border-color:var(--color-amber-600)}.border-amber-700{border-color:var(--color-amber-700)}.border-amber-800{border-color:var(--color-amber-800)}.border-amber-900{border-color:var(--color-amber-900)}.border-amber-950{border-color:var(--color-amber-950)}.border-black{border-color:var(--color-black)}.border-blue-50{border-color:var(--color-blue-50)}.border-blue-100{border-color:var(--color-blue-100)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-300{border-color:var(--color-blue-300)}.border-blue-400{border-color:var(--color-blue-400)}.border-blue-500{border-color:var(--color-blue-500)}.border-blue-600{border-color:var(--color-blue-600)}.border-blue-700{border-color:var(--color-blue-700)}.border-blue-800{border-color:var(--color-blue-800)}.border-blue-900{border-color:var(--color-blue-900)}.border-blue-950{border-color:var(--color-blue-950)}.border-current{border-color:currentColor}.border-cyan-50{border-color:var(--color-cyan-50)}.border-cyan-100{border-color:var(--color-cyan-100)}.border-cyan-200{border-color:var(--color-cyan-200)}.border-cyan-300{border-color:var(--color-cyan-300)}.border-cyan-400{border-color:var(--color-cyan-400)}.border-cyan-500{border-color:var(--color-cyan-500)}.border-cyan-600{border-color:var(--color-cyan-600)}.border-cyan-700{border-color:var(--color-cyan-700)}.border-cyan-800{border-color:var(--color-cyan-800)}.border-cyan-900{border-color:var(--color-cyan-900)}.border-cyan-950{border-color:var(--color-cyan-950)}.border-emerald-50{border-color:var(--color-emerald-50)}.border-emerald-100{border-color:var(--color-emerald-100)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-emerald-300{border-color:var(--color-emerald-300)}.border-emerald-400{border-color:var(--color-emerald-400)}.border-emerald-500{border-color:var(--color-emerald-500)}.border-emerald-600{border-color:var(--color-emerald-600)}.border-emerald-700{border-color:var(--color-emerald-700)}.border-emerald-800{border-color:var(--color-emerald-800)}.border-emerald-900{border-color:var(--color-emerald-900)}.border-emerald-950{border-color:var(--color-emerald-950)}.border-fuchsia-50{border-color:var(--color-fuchsia-50)}.border-fuchsia-100{border-color:var(--color-fuchsia-100)}.border-fuchsia-200{border-color:var(--color-fuchsia-200)}.border-fuchsia-300{border-color:var(--color-fuchsia-300)}.border-fuchsia-400{border-color:var(--color-fuchsia-400)}.border-fuchsia-500{border-color:var(--color-fuchsia-500)}.border-fuchsia-600{border-color:var(--color-fuchsia-600)}.border-fuchsia-700{border-color:var(--color-fuchsia-700)}.border-fuchsia-800{border-color:var(--color-fuchsia-800)}.border-fuchsia-900{border-color:var(--color-fuchsia-900)}.border-fuchsia-950{border-color:var(--color-fuchsia-950)}.border-gray-50{border-color:var(--color-gray-50)}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-400{border-color:var(--color-gray-400)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-gray-900{border-color:var(--color-gray-900)}.border-gray-950{border-color:var(--color-gray-950)}.border-green-50{border-color:var(--color-green-50)}.border-green-100{border-color:var(--color-green-100)}.border-green-200{border-color:var(--color-green-200)}.border-green-300{border-color:var(--color-green-300)}.border-green-400{border-color:var(--color-green-400)}.border-green-500{border-color:var(--color-green-500)}.border-green-600{border-color:var(--color-green-600)}.border-green-700{border-color:var(--color-green-700)}.border-green-800{border-color:var(--color-green-800)}.border-green-900{border-color:var(--color-green-900)}.border-green-950{border-color:var(--color-green-950)}.border-indigo-50{border-color:var(--color-indigo-50)}.border-indigo-100{border-color:var(--color-indigo-100)}.border-indigo-200{border-color:var(--color-indigo-200)}.border-indigo-300{border-color:var(--color-indigo-300)}.border-indigo-400{border-color:var(--color-indigo-400)}.border-indigo-500{border-color:var(--color-indigo-500)}.border-indigo-600{border-color:var(--color-indigo-600)}.border-indigo-700{border-color:var(--color-indigo-700)}.border-indigo-800{border-color:var(--color-indigo-800)}.border-indigo-900{border-color:var(--color-indigo-900)}.border-indigo-950{border-color:var(--color-indigo-950)}.border-inherit{border-color:inherit}.border-lime-50{border-color:var(--color-lime-50)}.border-lime-100{border-color:var(--color-lime-100)}.border-lime-200{border-color:var(--color-lime-200)}.border-lime-300{border-color:var(--color-lime-300)}.border-lime-400{border-color:var(--color-lime-400)}.border-lime-500{border-color:var(--color-lime-500)}.border-lime-600{border-color:var(--color-lime-600)}.border-lime-700{border-color:var(--color-lime-700)}.border-lime-800{border-color:var(--color-lime-800)}.border-lime-900{border-color:var(--color-lime-900)}.border-lime-950{border-color:var(--color-lime-950)}.border-neutral-50{border-color:var(--color-neutral-50)}.border-neutral-100{border-color:var(--color-neutral-100)}.border-neutral-200{border-color:var(--color-neutral-200)}.border-neutral-300{border-color:var(--color-neutral-300)}.border-neutral-400{border-color:var(--color-neutral-400)}.border-neutral-500{border-color:var(--color-neutral-500)}.border-neutral-600{border-color:var(--color-neutral-600)}.border-neutral-700{border-color:var(--color-neutral-700)}.border-neutral-800{border-color:var(--color-neutral-800)}.border-neutral-900{border-color:var(--color-neutral-900)}.border-neutral-950{border-color:var(--color-neutral-950)}.border-orange-50{border-color:var(--color-orange-50)}.border-orange-100{border-color:var(--color-orange-100)}.border-orange-200{border-color:var(--color-orange-200)}.border-orange-300{border-color:var(--color-orange-300)}.border-orange-400{border-color:var(--color-orange-400)}.border-orange-500{border-color:var(--color-orange-500)}.border-orange-600{border-color:var(--color-orange-600)}.border-orange-700{border-color:var(--color-orange-700)}.border-orange-800{border-color:var(--color-orange-800)}.border-orange-900{border-color:var(--color-orange-900)}.border-orange-950{border-color:var(--color-orange-950)}.border-pink-50{border-color:var(--color-pink-50)}.border-pink-100{border-color:var(--color-pink-100)}.border-pink-200{border-color:var(--color-pink-200)}.border-pink-300{border-color:var(--color-pink-300)}.border-pink-400{border-color:var(--color-pink-400)}.border-pink-500{border-color:var(--color-pink-500)}.border-pink-600{border-color:var(--color-pink-600)}.border-pink-700{border-color:var(--color-pink-700)}.border-pink-800{border-color:var(--color-pink-800)}.border-pink-900{border-color:var(--color-pink-900)}.border-pink-950{border-color:var(--color-pink-950)}.border-purple-50{border-color:var(--color-purple-50)}.border-purple-100{border-color:var(--color-purple-100)}.border-purple-200{border-color:var(--color-purple-200)}.border-purple-300{border-color:var(--color-purple-300)}.border-purple-400{border-color:var(--color-purple-400)}.border-purple-500{border-color:var(--color-purple-500)}.border-purple-600{border-color:var(--color-purple-600)}.border-purple-700{border-color:var(--color-purple-700)}.border-purple-800{border-color:var(--color-purple-800)}.border-purple-900{border-color:var(--color-purple-900)}.border-purple-950{border-color:var(--color-purple-950)}.border-red-50{border-color:var(--color-red-50)}.border-red-100{border-color:var(--color-red-100)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-red-400{border-color:var(--color-red-400)}.border-red-500{border-color:var(--color-red-500)}.border-red-600{border-color:var(--color-red-600)}.border-red-700{border-color:var(--color-red-700)}.border-red-800{border-color:var(--color-red-800)}.border-red-900{border-color:var(--color-red-900)}.border-red-950{border-color:var(--color-red-950)}.border-rose-50{border-color:var(--color-rose-50)}.border-rose-100{border-color:var(--color-rose-100)}.border-rose-200{border-color:var(--color-rose-200)}.border-rose-300{border-color:var(--color-rose-300)}.border-rose-400{border-color:var(--color-rose-400)}.border-rose-500{border-color:var(--color-rose-500)}.border-rose-600{border-color:var(--color-rose-600)}.border-rose-700{border-color:var(--color-rose-700)}.border-rose-800{border-color:var(--color-rose-800)}.border-rose-900{border-color:var(--color-rose-900)}.border-rose-950{border-color:var(--color-rose-950)}.border-sky-50{border-color:var(--color-sky-50)}.border-sky-100{border-color:var(--color-sky-100)}.border-sky-200{border-color:var(--color-sky-200)}.border-sky-300{border-color:var(--color-sky-300)}.border-sky-400{border-color:var(--color-sky-400)}.border-sky-500{border-color:var(--color-sky-500)}.border-sky-600{border-color:var(--color-sky-600)}.border-sky-700{border-color:var(--color-sky-700)}.border-sky-800{border-color:var(--color-sky-800)}.border-sky-900{border-color:var(--color-sky-900)}.border-sky-950{border-color:var(--color-sky-950)}.border-slate-50{border-color:var(--color-slate-50)}.border-slate-100{border-color:var(--color-slate-100)}.border-slate-200{border-color:var(--color-slate-200)}.border-slate-300{border-color:var(--color-slate-300)}.border-slate-400{border-color:var(--color-slate-400)}.border-slate-500{border-color:var(--color-slate-500)}.border-slate-600{border-color:var(--color-slate-600)}.border-slate-700{border-color:var(--color-slate-700)}.border-slate-800{border-color:var(--color-slate-800)}.border-slate-900{border-color:var(--color-slate-900)}.border-slate-950{border-color:var(--color-slate-950)}.border-stone-50{border-color:var(--color-stone-50)}.border-stone-100{border-color:var(--color-stone-100)}.border-stone-200{border-color:var(--color-stone-200)}.border-stone-300{border-color:var(--color-stone-300)}.border-stone-400{border-color:var(--color-stone-400)}.border-stone-500{border-color:var(--color-stone-500)}.border-stone-600{border-color:var(--color-stone-600)}.border-stone-700{border-color:var(--color-stone-700)}.border-stone-800{border-color:var(--color-stone-800)}.border-stone-900{border-color:var(--color-stone-900)}.border-stone-950{border-color:var(--color-stone-950)}.border-teal-50{border-color:var(--color-teal-50)}.border-teal-100{border-color:var(--color-teal-100)}.border-teal-200{border-color:var(--color-teal-200)}.border-teal-300{border-color:var(--color-teal-300)}.border-teal-400{border-color:var(--color-teal-400)}.border-teal-500{border-color:var(--color-teal-500)}.border-teal-600{border-color:var(--color-teal-600)}.border-teal-700{border-color:var(--color-teal-700)}.border-teal-800{border-color:var(--color-teal-800)}.border-teal-900{border-color:var(--color-teal-900)}.border-teal-950{border-color:var(--color-teal-950)}.border-transparent{border-color:#0000}.border-violet-50{border-color:var(--color-violet-50)}.border-violet-100{border-color:var(--color-violet-100)}.border-violet-200{border-color:var(--color-violet-200)}.border-violet-300{border-color:var(--color-violet-300)}.border-violet-400{border-color:var(--color-violet-400)}.border-violet-500{border-color:var(--color-violet-500)}.border-violet-600{border-color:var(--color-violet-600)}.border-violet-700{border-color:var(--color-violet-700)}.border-violet-800{border-color:var(--color-violet-800)}.border-violet-900{border-color:var(--color-violet-900)}.border-violet-950{border-color:var(--color-violet-950)}.border-white{border-color:var(--color-white)}.border-white\/30{border-color:#ffffff4d}@supports (color:color-mix(in lab,red,red)){.border-white\/30{border-color:color-mix(in oklab,var(--color-white) 30%,transparent)}}.border-yellow-50{border-color:var(--color-yellow-50)}.border-yellow-100{border-color:var(--color-yellow-100)}.border-yellow-200{border-color:var(--color-yellow-200)}.border-yellow-300{border-color:var(--color-yellow-300)}.border-yellow-400{border-color:var(--color-yellow-400)}.border-yellow-500{border-color:var(--color-yellow-500)}.border-yellow-600{border-color:var(--color-yellow-600)}.border-yellow-700{border-color:var(--color-yellow-700)}.border-yellow-800{border-color:var(--color-yellow-800)}.border-yellow-900{border-color:var(--color-yellow-900)}.border-yellow-950{border-color:var(--color-yellow-950)}.border-zinc-50{border-color:var(--color-zinc-50)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-400{border-color:var(--color-zinc-400)}.border-zinc-500{border-color:var(--color-zinc-500)}.border-zinc-600{border-color:var(--color-zinc-600)}.border-zinc-700{border-color:var(--color-zinc-700)}.border-zinc-800{border-color:var(--color-zinc-800)}.border-zinc-900{border-color:var(--color-zinc-900)}.border-zinc-950{border-color:var(--color-zinc-950)}.border-t-current{border-top-color:currentColor}.border-t-white{border-top-color:var(--color-white)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-200{background-color:var(--color-amber-200)}.bg-amber-300{background-color:var(--color-amber-300)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-600{background-color:var(--color-amber-600)}.bg-amber-700{background-color:var(--color-amber-700)}.bg-amber-800{background-color:var(--color-amber-800)}.bg-amber-900{background-color:var(--color-amber-900)}.bg-amber-950{background-color:var(--color-amber-950)}.bg-black{background-color:var(--color-black)}.bg-black\/0{background-color:#0000}@supports (color:color-mix(in lab,red,red)){.bg-black\/0{background-color:color-mix(in oklab,var(--color-black) 0%,transparent)}}.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-blue-50{background-color:var(--color-blue-50)}.bg-blue-50\/70{background-color:#eff6ffb3}@supports (color:color-mix(in lab,red,red)){.bg-blue-50\/70{background-color:color-mix(in oklab,var(--color-blue-50) 70%,transparent)}}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-200{background-color:var(--color-blue-200)}.bg-blue-300{background-color:var(--color-blue-300)}.bg-blue-400{background-color:var(--color-blue-400)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-700{background-color:var(--color-blue-700)}.bg-blue-800{background-color:var(--color-blue-800)}.bg-blue-900{background-color:var(--color-blue-900)}.bg-blue-950{background-color:var(--color-blue-950)}.bg-current{background-color:currentColor}.bg-cyan-50{background-color:var(--color-cyan-50)}.bg-cyan-100{background-color:var(--color-cyan-100)}.bg-cyan-200{background-color:var(--color-cyan-200)}.bg-cyan-300{background-color:var(--color-cyan-300)}.bg-cyan-400{background-color:var(--color-cyan-400)}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-cyan-600{background-color:var(--color-cyan-600)}.bg-cyan-700{background-color:var(--color-cyan-700)}.bg-cyan-800{background-color:var(--color-cyan-800)}.bg-cyan-900{background-color:var(--color-cyan-900)}.bg-cyan-950{background-color:var(--color-cyan-950)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-emerald-200{background-color:var(--color-emerald-200)}.bg-emerald-300{background-color:var(--color-emerald-300)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-emerald-700{background-color:var(--color-emerald-700)}.bg-emerald-800{background-color:var(--color-emerald-800)}.bg-emerald-900{background-color:var(--color-emerald-900)}.bg-emerald-950{background-color:var(--color-emerald-950)}.bg-fuchsia-50{background-color:var(--color-fuchsia-50)}.bg-fuchsia-100{background-color:var(--color-fuchsia-100)}.bg-fuchsia-200{background-color:var(--color-fuchsia-200)}.bg-fuchsia-300{background-color:var(--color-fuchsia-300)}.bg-fuchsia-400{background-color:var(--color-fuchsia-400)}.bg-fuchsia-500{background-color:var(--color-fuchsia-500)}.bg-fuchsia-600{background-color:var(--color-fuchsia-600)}.bg-fuchsia-700{background-color:var(--color-fuchsia-700)}.bg-fuchsia-800{background-color:var(--color-fuchsia-800)}.bg-fuchsia-900{background-color:var(--color-fuchsia-900)}.bg-fuchsia-950{background-color:var(--color-fuchsia-950)}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-100\/60{background-color:#f3f4f699}@supports (color:color-mix(in lab,red,red)){.bg-gray-100\/60{background-color:color-mix(in oklab,var(--color-gray-100) 60%,transparent)}}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-400{background-color:var(--color-gray-400)}.bg-gray-500{background-color:var(--color-gray-500)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-950{background-color:var(--color-gray-950)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-200{background-color:var(--color-green-200)}.bg-green-300{background-color:var(--color-green-300)}.bg-green-400{background-color:var(--color-green-400)}.bg-green-500{background-color:var(--color-green-500)}.bg-green-600{background-color:var(--color-green-600)}.bg-green-700{background-color:var(--color-green-700)}.bg-green-800{background-color:var(--color-green-800)}.bg-green-900{background-color:var(--color-green-900)}.bg-green-950{background-color:var(--color-green-950)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-indigo-100{background-color:var(--color-indigo-100)}.bg-indigo-200{background-color:var(--color-indigo-200)}.bg-indigo-300{background-color:var(--color-indigo-300)}.bg-indigo-400{background-color:var(--color-indigo-400)}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-600{background-color:var(--color-indigo-600)}.bg-indigo-700{background-color:var(--color-indigo-700)}.bg-indigo-800{background-color:var(--color-indigo-800)}.bg-indigo-900{background-color:var(--color-indigo-900)}.bg-indigo-950{background-color:var(--color-indigo-950)}.bg-inherit{background-color:inherit}.bg-lime-50{background-color:var(--color-lime-50)}.bg-lime-100{background-color:var(--color-lime-100)}.bg-lime-200{background-color:var(--color-lime-200)}.bg-lime-300{background-color:var(--color-lime-300)}.bg-lime-400{background-color:var(--color-lime-400)}.bg-lime-500{background-color:var(--color-lime-500)}.bg-lime-600{background-color:var(--color-lime-600)}.bg-lime-700{background-color:var(--color-lime-700)}.bg-lime-800{background-color:var(--color-lime-800)}.bg-lime-900{background-color:var(--color-lime-900)}.bg-lime-950{background-color:var(--color-lime-950)}.bg-neutral-50{background-color:var(--color-neutral-50)}.bg-neutral-100{background-color:var(--color-neutral-100)}.bg-neutral-200{background-color:var(--color-neutral-200)}.bg-neutral-300{background-color:var(--color-neutral-300)}.bg-neutral-400{background-color:var(--color-neutral-400)}.bg-neutral-500{background-color:var(--color-neutral-500)}.bg-neutral-600{background-color:var(--color-neutral-600)}.bg-neutral-700{background-color:var(--color-neutral-700)}.bg-neutral-800{background-color:var(--color-neutral-800)}.bg-neutral-900{background-color:var(--color-neutral-900)}.bg-neutral-950{background-color:var(--color-neutral-950)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-orange-100{background-color:var(--color-orange-100)}.bg-orange-200{background-color:var(--color-orange-200)}.bg-orange-300{background-color:var(--color-orange-300)}.bg-orange-400{background-color:var(--color-orange-400)}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-600{background-color:var(--color-orange-600)}.bg-orange-700{background-color:var(--color-orange-700)}.bg-orange-800{background-color:var(--color-orange-800)}.bg-orange-900{background-color:var(--color-orange-900)}.bg-orange-950{background-color:var(--color-orange-950)}.bg-pink-50{background-color:var(--color-pink-50)}.bg-pink-100{background-color:var(--color-pink-100)}.bg-pink-200{background-color:var(--color-pink-200)}.bg-pink-300{background-color:var(--color-pink-300)}.bg-pink-400{background-color:var(--color-pink-400)}.bg-pink-500{background-color:var(--color-pink-500)}.bg-pink-600{background-color:var(--color-pink-600)}.bg-pink-700{background-color:var(--color-pink-700)}.bg-pink-800{background-color:var(--color-pink-800)}.bg-pink-900{background-color:var(--color-pink-900)}.bg-pink-950{background-color:var(--color-pink-950)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-purple-200{background-color:var(--color-purple-200)}.bg-purple-300{background-color:var(--color-purple-300)}.bg-purple-400{background-color:var(--color-purple-400)}.bg-purple-500{background-color:var(--color-purple-500)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-purple-700{background-color:var(--color-purple-700)}.bg-purple-800{background-color:var(--color-purple-800)}.bg-purple-900{background-color:var(--color-purple-900)}.bg-purple-950{background-color:var(--color-purple-950)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-200{background-color:var(--color-red-200)}.bg-red-300{background-color:var(--color-red-300)}.bg-red-400{background-color:var(--color-red-400)}.bg-red-500{background-color:var(--color-red-500)}.bg-red-500\/50{background-color:#fb2c3680}@supports (color:color-mix(in lab,red,red)){.bg-red-500\/50{background-color:color-mix(in oklab,var(--color-red-500) 50%,transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-red-700{background-color:var(--color-red-700)}.bg-red-800{background-color:var(--color-red-800)}.bg-red-900{background-color:var(--color-red-900)}.bg-red-950{background-color:var(--color-red-950)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-100{background-color:var(--color-rose-100)}.bg-rose-200{background-color:var(--color-rose-200)}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-rose-500{background-color:var(--color-rose-500)}.bg-rose-600{background-color:var(--color-rose-600)}.bg-rose-700{background-color:var(--color-rose-700)}.bg-rose-800{background-color:var(--color-rose-800)}.bg-rose-900{background-color:var(--color-rose-900)}.bg-rose-950{background-color:var(--color-rose-950)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-sky-100{background-color:var(--color-sky-100)}.bg-sky-200{background-color:var(--color-sky-200)}.bg-sky-300{background-color:var(--color-sky-300)}.bg-sky-400{background-color:var(--color-sky-400)}.bg-sky-500{background-color:var(--color-sky-500)}.bg-sky-600{background-color:var(--color-sky-600)}.bg-sky-700{background-color:var(--color-sky-700)}.bg-sky-800{background-color:var(--color-sky-800)}.bg-sky-900{background-color:var(--color-sky-900)}.bg-sky-950{background-color:var(--color-sky-950)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-100{background-color:var(--color-slate-100)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-slate-300{background-color:var(--color-slate-300)}.bg-slate-400{background-color:var(--color-slate-400)}.bg-slate-500{background-color:var(--color-slate-500)}.bg-slate-600{background-color:var(--color-slate-600)}.bg-slate-700{background-color:var(--color-slate-700)}.bg-slate-800{background-color:var(--color-slate-800)}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-950{background-color:var(--color-slate-950)}.bg-stone-50{background-color:var(--color-stone-50)}.bg-stone-100{background-color:var(--color-stone-100)}.bg-stone-200{background-color:var(--color-stone-200)}.bg-stone-300{background-color:var(--color-stone-300)}.bg-stone-400{background-color:var(--color-stone-400)}.bg-stone-500{background-color:var(--color-stone-500)}.bg-stone-600{background-color:var(--color-stone-600)}.bg-stone-700{background-color:var(--color-stone-700)}.bg-stone-800{background-color:var(--color-stone-800)}.bg-stone-900{background-color:var(--color-stone-900)}.bg-stone-950{background-color:var(--color-stone-950)}.bg-teal-50{background-color:var(--color-teal-50)}.bg-teal-100{background-color:var(--color-teal-100)}.bg-teal-200{background-color:var(--color-teal-200)}.bg-teal-300{background-color:var(--color-teal-300)}.bg-teal-400{background-color:var(--color-teal-400)}.bg-teal-500{background-color:var(--color-teal-500)}.bg-teal-600{background-color:var(--color-teal-600)}.bg-teal-700{background-color:var(--color-teal-700)}.bg-teal-800{background-color:var(--color-teal-800)}.bg-teal-900{background-color:var(--color-teal-900)}.bg-teal-950{background-color:var(--color-teal-950)}.bg-transparent{background-color:#0000}.bg-violet-50{background-color:var(--color-violet-50)}.bg-violet-100{background-color:var(--color-violet-100)}.bg-violet-200{background-color:var(--color-violet-200)}.bg-violet-300{background-color:var(--color-violet-300)}.bg-violet-400{background-color:var(--color-violet-400)}.bg-violet-500{background-color:var(--color-violet-500)}.bg-violet-600{background-color:var(--color-violet-600)}.bg-violet-700{background-color:var(--color-violet-700)}.bg-violet-800{background-color:var(--color-violet-800)}.bg-violet-900{background-color:var(--color-violet-900)}.bg-violet-950{background-color:var(--color-violet-950)}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab,red,red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white) 90%,transparent)}}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-yellow-200{background-color:var(--color-yellow-200)}.bg-yellow-300{background-color:var(--color-yellow-300)}.bg-yellow-400{background-color:var(--color-yellow-400)}.bg-yellow-500{background-color:var(--color-yellow-500)}.bg-yellow-600{background-color:var(--color-yellow-600)}.bg-yellow-700{background-color:var(--color-yellow-700)}.bg-yellow-800{background-color:var(--color-yellow-800)}.bg-yellow-900{background-color:var(--color-yellow-900)}.bg-yellow-950{background-color:var(--color-yellow-950)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-300{background-color:var(--color-zinc-300)}.bg-zinc-400{background-color:var(--color-zinc-400)}.bg-zinc-500{background-color:var(--color-zinc-500)}.bg-zinc-600{background-color:var(--color-zinc-600)}.bg-zinc-700{background-color:var(--color-zinc-700)}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-bl{--tw-gradient-position:to bottom left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-l{--tw-gradient-position:to left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-tl{--tw-gradient-position:to top left in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-tr{--tw-gradient-position:to top right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-none{background-image:none}.from-black{--tw-gradient-from:var(--color-black);--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))}.from-blue-50{--tw-gradient-from:var(--color-blue-50);--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))}.from-blue-100{--tw-gradient-from:var(--color-blue-100);--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))}.from-blue-200{--tw-gradient-from:var(--color-blue-200);--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))}.from-blue-300{--tw-gradient-from:var(--color-blue-300);--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))}.from-blue-400{--tw-gradient-from:var(--color-blue-400);--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))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--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))}.from-blue-600{--tw-gradient-from:var(--color-blue-600);--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))}.from-blue-700{--tw-gradient-from:var(--color-blue-700);--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))}.from-blue-800{--tw-gradient-from:var(--color-blue-800);--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))}.from-blue-900{--tw-gradient-from:var(--color-blue-900);--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))}.from-current{--tw-gradient-from:currentcolor;--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))}.from-gray-50{--tw-gradient-from:var(--color-gray-50);--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))}.from-gray-100{--tw-gradient-from:var(--color-gray-100);--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))}.from-gray-200{--tw-gradient-from:var(--color-gray-200);--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))}.from-gray-300{--tw-gradient-from:var(--color-gray-300);--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))}.from-gray-400{--tw-gradient-from:var(--color-gray-400);--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))}.from-gray-500{--tw-gradient-from:var(--color-gray-500);--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))}.from-gray-600{--tw-gradient-from:var(--color-gray-600);--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))}.from-gray-700{--tw-gradient-from:var(--color-gray-700);--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))}.from-gray-800{--tw-gradient-from:var(--color-gray-800);--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))}.from-gray-900{--tw-gradient-from:var(--color-gray-900);--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))}.from-green-50{--tw-gradient-from:var(--color-green-50);--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))}.from-green-100{--tw-gradient-from:var(--color-green-100);--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))}.from-green-200{--tw-gradient-from:var(--color-green-200);--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))}.from-green-300{--tw-gradient-from:var(--color-green-300);--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))}.from-green-400{--tw-gradient-from:var(--color-green-400);--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))}.from-green-500{--tw-gradient-from:var(--color-green-500);--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))}.from-green-600{--tw-gradient-from:var(--color-green-600);--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))}.from-green-700{--tw-gradient-from:var(--color-green-700);--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))}.from-green-800{--tw-gradient-from:var(--color-green-800);--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))}.from-green-900{--tw-gradient-from:var(--color-green-900);--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))}.from-indigo-50{--tw-gradient-from:var(--color-indigo-50);--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))}.from-indigo-100{--tw-gradient-from:var(--color-indigo-100);--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))}.from-indigo-200{--tw-gradient-from:var(--color-indigo-200);--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))}.from-indigo-300{--tw-gradient-from:var(--color-indigo-300);--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))}.from-indigo-400{--tw-gradient-from:var(--color-indigo-400);--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))}.from-indigo-500{--tw-gradient-from:var(--color-indigo-500);--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))}.from-indigo-600{--tw-gradient-from:var(--color-indigo-600);--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))}.from-indigo-700{--tw-gradient-from:var(--color-indigo-700);--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))}.from-indigo-800{--tw-gradient-from:var(--color-indigo-800);--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))}.from-indigo-900{--tw-gradient-from:var(--color-indigo-900);--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))}.from-inherit{--tw-gradient-from:inherit;--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))}.from-orange-50{--tw-gradient-from:var(--color-orange-50);--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))}.from-orange-100{--tw-gradient-from:var(--color-orange-100);--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))}.from-orange-200{--tw-gradient-from:var(--color-orange-200);--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))}.from-orange-300{--tw-gradient-from:var(--color-orange-300);--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))}.from-orange-400{--tw-gradient-from:var(--color-orange-400);--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))}.from-orange-500{--tw-gradient-from:var(--color-orange-500);--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))}.from-orange-600{--tw-gradient-from:var(--color-orange-600);--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))}.from-orange-700{--tw-gradient-from:var(--color-orange-700);--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))}.from-orange-800{--tw-gradient-from:var(--color-orange-800);--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))}.from-orange-900{--tw-gradient-from:var(--color-orange-900);--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))}.from-pink-50{--tw-gradient-from:var(--color-pink-50);--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))}.from-pink-100{--tw-gradient-from:var(--color-pink-100);--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))}.from-pink-200{--tw-gradient-from:var(--color-pink-200);--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))}.from-pink-300{--tw-gradient-from:var(--color-pink-300);--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))}.from-pink-400{--tw-gradient-from:var(--color-pink-400);--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))}.from-pink-500{--tw-gradient-from:var(--color-pink-500);--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))}.from-pink-600{--tw-gradient-from:var(--color-pink-600);--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))}.from-pink-700{--tw-gradient-from:var(--color-pink-700);--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))}.from-pink-800{--tw-gradient-from:var(--color-pink-800);--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))}.from-pink-900{--tw-gradient-from:var(--color-pink-900);--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))}.from-purple-50{--tw-gradient-from:var(--color-purple-50);--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))}.from-purple-100{--tw-gradient-from:var(--color-purple-100);--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))}.from-purple-200{--tw-gradient-from:var(--color-purple-200);--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))}.from-purple-300{--tw-gradient-from:var(--color-purple-300);--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))}.from-purple-400{--tw-gradient-from:var(--color-purple-400);--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))}.from-purple-500{--tw-gradient-from:var(--color-purple-500);--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))}.from-purple-600{--tw-gradient-from:var(--color-purple-600);--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))}.from-purple-700{--tw-gradient-from:var(--color-purple-700);--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))}.from-purple-800{--tw-gradient-from:var(--color-purple-800);--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))}.from-purple-900{--tw-gradient-from:var(--color-purple-900);--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))}.from-red-50{--tw-gradient-from:var(--color-red-50);--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))}.from-red-100{--tw-gradient-from:var(--color-red-100);--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))}.from-red-200{--tw-gradient-from:var(--color-red-200);--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))}.from-red-300{--tw-gradient-from:var(--color-red-300);--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))}.from-red-400{--tw-gradient-from:var(--color-red-400);--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))}.from-red-500{--tw-gradient-from:var(--color-red-500);--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))}.from-red-600{--tw-gradient-from:var(--color-red-600);--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))}.from-red-700{--tw-gradient-from:var(--color-red-700);--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))}.from-red-800{--tw-gradient-from:var(--color-red-800);--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))}.from-red-900{--tw-gradient-from:var(--color-red-900);--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))}.from-slate-50{--tw-gradient-from:var(--color-slate-50);--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))}.from-slate-100{--tw-gradient-from:var(--color-slate-100);--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))}.from-slate-200{--tw-gradient-from:var(--color-slate-200);--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))}.from-slate-300{--tw-gradient-from:var(--color-slate-300);--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))}.from-slate-400{--tw-gradient-from:var(--color-slate-400);--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))}.from-slate-500{--tw-gradient-from:var(--color-slate-500);--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))}.from-slate-600{--tw-gradient-from:var(--color-slate-600);--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))}.from-slate-700{--tw-gradient-from:var(--color-slate-700);--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))}.from-slate-800{--tw-gradient-from:var(--color-slate-800);--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))}.from-slate-900{--tw-gradient-from:var(--color-slate-900);--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))}.from-transparent{--tw-gradient-from:transparent;--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))}.from-white{--tw-gradient-from:var(--color-white);--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))}.from-yellow-50{--tw-gradient-from:var(--color-yellow-50);--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))}.from-yellow-100{--tw-gradient-from:var(--color-yellow-100);--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))}.from-yellow-200{--tw-gradient-from:var(--color-yellow-200);--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))}.from-yellow-300{--tw-gradient-from:var(--color-yellow-300);--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))}.from-yellow-400{--tw-gradient-from:var(--color-yellow-400);--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))}.from-yellow-500{--tw-gradient-from:var(--color-yellow-500);--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))}.from-yellow-600{--tw-gradient-from:var(--color-yellow-600);--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))}.from-yellow-700{--tw-gradient-from:var(--color-yellow-700);--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))}.from-yellow-800{--tw-gradient-from:var(--color-yellow-800);--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))}.from-yellow-900{--tw-gradient-from:var(--color-yellow-900);--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))}.via-black{--tw-gradient-via:var(--color-black);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-50{--tw-gradient-via:var(--color-blue-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-100{--tw-gradient-via:var(--color-blue-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-200{--tw-gradient-via:var(--color-blue-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-300{--tw-gradient-via:var(--color-blue-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-400{--tw-gradient-via:var(--color-blue-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-500{--tw-gradient-via:var(--color-blue-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-600{--tw-gradient-via:var(--color-blue-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-700{--tw-gradient-via:var(--color-blue-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-800{--tw-gradient-via:var(--color-blue-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-blue-900{--tw-gradient-via:var(--color-blue-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-current{--tw-gradient-via:currentcolor;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-50{--tw-gradient-via:var(--color-gray-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-100{--tw-gradient-via:var(--color-gray-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-200{--tw-gradient-via:var(--color-gray-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-300{--tw-gradient-via:var(--color-gray-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-400{--tw-gradient-via:var(--color-gray-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-500{--tw-gradient-via:var(--color-gray-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-600{--tw-gradient-via:var(--color-gray-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-700{--tw-gradient-via:var(--color-gray-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-800{--tw-gradient-via:var(--color-gray-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-gray-900{--tw-gradient-via:var(--color-gray-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-inherit{--tw-gradient-via:inherit;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-50{--tw-gradient-via:var(--color-slate-50);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-100{--tw-gradient-via:var(--color-slate-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-200{--tw-gradient-via:var(--color-slate-200);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-300{--tw-gradient-via:var(--color-slate-300);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-400{--tw-gradient-via:var(--color-slate-400);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-500{--tw-gradient-via:var(--color-slate-500);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-600{--tw-gradient-via:var(--color-slate-600);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-700{--tw-gradient-via:var(--color-slate-700);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-800{--tw-gradient-via:var(--color-slate-800);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-slate-900{--tw-gradient-via:var(--color-slate-900);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-transparent{--tw-gradient-via:transparent;--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-white{--tw-gradient-via:var(--color-white);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-black{--tw-gradient-to:var(--color-black);--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))}.to-blue-50{--tw-gradient-to:var(--color-blue-50);--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))}.to-blue-100{--tw-gradient-to:var(--color-blue-100);--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))}.to-blue-200{--tw-gradient-to:var(--color-blue-200);--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))}.to-blue-300{--tw-gradient-to:var(--color-blue-300);--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))}.to-blue-400{--tw-gradient-to:var(--color-blue-400);--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))}.to-blue-500{--tw-gradient-to:var(--color-blue-500);--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))}.to-blue-600{--tw-gradient-to:var(--color-blue-600);--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))}.to-blue-700{--tw-gradient-to:var(--color-blue-700);--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))}.to-blue-800{--tw-gradient-to:var(--color-blue-800);--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))}.to-blue-900{--tw-gradient-to:var(--color-blue-900);--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))}.to-current{--tw-gradient-to:currentcolor;--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))}.to-gray-50{--tw-gradient-to:var(--color-gray-50);--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))}.to-gray-100{--tw-gradient-to:var(--color-gray-100);--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))}.to-gray-200{--tw-gradient-to:var(--color-gray-200);--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))}.to-gray-300{--tw-gradient-to:var(--color-gray-300);--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))}.to-gray-400{--tw-gradient-to:var(--color-gray-400);--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))}.to-gray-500{--tw-gradient-to:var(--color-gray-500);--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))}.to-gray-600{--tw-gradient-to:var(--color-gray-600);--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))}.to-gray-700{--tw-gradient-to:var(--color-gray-700);--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))}.to-gray-800{--tw-gradient-to:var(--color-gray-800);--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))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--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))}.to-indigo-50{--tw-gradient-to:var(--color-indigo-50);--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))}.to-inherit{--tw-gradient-to:inherit;--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))}.to-purple-50{--tw-gradient-to:var(--color-purple-50);--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))}.to-purple-100{--tw-gradient-to:var(--color-purple-100);--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))}.to-purple-200{--tw-gradient-to:var(--color-purple-200);--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))}.to-purple-300{--tw-gradient-to:var(--color-purple-300);--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))}.to-purple-400{--tw-gradient-to:var(--color-purple-400);--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))}.to-purple-500{--tw-gradient-to:var(--color-purple-500);--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))}.to-purple-600{--tw-gradient-to:var(--color-purple-600);--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))}.to-purple-700{--tw-gradient-to:var(--color-purple-700);--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))}.to-purple-800{--tw-gradient-to:var(--color-purple-800);--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))}.to-purple-900{--tw-gradient-to:var(--color-purple-900);--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))}.to-red-50{--tw-gradient-to:var(--color-red-50);--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))}.to-red-100{--tw-gradient-to:var(--color-red-100);--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))}.to-red-200{--tw-gradient-to:var(--color-red-200);--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))}.to-red-300{--tw-gradient-to:var(--color-red-300);--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))}.to-red-400{--tw-gradient-to:var(--color-red-400);--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))}.to-red-500{--tw-gradient-to:var(--color-red-500);--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))}.to-red-600{--tw-gradient-to:var(--color-red-600);--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))}.to-red-700{--tw-gradient-to:var(--color-red-700);--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))}.to-red-800{--tw-gradient-to:var(--color-red-800);--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))}.to-red-900{--tw-gradient-to:var(--color-red-900);--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))}.to-slate-50{--tw-gradient-to:var(--color-slate-50);--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))}.to-slate-100{--tw-gradient-to:var(--color-slate-100);--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))}.to-slate-200{--tw-gradient-to:var(--color-slate-200);--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))}.to-slate-300{--tw-gradient-to:var(--color-slate-300);--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))}.to-slate-400{--tw-gradient-to:var(--color-slate-400);--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))}.to-slate-500{--tw-gradient-to:var(--color-slate-500);--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))}.to-slate-600{--tw-gradient-to:var(--color-slate-600);--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))}.to-slate-700{--tw-gradient-to:var(--color-slate-700);--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))}.to-slate-800{--tw-gradient-to:var(--color-slate-800);--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))}.to-slate-900{--tw-gradient-to:var(--color-slate-900);--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))}.to-transparent{--tw-gradient-to:transparent;--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))}.to-white{--tw-gradient-to:var(--color-white);--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))}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.object-fill{object-fit:fill}.object-none{object-fit:none}.object-scale-down{object-fit:scale-down}.object-bottom{object-position:bottom}.object-center{object-position:center}.object-left{object-position:left}.object-left-bottom{object-position:left bottom}.object-left-top{object-position:left top}.object-right{object-position:right}.object-right-bottom{object-position:right bottom}.object-right-top{object-position:right top}.object-top{object-position:top}.\!p-0{padding:calc(var(--spacing) * 0)!important}.\!p-1{padding:calc(var(--spacing) * 1)!important}.\!p-2{padding:calc(var(--spacing) * 2)!important}.\!p-3{padding:calc(var(--spacing) * 3)!important}.\!p-4{padding:calc(var(--spacing) * 4)!important}.\!p-5{padding:calc(var(--spacing) * 5)!important}.\!p-6{padding:calc(var(--spacing) * 6)!important}.\!p-8{padding:calc(var(--spacing) * 8)!important}.\!p-10{padding:calc(var(--spacing) * 10)!important}.\!p-12{padding:calc(var(--spacing) * 12)!important}.\!p-16{padding:calc(var(--spacing) * 16)!important}.p-0{padding:calc(var(--spacing) * 0)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:calc(var(--spacing) * 1)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.p-10{padding:calc(var(--spacing) * 10)}.p-12{padding:calc(var(--spacing) * 12)}.p-16{padding:calc(var(--spacing) * 16)}.p-20{padding:calc(var(--spacing) * 20)}.p-24{padding:calc(var(--spacing) * 24)}.\!px-0{padding-inline:calc(var(--spacing) * 0)!important}.\!px-1{padding-inline:calc(var(--spacing) * 1)!important}.\!px-2{padding-inline:calc(var(--spacing) * 2)!important}.\!px-3{padding-inline:calc(var(--spacing) * 3)!important}.\!px-4{padding-inline:calc(var(--spacing) * 4)!important}.\!px-5{padding-inline:calc(var(--spacing) * 5)!important}.\!px-6{padding-inline:calc(var(--spacing) * 6)!important}.\!px-8{padding-inline:calc(var(--spacing) * 8)!important}.\!px-10{padding-inline:calc(var(--spacing) * 10)!important}.\!px-12{padding-inline:calc(var(--spacing) * 12)!important}.\!px-16{padding-inline:calc(var(--spacing) * 16)!important}.px-0{padding-inline:calc(var(--spacing) * 0)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.px-10{padding-inline:calc(var(--spacing) * 10)}.px-12{padding-inline:calc(var(--spacing) * 12)}.px-16{padding-inline:calc(var(--spacing) * 16)}.\!py-0{padding-block:calc(var(--spacing) * 0)!important}.\!py-1{padding-block:calc(var(--spacing) * 1)!important}.\!py-2{padding-block:calc(var(--spacing) * 2)!important}.\!py-3{padding-block:calc(var(--spacing) * 3)!important}.\!py-4{padding-block:calc(var(--spacing) * 4)!important}.\!py-5{padding-block:calc(var(--spacing) * 5)!important}.\!py-6{padding-block:calc(var(--spacing) * 6)!important}.\!py-8{padding-block:calc(var(--spacing) * 8)!important}.\!py-10{padding-block:calc(var(--spacing) * 10)!important}.\!py-12{padding-block:calc(var(--spacing) * 12)!important}.\!py-16{padding-block:calc(var(--spacing) * 16)!important}.py-0{padding-block:calc(var(--spacing) * 0)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-0{padding-top:calc(var(--spacing) * 0)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-5{padding-top:calc(var(--spacing) * 5)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-10{padding-top:calc(var(--spacing) * 10)}.pt-12{padding-top:calc(var(--spacing) * 12)}.pt-16{padding-top:calc(var(--spacing) * 16)}.pr-0{padding-right:calc(var(--spacing) * 0)}.pr-1{padding-right:calc(var(--spacing) * 1)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pr-5{padding-right:calc(var(--spacing) * 5)}.pr-6{padding-right:calc(var(--spacing) * 6)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-10{padding-right:calc(var(--spacing) * 10)}.pr-12{padding-right:calc(var(--spacing) * 12)}.pr-16{padding-right:calc(var(--spacing) * 16)}.pb-0{padding-bottom:calc(var(--spacing) * 0)}.pb-1{padding-bottom:calc(var(--spacing) * 1)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pb-6{padding-bottom:calc(var(--spacing) * 6)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pb-10{padding-bottom:calc(var(--spacing) * 10)}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pb-16{padding-bottom:calc(var(--spacing) * 16)}.pb-24{padding-bottom:calc(var(--spacing) * 24)}.pl-0{padding-left:calc(var(--spacing) * 0)}.pl-1{padding-left:calc(var(--spacing) * 1)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-7{padding-left:calc(var(--spacing) * 7)}.pl-8{padding-left:calc(var(--spacing) * 8)}.pl-10{padding-left:calc(var(--spacing) * 10)}.pl-12{padding-left:calc(var(--spacing) * 12)}.pl-16{padding-left:calc(var(--spacing) * 16)}.\!text-center{text-align:center!important}.\!text-justify{text-align:justify!important}.\!text-left{text-align:left!important}.\!text-right{text-align:right!important}.text-center{text-align:center}.text-end{text-align:end}.text-justify{text-align:justify}.text-left{text-align:left}.text-right{text-align:right}.text-start{text-align:start}.indent-0{text-indent:calc(var(--spacing) * 0)}.indent-1{text-indent:calc(var(--spacing) * 1)}.indent-2{text-indent:calc(var(--spacing) * 2)}.indent-4{text-indent:calc(var(--spacing) * 4)}.indent-8{text-indent:calc(var(--spacing) * 8)}.align-baseline{vertical-align:baseline}.align-bottom{vertical-align:bottom}.align-middle{vertical-align:middle}.align-sub{vertical-align:sub}.align-super{vertical-align:super}.align-text-bottom{vertical-align:text-bottom}.align-text-top{vertical-align:text-top}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.\!text-2xl{font-size:var(--text-2xl)!important;line-height:var(--tw-leading,var(--text-2xl--line-height))!important}.\!text-3xl{font-size:var(--text-3xl)!important;line-height:var(--tw-leading,var(--text-3xl--line-height))!important}.\!text-4xl{font-size:var(--text-4xl)!important;line-height:var(--tw-leading,var(--text-4xl--line-height))!important}.\!text-base{font-size:var(--text-base)!important;line-height:var(--tw-leading,var(--text-base--line-height))!important}.\!text-lg{font-size:var(--text-lg)!important;line-height:var(--tw-leading,var(--text-lg--line-height))!important}.\!text-sm{font-size:var(--text-sm)!important;line-height:var(--tw-leading,var(--text-sm--line-height))!important}.\!text-xl{font-size:var(--text-xl)!important;line-height:var(--tw-leading,var(--text-xl--line-height))!important}.\!text-xs{font-size:var(--text-xs)!important;line-height:var(--tw-leading,var(--text-xs--line-height))!important}.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-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.text-8xl{font-size:var(--text-8xl);line-height:var(--tw-leading,var(--text-8xl--line-height))}.text-9xl{font-size:var(--text-9xl);line-height:var(--tw-leading,var(--text-9xl--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))}.text-\[2rem\]{font-size:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-3{--tw-leading:calc(var(--spacing) * 3);line-height:calc(var(--spacing) * 3)}.leading-4{--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4)}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-7{--tw-leading:calc(var(--spacing) * 7);line-height:calc(var(--spacing) * 7)}.leading-8{--tw-leading:calc(var(--spacing) * 8);line-height:calc(var(--spacing) * 8)}.leading-9{--tw-leading:calc(var(--spacing) * 9);line-height:calc(var(--spacing) * 9)}.leading-10{--tw-leading:calc(var(--spacing) * 10);line-height:calc(var(--spacing) * 10)}.leading-loose{--tw-leading:var(--leading-loose);line-height:var(--leading-loose)}.leading-none{--tw-leading:1;line-height:1}.leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.\!font-bold{--tw-font-weight:var(--font-weight-bold)!important;font-weight:var(--font-weight-bold)!important}.\!font-medium{--tw-font-weight:var(--font-weight-medium)!important;font-weight:var(--font-weight-medium)!important}.\!font-normal{--tw-font-weight:var(--font-weight-normal)!important;font-weight:var(--font-weight-normal)!important}.\!font-semibold{--tw-font-weight:var(--font-weight-semibold)!important;font-weight:var(--font-weight-semibold)!important}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-normal{overflow-wrap:normal;word-break:normal}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.break-keep{word-break:keep-all}.text-clip{text-overflow:clip}.text-ellipsis{text-overflow:ellipsis}.whitespace-break-spaces{white-space:break-spaces}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-line{white-space:pre-line}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-50{color:var(--color-amber-50)}.text-amber-100{color:var(--color-amber-100)}.text-amber-200{color:var(--color-amber-200)}.text-amber-300{color:var(--color-amber-300)}.text-amber-400{color:var(--color-amber-400)}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-amber-950{color:var(--color-amber-950)}.text-black{color:var(--color-black)}.text-blue-50{color:var(--color-blue-50)}.text-blue-100{color:var(--color-blue-100)}.text-blue-200{color:var(--color-blue-200)}.text-blue-300{color:var(--color-blue-300)}.text-blue-400{color:var(--color-blue-400)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-blue-800{color:var(--color-blue-800)}.text-blue-900{color:var(--color-blue-900)}.text-blue-950{color:var(--color-blue-950)}.text-current{color:currentColor}.text-cyan-50{color:var(--color-cyan-50)}.text-cyan-100{color:var(--color-cyan-100)}.text-cyan-200{color:var(--color-cyan-200)}.text-cyan-300{color:var(--color-cyan-300)}.text-cyan-400{color:var(--color-cyan-400)}.text-cyan-500{color:var(--color-cyan-500)}.text-cyan-600{color:var(--color-cyan-600)}.text-cyan-700{color:var(--color-cyan-700)}.text-cyan-800{color:var(--color-cyan-800)}.text-cyan-900{color:var(--color-cyan-900)}.text-cyan-950{color:var(--color-cyan-950)}.text-emerald-50{color:var(--color-emerald-50)}.text-emerald-100{color:var(--color-emerald-100)}.text-emerald-200{color:var(--color-emerald-200)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-500{color:var(--color-emerald-500)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-emerald-900{color:var(--color-emerald-900)}.text-emerald-950{color:var(--color-emerald-950)}.text-fuchsia-50{color:var(--color-fuchsia-50)}.text-fuchsia-100{color:var(--color-fuchsia-100)}.text-fuchsia-200{color:var(--color-fuchsia-200)}.text-fuchsia-300{color:var(--color-fuchsia-300)}.text-fuchsia-400{color:var(--color-fuchsia-400)}.text-fuchsia-500{color:var(--color-fuchsia-500)}.text-fuchsia-600{color:var(--color-fuchsia-600)}.text-fuchsia-700{color:var(--color-fuchsia-700)}.text-fuchsia-800{color:var(--color-fuchsia-800)}.text-fuchsia-900{color:var(--color-fuchsia-900)}.text-fuchsia-950{color:var(--color-fuchsia-950)}.text-gray-50{color:var(--color-gray-50)}.text-gray-100{color:var(--color-gray-100)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-gray-950{color:var(--color-gray-950)}.text-green-50{color:var(--color-green-50)}.text-green-100{color:var(--color-green-100)}.text-green-200{color:var(--color-green-200)}.text-green-300{color:var(--color-green-300)}.text-green-400{color:var(--color-green-400)}.text-green-500{color:var(--color-green-500)}.text-green-600{color:var(--color-green-600)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-green-900{color:var(--color-green-900)}.text-green-950{color:var(--color-green-950)}.text-indigo-50{color:var(--color-indigo-50)}.text-indigo-100{color:var(--color-indigo-100)}.text-indigo-200{color:var(--color-indigo-200)}.text-indigo-300{color:var(--color-indigo-300)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-500{color:var(--color-indigo-500)}.text-indigo-600{color:var(--color-indigo-600)}.text-indigo-700{color:var(--color-indigo-700)}.text-indigo-800{color:var(--color-indigo-800)}.text-indigo-900{color:var(--color-indigo-900)}.text-indigo-950{color:var(--color-indigo-950)}.text-inherit{color:inherit}.text-lime-50{color:var(--color-lime-50)}.text-lime-100{color:var(--color-lime-100)}.text-lime-200{color:var(--color-lime-200)}.text-lime-300{color:var(--color-lime-300)}.text-lime-400{color:var(--color-lime-400)}.text-lime-500{color:var(--color-lime-500)}.text-lime-600{color:var(--color-lime-600)}.text-lime-700{color:var(--color-lime-700)}.text-lime-800{color:var(--color-lime-800)}.text-lime-900{color:var(--color-lime-900)}.text-lime-950{color:var(--color-lime-950)}.text-neutral-50{color:var(--color-neutral-50)}.text-neutral-100{color:var(--color-neutral-100)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-700{color:var(--color-neutral-700)}.text-neutral-800{color:var(--color-neutral-800)}.text-neutral-900{color:var(--color-neutral-900)}.text-neutral-950{color:var(--color-neutral-950)}.text-orange-50{color:var(--color-orange-50)}.text-orange-100{color:var(--color-orange-100)}.text-orange-200{color:var(--color-orange-200)}.text-orange-300{color:var(--color-orange-300)}.text-orange-400{color:var(--color-orange-400)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-orange-800{color:var(--color-orange-800)}.text-orange-900{color:var(--color-orange-900)}.text-orange-950{color:var(--color-orange-950)}.text-pink-50{color:var(--color-pink-50)}.text-pink-100{color:var(--color-pink-100)}.text-pink-200{color:var(--color-pink-200)}.text-pink-300{color:var(--color-pink-300)}.text-pink-400{color:var(--color-pink-400)}.text-pink-500{color:var(--color-pink-500)}.text-pink-600{color:var(--color-pink-600)}.text-pink-700{color:var(--color-pink-700)}.text-pink-800{color:var(--color-pink-800)}.text-pink-900{color:var(--color-pink-900)}.text-pink-950{color:var(--color-pink-950)}.text-purple-50{color:var(--color-purple-50)}.text-purple-100{color:var(--color-purple-100)}.text-purple-200{color:var(--color-purple-200)}.text-purple-300{color:var(--color-purple-300)}.text-purple-400{color:var(--color-purple-400)}.text-purple-500{color:var(--color-purple-500)}.text-purple-600{color:var(--color-purple-600)}.text-purple-700{color:var(--color-purple-700)}.text-purple-800{color:var(--color-purple-800)}.text-purple-900{color:var(--color-purple-900)}.text-purple-950{color:var(--color-purple-950)}.text-red-50{color:var(--color-red-50)}.text-red-100{color:var(--color-red-100)}.text-red-200{color:var(--color-red-200)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-red-900{color:var(--color-red-900)}.text-red-950{color:var(--color-red-950)}.text-rose-50{color:var(--color-rose-50)}.text-rose-100{color:var(--color-rose-100)}.text-rose-200{color:var(--color-rose-200)}.text-rose-300{color:var(--color-rose-300)}.text-rose-400{color:var(--color-rose-400)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-rose-700{color:var(--color-rose-700)}.text-rose-800{color:var(--color-rose-800)}.text-rose-900{color:var(--color-rose-900)}.text-rose-950{color:var(--color-rose-950)}.text-sky-50{color:var(--color-sky-50)}.text-sky-100{color:var(--color-sky-100)}.text-sky-200{color:var(--color-sky-200)}.text-sky-300{color:var(--color-sky-300)}.text-sky-400{color:var(--color-sky-400)}.text-sky-500{color:var(--color-sky-500)}.text-sky-600{color:var(--color-sky-600)}.text-sky-700{color:var(--color-sky-700)}.text-sky-800{color:var(--color-sky-800)}.text-sky-900{color:var(--color-sky-900)}.text-sky-950{color:var(--color-sky-950)}.text-slate-50{color:var(--color-slate-50)}.text-slate-100{color:var(--color-slate-100)}.text-slate-200{color:var(--color-slate-200)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-slate-800{color:var(--color-slate-800)}.text-slate-900{color:var(--color-slate-900)}.text-slate-950{color:var(--color-slate-950)}.text-stone-50{color:var(--color-stone-50)}.text-stone-100{color:var(--color-stone-100)}.text-stone-200{color:var(--color-stone-200)}.text-stone-300{color:var(--color-stone-300)}.text-stone-400{color:var(--color-stone-400)}.text-stone-500{color:var(--color-stone-500)}.text-stone-600{color:var(--color-stone-600)}.text-stone-700{color:var(--color-stone-700)}.text-stone-800{color:var(--color-stone-800)}.text-stone-900{color:var(--color-stone-900)}.text-stone-950{color:var(--color-stone-950)}.text-teal-50{color:var(--color-teal-50)}.text-teal-100{color:var(--color-teal-100)}.text-teal-200{color:var(--color-teal-200)}.text-teal-300{color:var(--color-teal-300)}.text-teal-400{color:var(--color-teal-400)}.text-teal-500{color:var(--color-teal-500)}.text-teal-600{color:var(--color-teal-600)}.text-teal-700{color:var(--color-teal-700)}.text-teal-800{color:var(--color-teal-800)}.text-teal-900{color:var(--color-teal-900)}.text-teal-950{color:var(--color-teal-950)}.text-transparent{color:#0000}.text-violet-50{color:var(--color-violet-50)}.text-violet-100{color:var(--color-violet-100)}.text-violet-200{color:var(--color-violet-200)}.text-violet-300{color:var(--color-violet-300)}.text-violet-400{color:var(--color-violet-400)}.text-violet-500{color:var(--color-violet-500)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-violet-800{color:var(--color-violet-800)}.text-violet-900{color:var(--color-violet-900)}.text-violet-950{color:var(--color-violet-950)}.text-white{color:var(--color-white)}.text-yellow-50{color:var(--color-yellow-50)}.text-yellow-100{color:var(--color-yellow-100)}.text-yellow-200{color:var(--color-yellow-200)}.text-yellow-300{color:var(--color-yellow-300)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-500{color:var(--color-yellow-500)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.text-yellow-900{color:var(--color-yellow-900)}.text-yellow-950{color:var(--color-yellow-950)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.text-zinc-950{color:var(--color-zinc-950)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.not-italic{font-style:normal}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.placeholder-gray-400::placeholder{color:var(--color-gray-400)}.accent-blue-500{accent-color:var(--color-blue-500)}.opacity-0{opacity:0}.opacity-5{opacity:.05}.opacity-10{opacity:.1}.opacity-15{opacity:.15}.opacity-20{opacity:.2}.opacity-25{opacity:.25}.opacity-30{opacity:.3}.opacity-35{opacity:.35}.opacity-40{opacity:.4}.opacity-45{opacity:.45}.opacity-50{opacity:.5}.opacity-55{opacity:.55}.opacity-60{opacity:.6}.opacity-65{opacity:.65}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}.opacity-95{opacity:.95}.opacity-100{opacity:1}.bg-blend-color{background-blend-mode:color}.bg-blend-color-burn{background-blend-mode:color-burn}.bg-blend-color-dodge{background-blend-mode:color-dodge}.bg-blend-darken{background-blend-mode:darken}.bg-blend-difference{background-blend-mode:difference}.bg-blend-exclusion{background-blend-mode:exclusion}.bg-blend-hard-light{background-blend-mode:hard-light}.bg-blend-hue{background-blend-mode:hue}.bg-blend-lighten{background-blend-mode:lighten}.bg-blend-luminosity{background-blend-mode:luminosity}.bg-blend-multiply{background-blend-mode:multiply}.bg-blend-normal{background-blend-mode:normal}.bg-blend-overlay{background-blend-mode:overlay}.bg-blend-saturation{background-blend-mode:saturation}.bg-blend-screen{background-blend-mode:screen}.bg-blend-soft-light{background-blend-mode:soft-light}.mix-blend-color{mix-blend-mode:color}.mix-blend-color-burn{mix-blend-mode:color-burn}.mix-blend-color-dodge{mix-blend-mode:color-dodge}.mix-blend-darken{mix-blend-mode:darken}.mix-blend-difference{mix-blend-mode:difference}.mix-blend-exclusion{mix-blend-mode:exclusion}.mix-blend-hard-light{mix-blend-mode:hard-light}.mix-blend-hue{mix-blend-mode:hue}.mix-blend-lighten{mix-blend-mode:lighten}.mix-blend-luminosity{mix-blend-mode:luminosity}.mix-blend-multiply{mix-blend-mode:multiply}.mix-blend-normal{mix-blend-mode:normal}.mix-blend-overlay{mix-blend-mode:overlay}.mix-blend-plus-darker{mix-blend-mode:plus-darker}.mix-blend-plus-lighter{mix-blend-mode:plus-lighter}.mix-blend-saturation{mix-blend-mode:saturation}.mix-blend-screen{mix-blend-mode:screen}.mix-blend-soft-light{mix-blend-mode:soft-light}.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-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);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-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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-none{--tw-shadow:0 0 #0000;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)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + 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)}.ring-1{--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)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + 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)}.ring-8{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + 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)}.shadow-purple-200{--tw-shadow-color:oklch(90.2% .063 306.703)}@supports (color:color-mix(in lab,red,red)){.shadow-purple-200{--tw-shadow-color:color-mix(in oklab, var(--color-purple-200) var(--tw-shadow-alpha), transparent)}}.ring-black{--tw-ring-color:var(--color-black)}.ring-blue-50{--tw-ring-color:var(--color-blue-50)}.ring-blue-100{--tw-ring-color:var(--color-blue-100)}.ring-blue-200{--tw-ring-color:var(--color-blue-200)}.ring-blue-300{--tw-ring-color:var(--color-blue-300)}.ring-blue-400{--tw-ring-color:var(--color-blue-400)}.ring-blue-500{--tw-ring-color:var(--color-blue-500)}.ring-blue-600{--tw-ring-color:var(--color-blue-600)}.ring-blue-700{--tw-ring-color:var(--color-blue-700)}.ring-blue-800{--tw-ring-color:var(--color-blue-800)}.ring-blue-900{--tw-ring-color:var(--color-blue-900)}.ring-current{--tw-ring-color:currentcolor}.ring-gray-50{--tw-ring-color:var(--color-gray-50)}.ring-gray-100{--tw-ring-color:var(--color-gray-100)}.ring-gray-200{--tw-ring-color:var(--color-gray-200)}.ring-gray-300{--tw-ring-color:var(--color-gray-300)}.ring-gray-400{--tw-ring-color:var(--color-gray-400)}.ring-gray-500{--tw-ring-color:var(--color-gray-500)}.ring-gray-600{--tw-ring-color:var(--color-gray-600)}.ring-gray-700{--tw-ring-color:var(--color-gray-700)}.ring-gray-800{--tw-ring-color:var(--color-gray-800)}.ring-gray-900{--tw-ring-color:var(--color-gray-900)}.ring-inherit{--tw-ring-color:inherit}.ring-slate-50{--tw-ring-color:var(--color-slate-50)}.ring-slate-100{--tw-ring-color:var(--color-slate-100)}.ring-slate-200{--tw-ring-color:var(--color-slate-200)}.ring-slate-300{--tw-ring-color:var(--color-slate-300)}.ring-slate-400{--tw-ring-color:var(--color-slate-400)}.ring-slate-500{--tw-ring-color:var(--color-slate-500)}.ring-slate-600{--tw-ring-color:var(--color-slate-600)}.ring-slate-700{--tw-ring-color:var(--color-slate-700)}.ring-slate-800{--tw-ring-color:var(--color-slate-800)}.ring-slate-900{--tw-ring-color:var(--color-slate-900)}.ring-transparent{--tw-ring-color:transparent}.ring-white{--tw-ring-color:var(--color-white)}.ring-offset-0{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-1{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-4{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-8{--tw-ring-offset-width:8px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.ring-offset-black{--tw-ring-offset-color:var(--color-black)}.ring-offset-current{--tw-ring-offset-color:currentcolor}.ring-offset-inherit{--tw-ring-offset-color:inherit}.ring-offset-transparent{--tw-ring-offset-color:transparent}.ring-offset-white{--tw-ring-offset-color:var(--color-white)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.outline-0{outline-style:var(--tw-outline-style);outline-width:0}.outline-1{outline-style:var(--tw-outline-style);outline-width:1px}.outline-2{outline-style:var(--tw-outline-style);outline-width:2px}.outline-4{outline-style:var(--tw-outline-style);outline-width:4px}.outline-8{outline-style:var(--tw-outline-style);outline-width:8px}.outline-offset-0{outline-offset:0px}.outline-offset-1{outline-offset:1px}.outline-offset-2{outline-offset:2px}.outline-offset-4{outline-offset:4px}.outline-offset-8{outline-offset:8px}.outline-black{outline-color:var(--color-black)}.outline-current{outline-color:currentColor}.outline-inherit{outline-color:inherit}.outline-transparent{outline-color:#0000}.outline-white{outline-color:var(--color-white)}.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,)}.blur-2xl{--tw-blur:blur(var(--blur-2xl));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,)}.blur-3xl{--tw-blur:blur(var(--blur-3xl));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,)}.blur-lg{--tw-blur:blur(var(--blur-lg));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,)}.blur-md{--tw-blur:blur(var(--blur-md));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,)}.blur-none{--tw-blur: ;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,)}.blur-sm{--tw-blur:blur(var(--blur-sm));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,)}.blur-xl{--tw-blur:blur(var(--blur-xl));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,)}.brightness-0{--tw-brightness:brightness(0%);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,)}.brightness-50{--tw-brightness:brightness(50%);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,)}.brightness-75{--tw-brightness:brightness(75%);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,)}.brightness-90{--tw-brightness:brightness(90%);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,)}.brightness-95{--tw-brightness:brightness(95%);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,)}.brightness-100{--tw-brightness:brightness(100%);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,)}.brightness-105{--tw-brightness:brightness(105%);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,)}.brightness-110{--tw-brightness:brightness(110%);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,)}.brightness-125{--tw-brightness:brightness(125%);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,)}.brightness-150{--tw-brightness:brightness(150%);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,)}.brightness-200{--tw-brightness:brightness(200%);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,)}.contrast-0{--tw-contrast:contrast(0%);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,)}.contrast-50{--tw-contrast:contrast(50%);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,)}.contrast-75{--tw-contrast:contrast(75%);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,)}.contrast-100{--tw-contrast:contrast(100%);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,)}.contrast-125{--tw-contrast:contrast(125%);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,)}.contrast-150{--tw-contrast:contrast(150%);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,)}.contrast-200{--tw-contrast:contrast(200%);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,)}.drop-shadow-2xl{--tw-drop-shadow-size:drop-shadow(0 25px 25px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-2xl));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-lg{--tw-drop-shadow-size:drop-shadow(0 4px 4px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-lg));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-md{--tw-drop-shadow-size:drop-shadow(0 3px 3px var(--tw-drop-shadow-color,#0000001f));--tw-drop-shadow:drop-shadow(var(--drop-shadow-md));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-sm{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#00000026));--tw-drop-shadow:drop-shadow(var(--drop-shadow-sm));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-xl{--tw-drop-shadow-size:drop-shadow(0 9px 7px var(--tw-drop-shadow-color,#0000001a));--tw-drop-shadow:drop-shadow(var(--drop-shadow-xl));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-none{--tw-drop-shadow: ;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,)}.grayscale{--tw-grayscale:grayscale(100%);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,)}.grayscale-0{--tw-grayscale:grayscale(0%);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,)}.-hue-rotate-15{--tw-hue-rotate:hue-rotate( -15deg );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,)}.-hue-rotate-30{--tw-hue-rotate:hue-rotate( -30deg );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,)}.-hue-rotate-60{--tw-hue-rotate:hue-rotate( -60deg );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,)}.-hue-rotate-90{--tw-hue-rotate:hue-rotate( -90deg );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,)}.-hue-rotate-180{--tw-hue-rotate:hue-rotate( -180deg );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,)}.hue-rotate-0{--tw-hue-rotate:hue-rotate(0deg);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,)}.hue-rotate-15{--tw-hue-rotate:hue-rotate(15deg);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,)}.hue-rotate-30{--tw-hue-rotate:hue-rotate(30deg);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,)}.hue-rotate-60{--tw-hue-rotate:hue-rotate(60deg);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,)}.hue-rotate-90{--tw-hue-rotate:hue-rotate(90deg);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,)}.hue-rotate-180{--tw-hue-rotate:hue-rotate(180deg);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,)}.invert{--tw-invert:invert(100%);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,)}.invert-0{--tw-invert:invert(0%);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,)}.saturate-0{--tw-saturate:saturate(0%);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,)}.saturate-50{--tw-saturate:saturate(50%);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,)}.saturate-100{--tw-saturate:saturate(100%);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,)}.saturate-150{--tw-saturate:saturate(150%);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,)}.saturate-200{--tw-saturate:saturate(200%);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,)}.sepia{--tw-sepia:sepia(100%);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,)}.sepia-0{--tw-sepia:sepia(0%);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,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-2xl{--tw-backdrop-blur:blur(var(--blur-2xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-3xl{--tw-backdrop-blur:blur(var(--blur-3xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-lg{--tw-backdrop-blur:blur(var(--blur-lg));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-none{--tw-backdrop-blur: ;-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-0{--tw-backdrop-brightness:brightness(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-50{--tw-backdrop-brightness:brightness(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-75{--tw-backdrop-brightness:brightness(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-90{--tw-backdrop-brightness:brightness(90%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-95{--tw-backdrop-brightness:brightness(95%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-100{--tw-backdrop-brightness:brightness(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-105{--tw-backdrop-brightness:brightness(105%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-110{--tw-backdrop-brightness:brightness(110%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-125{--tw-backdrop-brightness:brightness(125%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-150{--tw-backdrop-brightness:brightness(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-brightness-200{--tw-backdrop-brightness:brightness(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-0{--tw-backdrop-contrast:contrast(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-50{--tw-backdrop-contrast:contrast(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-75{--tw-backdrop-contrast:contrast(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-100{--tw-backdrop-contrast:contrast(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-125{--tw-backdrop-contrast:contrast(125%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-150{--tw-backdrop-contrast:contrast(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-contrast-200{--tw-backdrop-contrast:contrast(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-grayscale{--tw-backdrop-grayscale:grayscale(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-grayscale-0{--tw-backdrop-grayscale:grayscale(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-0{--tw-backdrop-hue-rotate:hue-rotate(0deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-15{--tw-backdrop-hue-rotate:hue-rotate(15deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-30{--tw-backdrop-hue-rotate:hue-rotate(30deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-60{--tw-backdrop-hue-rotate:hue-rotate(60deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-90{--tw-backdrop-hue-rotate:hue-rotate(90deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-hue-rotate-180{--tw-backdrop-hue-rotate:hue-rotate(180deg);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-invert{--tw-backdrop-invert:invert(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-invert-0{--tw-backdrop-invert:invert(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-0{--tw-backdrop-opacity:opacity(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-5{--tw-backdrop-opacity:opacity(5%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-10{--tw-backdrop-opacity:opacity(10%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-20{--tw-backdrop-opacity:opacity(20%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-25{--tw-backdrop-opacity:opacity(25%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-30{--tw-backdrop-opacity:opacity(30%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-40{--tw-backdrop-opacity:opacity(40%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-50{--tw-backdrop-opacity:opacity(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-60{--tw-backdrop-opacity:opacity(60%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-70{--tw-backdrop-opacity:opacity(70%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-75{--tw-backdrop-opacity:opacity(75%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-80{--tw-backdrop-opacity:opacity(80%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-90{--tw-backdrop-opacity:opacity(90%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-95{--tw-backdrop-opacity:opacity(95%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-opacity-100{--tw-backdrop-opacity:opacity(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-0{--tw-backdrop-saturate:saturate(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-50{--tw-backdrop-saturate:saturate(50%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-100{--tw-backdrop-saturate:saturate(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-150{--tw-backdrop-saturate:saturate(150%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-saturate-200{--tw-backdrop-saturate:saturate(200%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-sepia{--tw-backdrop-sepia:sepia(100%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.backdrop-sepia-0{--tw-backdrop-sepia:sepia(0%);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.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))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-none{transition-property:none}.delay-75{transition-delay:75ms}.delay-100{transition-delay:.1s}.delay-150{transition-delay:.15s}.delay-200{transition-delay:.2s}.delay-300{transition-delay:.3s}.delay-500{transition-delay:.5s}.delay-700{transition-delay:.7s}.delay-1000{transition-delay:1s}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-500{--tw-duration:.5s;transition-duration:.5s}.duration-700{--tw-duration:.7s;transition-duration:.7s}.duration-1000{--tw-duration:1s;transition-duration:1s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-linear{--tw-ease:linear;transition-timing-function:linear}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-dashed{--tw-outline-style:dashed;outline-style:dashed}.outline-dotted{--tw-outline-style:dotted;outline-style:dotted}.outline-double{--tw-outline-style:double;outline-style:double}.outline-none{--tw-outline-style:none;outline-style:none}.select-all{-webkit-user-select:all;user-select:all}.select-auto{-webkit-user-select:auto;user-select:auto}.select-none{-webkit-user-select:none;user-select:none}.select-text{-webkit-user-select:text;user-select:text}:where(.divide-x-reverse>:not(:last-child)){--tw-divide-x-reverse:1}.ring-inset{--tw-ring-inset:inset}@media(hover:hover){.group-hover\:visible:is(:where(.group):hover *){visibility:visible}.group-hover\:translate-x-1:is(:where(.group):hover *){--tw-translate-x:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:translate-y-1:is(:where(.group):hover *){--tw-translate-y:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.group-hover\:scale-100:is(:where(.group):hover *){--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:scale-105:is(:where(.group):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-hover\:bg-black\/40:is(:where(.group):hover *){background-color:#0006}@supports (color:color-mix(in lab,red,red)){.group-hover\:bg-black\/40:is(:where(.group):hover *){background-color:color-mix(in oklab,var(--color-black) 40%,transparent)}}.group-hover\:bg-blue-100:is(:where(.group):hover *){background-color:var(--color-blue-100)}.group-hover\:bg-blue-500:is(:where(.group):hover *){background-color:var(--color-blue-500)}.group-hover\:bg-blue-600:is(:where(.group):hover *){background-color:var(--color-blue-600)}.group-hover\:bg-gray-100:is(:where(.group):hover *){background-color:var(--color-gray-100)}.group-hover\:bg-gray-200:is(:where(.group):hover *){background-color:var(--color-gray-200)}.group-hover\:bg-gray-700:is(:where(.group):hover *){background-color:var(--color-gray-700)}.group-hover\:bg-gray-800:is(:where(.group):hover *){background-color:var(--color-gray-800)}.group-hover\:text-black:is(:where(.group):hover *){color:var(--color-black)}.group-hover\:text-blue-500:is(:where(.group):hover *){color:var(--color-blue-500)}.group-hover\:text-blue-600:is(:where(.group):hover *){color:var(--color-blue-600)}.group-hover\:text-blue-700:is(:where(.group):hover *){color:var(--color-blue-700)}.group-hover\:text-gray-700:is(:where(.group):hover *){color:var(--color-gray-700)}.group-hover\:text-gray-800:is(:where(.group):hover *){color:var(--color-gray-800)}.group-hover\:text-gray-900:is(:where(.group):hover *){color:var(--color-gray-900)}.group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.group-focus\:border-blue-500:is(:where(.group):focus *){border-color:var(--color-blue-500)}.group-focus\:ring-2:is(:where(.group):focus *){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.peer-checked\:border-blue-500:is(:where(.peer):checked~*){border-color:var(--color-blue-500)}.peer-checked\:bg-blue-500:is(:where(.peer):checked~*){background-color:var(--color-blue-500)}.peer-invalid\:border-red-500:is(:where(.peer):invalid~*){border-color:var(--color-red-500)}.peer-invalid\:text-red-500:is(:where(.peer):invalid~*){color:var(--color-red-500)}.peer-focus\:border-blue-500:is(:where(.peer):focus~*){border-color:var(--color-blue-500)}.peer-focus\:ring-2:is(:where(.peer):focus~*){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.first-letter\:text-2xl:first-letter{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.first-letter\:font-bold:first-letter{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.first-letter\:text-blue-600:first-letter{color:var(--color-blue-600)}.first-line\:font-semibold:first-line{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.first-line\:text-gray-700:first-line{color:var(--color-gray-700)}.marker\:text-blue-500 ::marker{color:var(--color-blue-500)}.marker\:text-blue-500::marker{color:var(--color-blue-500)}.marker\:text-blue-500 ::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-blue-500::-webkit-details-marker{color:var(--color-blue-500)}.marker\:text-gray-500 ::marker{color:var(--color-gray-500)}.marker\:text-gray-500::marker{color:var(--color-gray-500)}.marker\:text-gray-500 ::-webkit-details-marker{color:var(--color-gray-500)}.marker\:text-gray-500::-webkit-details-marker{color:var(--color-gray-500)}.selection\:bg-blue-200 ::selection{background-color:var(--color-blue-200)}.selection\:bg-blue-200::selection{background-color:var(--color-blue-200)}.selection\:text-blue-900 ::selection{color:var(--color-blue-900)}.selection\:text-blue-900::selection{color:var(--color-blue-900)}.file\:mr-4::file-selector-button{margin-right:calc(var(--spacing) * 4)}.file\:cursor-pointer::file-selector-button{cursor:pointer}.file\:rounded-full::file-selector-button{border-radius:3.40282e38px}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-blue-50::file-selector-button{background-color:var(--color-blue-50)}.file\:px-4::file-selector-button{padding-inline:calc(var(--spacing) * 4)}.file\:py-2::file-selector-button{padding-block:calc(var(--spacing) * 2)}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-semibold::file-selector-button{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.file\:text-blue-700::file-selector-button{color:var(--color-blue-700)}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:inset-0:before{content:var(--tw-content);inset:calc(var(--spacing) * 0)}.before\:block:before{content:var(--tw-content);display:block}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:inset-0:after{content:var(--tw-content);inset:calc(var(--spacing) * 0)}.after\:clear-both:after{content:var(--tw-content);clear:both}.after\:block:after{content:var(--tw-content);display:block}.after\:h-4:after{content:var(--tw-content);height:calc(var(--spacing) * 4)}.after\:h-5:after{content:var(--tw-content);height:calc(var(--spacing) * 5)}.after\:h-6:after{content:var(--tw-content);height:calc(var(--spacing) * 6)}.after\:w-4:after{content:var(--tw-content);width:calc(var(--spacing) * 4)}.after\:w-5:after{content:var(--tw-content);width:calc(var(--spacing) * 5)}.after\:w-6:after{content:var(--tw-content);width:calc(var(--spacing) * 6)}.after\:content-\[\'\'\]:after{--tw-content:"";content:var(--tw-content)}.first\:mt-0:first-child{margin-top:calc(var(--spacing) * 0)}.first\:rounded-t-lg:first-child{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.first\:rounded-l:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.first\:rounded-l-lg:first-child{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.first\:border-t-0:first-child{border-top-style:var(--tw-border-style);border-top-width:0}.first\:pt-0:first-child{padding-top:calc(var(--spacing) * 0)}.last\:mb-0:last-child{margin-bottom:calc(var(--spacing) * 0)}.last\:rounded-r:last-child{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.last\:rounded-r-lg:last-child{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.last\:rounded-b-lg:last-child{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.last\:border-r:last-child{border-right-style:var(--tw-border-style);border-right-width:1px}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.last\:pb-0:last-child{padding-bottom:calc(var(--spacing) * 0)}.odd\:bg-gray-50:nth-child(odd){background-color:var(--color-gray-50)}.odd\:bg-gray-100:nth-child(odd){background-color:var(--color-gray-100)}.odd\:bg-white:nth-child(odd){background-color:var(--color-white)}.even\:bg-gray-50:nth-child(2n){background-color:var(--color-gray-50)}.even\:bg-gray-100:nth-child(2n){background-color:var(--color-gray-100)}.even\:bg-white:nth-child(2n){background-color:var(--color-white)}.checked\:border-blue-500:checked{border-color:var(--color-blue-500)}.checked\:border-blue-600:checked{border-color:var(--color-blue-600)}.checked\:bg-blue-500:checked{background-color:var(--color-blue-500)}.checked\:bg-blue-600:checked{background-color:var(--color-blue-600)}.checked\:bg-green-500:checked{background-color:var(--color-green-500)}.checked\:text-white:checked{color:var(--color-white)}.indeterminate\:border-gray-400:indeterminate{border-color:var(--color-gray-400)}.indeterminate\:bg-gray-300:indeterminate{background-color:var(--color-gray-300)}.placeholder-shown\:border-gray-300:placeholder-shown{border-color:var(--color-gray-300)}.autofill\:bg-yellow-100:autofill{background-color:var(--color-yellow-100)}.autofill\:text-black:autofill{color:var(--color-black)}.valid\:border-green-500:valid{border-color:var(--color-green-500)}.valid\:text-green-600:valid{color:var(--color-green-600)}.valid\:ring-green-500:valid{--tw-ring-color:var(--color-green-500)}.invalid\:border-red-500:invalid{border-color:var(--color-red-500)}.invalid\:text-red-600:invalid{color:var(--color-red-600)}.invalid\:ring-red-500:invalid{--tw-ring-color:var(--color-red-500)}.read-only\:cursor-not-allowed:read-only{cursor:not-allowed}.read-only\:bg-gray-50:read-only{background-color:var(--color-gray-50)}.read-only\:bg-gray-100:read-only{background-color:var(--color-gray-100)}.empty\:hidden:empty{display:none}.focus-within\:border-blue-500:focus-within{border-color:var(--color-blue-500)}.focus-within\:border-gray-500:focus-within{border-color:var(--color-gray-500)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.focus-within\:ring-blue-500:focus-within{--tw-ring-color:var(--color-blue-500)}.focus-within\:ring-gray-500:focus-within{--tw-ring-color:var(--color-gray-500)}@media(hover:hover){.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:scale-95:hover{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-100:hover{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:border-blue-50:hover{border-color:var(--color-blue-50)}.hover\:border-blue-100:hover{border-color:var(--color-blue-100)}.hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.hover\:border-blue-600:hover{border-color:var(--color-blue-600)}.hover\:border-blue-700:hover{border-color:var(--color-blue-700)}.hover\:border-blue-800:hover{border-color:var(--color-blue-800)}.hover\:border-blue-900:hover{border-color:var(--color-blue-900)}.hover\:border-gray-50:hover{border-color:var(--color-gray-50)}.hover\:border-gray-100:hover{border-color:var(--color-gray-100)}.hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.hover\:border-gray-600:hover{border-color:var(--color-gray-600)}.hover\:border-gray-700:hover{border-color:var(--color-gray-700)}.hover\:border-gray-800:hover{border-color:var(--color-gray-800)}.hover\:border-gray-900:hover{border-color:var(--color-gray-900)}.hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-black\/10:hover{background-color:#0000001a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/10:hover{background-color:color-mix(in oklab,var(--color-black) 10%,transparent)}}.hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.hover\:bg-blue-300:hover{background-color:var(--color-blue-300)}.hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-blue-800:hover{background-color:var(--color-blue-800)}.hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.hover\:bg-green-50:hover{background-color:var(--color-green-50)}.hover\:bg-green-100:hover{background-color:var(--color-green-100)}.hover\:bg-green-200:hover{background-color:var(--color-green-200)}.hover\:bg-green-300:hover{background-color:var(--color-green-300)}.hover\:bg-green-400:hover{background-color:var(--color-green-400)}.hover\:bg-green-500:hover{background-color:var(--color-green-500)}.hover\:bg-green-600:hover{background-color:var(--color-green-600)}.hover\:bg-green-700:hover{background-color:var(--color-green-700)}.hover\:bg-green-800:hover{background-color:var(--color-green-800)}.hover\:bg-green-900:hover{background-color:var(--color-green-900)}.hover\:bg-indigo-50:hover{background-color:var(--color-indigo-50)}.hover\:bg-indigo-100:hover{background-color:var(--color-indigo-100)}.hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.hover\:bg-indigo-300:hover{background-color:var(--color-indigo-300)}.hover\:bg-indigo-400:hover{background-color:var(--color-indigo-400)}.hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.hover\:bg-indigo-800:hover{background-color:var(--color-indigo-800)}.hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.hover\:bg-orange-300:hover{background-color:var(--color-orange-300)}.hover\:bg-orange-400:hover{background-color:var(--color-orange-400)}.hover\:bg-orange-500:hover{background-color:var(--color-orange-500)}.hover\:bg-orange-600:hover{background-color:var(--color-orange-600)}.hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.hover\:bg-pink-50:hover{background-color:var(--color-pink-50)}.hover\:bg-pink-100:hover{background-color:var(--color-pink-100)}.hover\:bg-pink-200:hover{background-color:var(--color-pink-200)}.hover\:bg-pink-300:hover{background-color:var(--color-pink-300)}.hover\:bg-pink-400:hover{background-color:var(--color-pink-400)}.hover\:bg-pink-500:hover{background-color:var(--color-pink-500)}.hover\:bg-pink-600:hover{background-color:var(--color-pink-600)}.hover\:bg-pink-700:hover{background-color:var(--color-pink-700)}.hover\:bg-pink-800:hover{background-color:var(--color-pink-800)}.hover\:bg-pink-900:hover{background-color:var(--color-pink-900)}.hover\:bg-purple-50:hover{background-color:var(--color-purple-50)}.hover\:bg-purple-100:hover{background-color:var(--color-purple-100)}.hover\:bg-purple-200:hover{background-color:var(--color-purple-200)}.hover\:bg-purple-300:hover{background-color:var(--color-purple-300)}.hover\:bg-purple-400:hover{background-color:var(--color-purple-400)}.hover\:bg-purple-500:hover{background-color:var(--color-purple-500)}.hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-purple-800:hover{background-color:var(--color-purple-800)}.hover\:bg-purple-900:hover{background-color:var(--color-purple-900)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-100:hover{background-color:var(--color-red-100)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-red-300:hover{background-color:var(--color-red-300)}.hover\:bg-red-400:hover{background-color:var(--color-red-400)}.hover\:bg-red-500:hover{background-color:var(--color-red-500)}.hover\:bg-red-600:hover{background-color:var(--color-red-600)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-red-800:hover{background-color:var(--color-red-800)}.hover\:bg-red-900:hover{background-color:var(--color-red-900)}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}.hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}.hover\:bg-slate-600:hover{background-color:var(--color-slate-600)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.hover\:bg-slate-900:hover{background-color:var(--color-slate-900)}.hover\:bg-transparent:hover{background-color:#0000}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:bg-yellow-50:hover{background-color:var(--color-yellow-50)}.hover\:bg-yellow-100:hover{background-color:var(--color-yellow-100)}.hover\:bg-yellow-200:hover{background-color:var(--color-yellow-200)}.hover\:bg-yellow-300:hover{background-color:var(--color-yellow-300)}.hover\:bg-yellow-400:hover{background-color:var(--color-yellow-400)}.hover\:bg-yellow-500:hover{background-color:var(--color-yellow-500)}.hover\:bg-yellow-600:hover{background-color:var(--color-yellow-600)}.hover\:bg-yellow-700:hover{background-color:var(--color-yellow-700)}.hover\:bg-yellow-800:hover{background-color:var(--color-yellow-800)}.hover\:bg-yellow-900:hover{background-color:var(--color-yellow-900)}.hover\:text-black:hover{color:var(--color-black)}.hover\:text-blue-50:hover{color:var(--color-blue-50)}.hover\:text-blue-100:hover{color:var(--color-blue-100)}.hover\:text-blue-200:hover{color:var(--color-blue-200)}.hover\:text-blue-300:hover{color:var(--color-blue-300)}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-blue-500:hover{color:var(--color-blue-500)}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-blue-800:hover{color:var(--color-blue-800)}.hover\:text-blue-900:hover{color:var(--color-blue-900)}.hover\:text-gray-50:hover{color:var(--color-gray-50)}.hover\:text-gray-100:hover{color:var(--color-gray-100)}.hover\:text-gray-200:hover{color:var(--color-gray-200)}.hover\:text-gray-300:hover{color:var(--color-gray-300)}.hover\:text-gray-400:hover{color:var(--color-gray-400)}.hover\:text-gray-500:hover{color:var(--color-gray-500)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-gray-800:hover{color:var(--color-gray-800)}.hover\:text-gray-900:hover{color:var(--color-gray-900)}.hover\:text-green-50:hover{color:var(--color-green-50)}.hover\:text-green-100:hover{color:var(--color-green-100)}.hover\:text-green-200:hover{color:var(--color-green-200)}.hover\:text-green-300:hover{color:var(--color-green-300)}.hover\:text-green-400:hover{color:var(--color-green-400)}.hover\:text-green-500:hover{color:var(--color-green-500)}.hover\:text-green-600:hover{color:var(--color-green-600)}.hover\:text-green-700:hover{color:var(--color-green-700)}.hover\:text-green-800:hover{color:var(--color-green-800)}.hover\:text-green-900:hover{color:var(--color-green-900)}.hover\:text-purple-50:hover{color:var(--color-purple-50)}.hover\:text-purple-100:hover{color:var(--color-purple-100)}.hover\:text-purple-200:hover{color:var(--color-purple-200)}.hover\:text-purple-300:hover{color:var(--color-purple-300)}.hover\:text-purple-400:hover{color:var(--color-purple-400)}.hover\:text-purple-500:hover{color:var(--color-purple-500)}.hover\:text-purple-600:hover{color:var(--color-purple-600)}.hover\:text-purple-700:hover{color:var(--color-purple-700)}.hover\:text-purple-800:hover{color:var(--color-purple-800)}.hover\:text-purple-900:hover{color:var(--color-purple-900)}.hover\:text-red-50:hover{color:var(--color-red-50)}.hover\:text-red-100:hover{color:var(--color-red-100)}.hover\:text-red-200:hover{color:var(--color-red-200)}.hover\:text-red-300:hover{color:var(--color-red-300)}.hover\:text-red-400:hover{color:var(--color-red-400)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-red-800:hover{color:var(--color-red-800)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:text-slate-50:hover{color:var(--color-slate-50)}.hover\:text-slate-100:hover{color:var(--color-slate-100)}.hover\:text-slate-200:hover{color:var(--color-slate-200)}.hover\:text-slate-300:hover{color:var(--color-slate-300)}.hover\:text-slate-400:hover{color:var(--color-slate-400)}.hover\:text-slate-500:hover{color:var(--color-slate-500)}.hover\:text-slate-600:hover{color:var(--color-slate-600)}.hover\:text-slate-700:hover{color:var(--color-slate-700)}.hover\:text-slate-800:hover{color:var(--color-slate-800)}.hover\:text-slate-900:hover{color:var(--color-slate-900)}.hover\:text-white:hover{color:var(--color-white)}.hover\:line-through:hover{text-decoration-line:line-through}.hover\:no-underline:hover{text-decoration-line:none}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-0:hover{opacity:0}.hover\:opacity-25:hover{opacity:.25}.hover\:opacity-50:hover{opacity:.5}.hover\:opacity-75:hover{opacity:.75}.hover\:opacity-100:hover{opacity:1}.hover\:shadow:hover{--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)}.hover\:shadow-2xl:hover{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--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)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}.hover\:shadow-none:hover{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-sm:hover{--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)}.hover\:shadow-xl:hover{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.hover\:ring-0:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + 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)}.hover\:ring-1:hover{--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)}.hover\:ring-2:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.hover\:ring-4:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + 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)}.hover\:ring-8:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + 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)}.hover\:ring-blue-500:hover{--tw-ring-color:var(--color-blue-500)}.hover\:ring-gray-500:hover{--tw-ring-color:var(--color-gray-500)}.hover\:ring-green-500:hover{--tw-ring-color:var(--color-green-500)}.hover\:ring-red-500:hover{--tw-ring-color:var(--color-red-500)}.file\:hover\:bg-blue-100::file-selector-button:hover{background-color:var(--color-blue-100)}}.focus\:border-blue-50:focus{border-color:var(--color-blue-50)}.focus\:border-blue-100:focus{border-color:var(--color-blue-100)}.focus\:border-blue-200:focus{border-color:var(--color-blue-200)}.focus\:border-blue-300:focus{border-color:var(--color-blue-300)}.focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:border-blue-600:focus{border-color:var(--color-blue-600)}.focus\:border-blue-700:focus{border-color:var(--color-blue-700)}.focus\:border-blue-800:focus{border-color:var(--color-blue-800)}.focus\:border-blue-900:focus{border-color:var(--color-blue-900)}.focus\:border-gray-50:focus{border-color:var(--color-gray-50)}.focus\:border-gray-100:focus{border-color:var(--color-gray-100)}.focus\:border-gray-200:focus{border-color:var(--color-gray-200)}.focus\:border-gray-300:focus{border-color:var(--color-gray-300)}.focus\:border-gray-400:focus{border-color:var(--color-gray-400)}.focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.focus\:border-gray-600:focus{border-color:var(--color-gray-600)}.focus\:border-gray-700:focus{border-color:var(--color-gray-700)}.focus\:border-gray-800:focus{border-color:var(--color-gray-800)}.focus\:border-gray-900:focus{border-color:var(--color-gray-900)}.focus\:border-green-500:focus{border-color:var(--color-green-500)}.focus\:border-indigo-500:focus{border-color:var(--color-indigo-500)}.focus\:border-purple-500:focus{border-color:var(--color-purple-500)}.focus\:border-red-500:focus{border-color:var(--color-red-500)}.focus\:border-transparent:focus{border-color:#0000}.focus\:border-yellow-500:focus{border-color:var(--color-yellow-500)}.focus\:bg-black:focus{background-color:var(--color-black)}.focus\:bg-blue-50:focus{background-color:var(--color-blue-50)}.focus\:bg-blue-100:focus{background-color:var(--color-blue-100)}.focus\:bg-blue-200:focus{background-color:var(--color-blue-200)}.focus\:bg-blue-300:focus{background-color:var(--color-blue-300)}.focus\:bg-blue-400:focus{background-color:var(--color-blue-400)}.focus\:bg-blue-500:focus{background-color:var(--color-blue-500)}.focus\:bg-blue-600:focus{background-color:var(--color-blue-600)}.focus\:bg-blue-700:focus{background-color:var(--color-blue-700)}.focus\:bg-blue-800:focus{background-color:var(--color-blue-800)}.focus\:bg-blue-900:focus{background-color:var(--color-blue-900)}.focus\:bg-gray-50:focus{background-color:var(--color-gray-50)}.focus\:bg-gray-100:focus{background-color:var(--color-gray-100)}.focus\:bg-gray-200:focus{background-color:var(--color-gray-200)}.focus\:bg-gray-300:focus{background-color:var(--color-gray-300)}.focus\:bg-gray-400:focus{background-color:var(--color-gray-400)}.focus\:bg-gray-500:focus{background-color:var(--color-gray-500)}.focus\:bg-gray-600:focus{background-color:var(--color-gray-600)}.focus\:bg-gray-700:focus{background-color:var(--color-gray-700)}.focus\:bg-gray-800:focus{background-color:var(--color-gray-800)}.focus\:bg-gray-900:focus{background-color:var(--color-gray-900)}.focus\:bg-transparent:focus{background-color:#0000}.focus\:bg-white:focus{background-color:var(--color-white)}.focus\:text-black:focus{color:var(--color-black)}.focus\:text-blue-50:focus{color:var(--color-blue-50)}.focus\:text-blue-100:focus{color:var(--color-blue-100)}.focus\:text-blue-200:focus{color:var(--color-blue-200)}.focus\:text-blue-300:focus{color:var(--color-blue-300)}.focus\:text-blue-400:focus{color:var(--color-blue-400)}.focus\:text-blue-500:focus{color:var(--color-blue-500)}.focus\:text-blue-600:focus{color:var(--color-blue-600)}.focus\:text-blue-700:focus{color:var(--color-blue-700)}.focus\:text-blue-800:focus{color:var(--color-blue-800)}.focus\:text-blue-900:focus{color:var(--color-blue-900)}.focus\:text-gray-50:focus{color:var(--color-gray-50)}.focus\:text-gray-100:focus{color:var(--color-gray-100)}.focus\:text-gray-200:focus{color:var(--color-gray-200)}.focus\:text-gray-300:focus{color:var(--color-gray-300)}.focus\:text-gray-400:focus{color:var(--color-gray-400)}.focus\:text-gray-500:focus{color:var(--color-gray-500)}.focus\:text-gray-600:focus{color:var(--color-gray-600)}.focus\:text-gray-700:focus{color:var(--color-gray-700)}.focus\:text-gray-800:focus{color:var(--color-gray-800)}.focus\:text-gray-900:focus{color:var(--color-gray-900)}.focus\:text-white:focus{color:var(--color-white)}.focus\:shadow:focus{--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)}.focus\:shadow-lg:focus{--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)}.focus\:shadow-md:focus{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}.focus\:shadow-none:focus{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:shadow-sm:focus{--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)}.focus\:shadow-xl:focus{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + 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)}.focus\:ring-1:focus{--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)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.focus\:ring-4:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + 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)}.focus\:ring-8:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(8px + 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)}.focus\:ring-amber-500:focus{--tw-ring-color:var(--color-amber-500)}.focus\:ring-blue-50:focus{--tw-ring-color:var(--color-blue-50)}.focus\:ring-blue-100:focus{--tw-ring-color:var(--color-blue-100)}.focus\:ring-blue-200:focus{--tw-ring-color:var(--color-blue-200)}.focus\:ring-blue-300:focus{--tw-ring-color:var(--color-blue-300)}.focus\:ring-blue-400:focus{--tw-ring-color:var(--color-blue-400)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-600:focus{--tw-ring-color:var(--color-blue-600)}.focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.focus\:ring-blue-800:focus{--tw-ring-color:var(--color-blue-800)}.focus\:ring-blue-900:focus{--tw-ring-color:var(--color-blue-900)}.focus\:ring-current:focus{--tw-ring-color:currentcolor}.focus\:ring-gray-50:focus{--tw-ring-color:var(--color-gray-50)}.focus\:ring-gray-100:focus{--tw-ring-color:var(--color-gray-100)}.focus\:ring-gray-200:focus{--tw-ring-color:var(--color-gray-200)}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.focus\:ring-gray-700:focus{--tw-ring-color:var(--color-gray-700)}.focus\:ring-gray-800:focus{--tw-ring-color:var(--color-gray-800)}.focus\:ring-gray-900:focus{--tw-ring-color:var(--color-gray-900)}.focus\:ring-green-500:focus{--tw-ring-color:var(--color-green-500)}.focus\:ring-indigo-500:focus{--tw-ring-color:var(--color-indigo-500)}.focus\:ring-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.focus\:ring-slate-400:focus{--tw-ring-color:var(--color-slate-400)}.focus\:ring-transparent:focus{--tw-ring-color:transparent}.focus\:ring-yellow-500:focus{--tw-ring-color:var(--color-yellow-500)}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-4:focus{--tw-ring-offset-width:4px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-gray-100:focus{--tw-ring-offset-color:var(--color-gray-100)}.focus\:ring-offset-gray-900:focus{--tw-ring-offset-color:var(--color-gray-900)}.focus\:ring-offset-white:focus{--tw-ring-offset-color:var(--color-white)}.focus\:outline:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-0:focus{outline-style:var(--tw-outline-style);outline-width:0}.focus\:outline-1:focus{outline-style:var(--tw-outline-style);outline-width:1px}.focus\:outline-2:focus{outline-style:var(--tw-outline-style);outline-width:2px}.focus\:outline-4:focus{outline-style:var(--tw-outline-style);outline-width:4px}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus\:ring-inset:focus{--tw-ring-inset:inset}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + 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)}.focus-visible\:ring-blue-500:focus-visible{--tw-ring-color:var(--color-blue-500)}.focus-visible\:ring-gray-500:focus-visible{--tw-ring-color:var(--color-gray-500)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:outline:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:scale-100:active{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.active\:cursor-grabbing:active{cursor:grabbing}.active\:bg-blue-600:active{background-color:var(--color-blue-600)}.active\:bg-blue-700:active{background-color:var(--color-blue-700)}.active\:bg-blue-800:active{background-color:var(--color-blue-800)}.active\:bg-gray-100:active{background-color:var(--color-gray-100)}.active\:bg-gray-200:active{background-color:var(--color-gray-200)}.active\:bg-gray-300:active{background-color:var(--color-gray-300)}.active\:bg-gray-700:active{background-color:var(--color-gray-700)}.active\:bg-gray-800:active{background-color:var(--color-gray-800)}.active\:bg-green-600:active{background-color:var(--color-green-600)}.active\:bg-green-700:active{background-color:var(--color-green-700)}.active\:bg-red-600:active{background-color:var(--color-red-600)}.active\:bg-red-700:active{background-color:var(--color-red-700)}.active\:opacity-75:active{opacity:.75}.active\:opacity-100:active{opacity:1}.disabled\:cursor-default:disabled{cursor:default}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:border-gray-200:disabled{border-color:var(--color-gray-200)}.disabled\:border-gray-300:disabled{border-color:var(--color-gray-300)}.disabled\:bg-blue-400:disabled{background-color:var(--color-blue-400)}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:bg-gray-200:disabled{background-color:var(--color-gray-200)}.disabled\:bg-gray-300:disabled{background-color:var(--color-gray-300)}.disabled\:bg-gray-400:disabled{background-color:var(--color-gray-400)}.disabled\:text-gray-300:disabled{color:var(--color-gray-300)}.disabled\:text-gray-400:disabled{color:var(--color-gray-400)}.disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.disabled\:no-underline:disabled{text-decoration-line:none}.disabled\:opacity-25:disabled{opacity:.25}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-75:disabled{opacity:.75}@media(hover:hover){.disabled\:hover\:bg-indigo-600:disabled:hover{background-color:var(--color-indigo-600)}.disabled\:hover\:bg-transparent:disabled:hover{background-color:#0000}}@media(min-width:40rem){.sm\:col-span-1{grid-column:span 1/span 1}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:col-span-3{grid-column:span 3/span 3}.sm\:col-span-4{grid-column:span 4/span 4}.sm\:col-span-6{grid-column:span 6/span 6}.sm\:col-span-12{grid-column:span 12/span 12}.sm\:col-span-full{grid-column:1/-1}.sm\:m-0{margin:calc(var(--spacing) * 0)}.sm\:m-1{margin:calc(var(--spacing) * 1)}.sm\:m-2{margin:calc(var(--spacing) * 2)}.sm\:m-3{margin:calc(var(--spacing) * 3)}.sm\:m-4{margin:calc(var(--spacing) * 4)}.sm\:m-auto{margin:auto}.sm\:mx-auto{margin-inline:auto}.sm\:my-auto{margin-block:auto}.sm\:mt-6{margin-top:calc(var(--spacing) * 6)}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:inline{display:inline}.sm\:inline-block{display:inline-block}.sm\:inline-flex{display:inline-flex}.sm\:w-1\/2{width:50%}.sm\:w-1\/3{width:33.3333%}.sm\:w-1\/4{width:25%}.sm\:w-2\/3{width:66.6667%}.sm\:w-3\/4{width:75%}.sm\:w-40{width:calc(var(--spacing) * 40)}.sm\:w-auto{width:auto}.sm\:w-full{width:100%}.sm\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.sm\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.sm\:flex-col{flex-direction:column}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:items-start{align-items:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:justify-center{justify-content:center}.sm\:justify-end{justify-content:flex-end}.sm\:justify-start{justify-content:flex-start}.sm\:gap-0{gap:calc(var(--spacing) * 0)}.sm\:gap-1{gap:calc(var(--spacing) * 1)}.sm\:gap-2{gap:calc(var(--spacing) * 2)}.sm\:gap-3{gap:calc(var(--spacing) * 3)}.sm\:gap-4{gap:calc(var(--spacing) * 4)}.sm\:gap-5{gap:calc(var(--spacing) * 5)}.sm\:gap-6{gap:calc(var(--spacing) * 6)}.sm\:gap-8{gap:calc(var(--spacing) * 8)}:where(.sm\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.sm\: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(.sm\: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(.sm\: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(.sm\: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(.sm\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}.sm\:p-0{padding:calc(var(--spacing) * 0)}.sm\:p-1{padding:calc(var(--spacing) * 1)}.sm\:p-2{padding:calc(var(--spacing) * 2)}.sm\:p-3{padding:calc(var(--spacing) * 3)}.sm\:p-4{padding:calc(var(--spacing) * 4)}.sm\:p-5{padding:calc(var(--spacing) * 5)}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:p-8{padding:calc(var(--spacing) * 8)}.sm\:px-0{padding-inline:calc(var(--spacing) * 0)}.sm\:px-1{padding-inline:calc(var(--spacing) * 1)}.sm\:px-2{padding-inline:calc(var(--spacing) * 2)}.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-4{padding-inline:calc(var(--spacing) * 4)}.sm\:px-5{padding-inline:calc(var(--spacing) * 5)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:px-8{padding-inline:calc(var(--spacing) * 8)}.sm\:py-0{padding-block:calc(var(--spacing) * 0)}.sm\:py-1{padding-block:calc(var(--spacing) * 1)}.sm\:py-2{padding-block:calc(var(--spacing) * 2)}.sm\:py-3{padding-block:calc(var(--spacing) * 3)}.sm\:py-4{padding-block:calc(var(--spacing) * 4)}.sm\:py-5{padding-block:calc(var(--spacing) * 5)}.sm\:py-6{padding-block:calc(var(--spacing) * 6)}.sm\:py-8{padding-block:calc(var(--spacing) * 8)}.sm\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.sm\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.sm\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:48rem){.md\:absolute{position:absolute}.md\:fixed{position:fixed}.md\:relative{position:relative}.md\:static{position:static}.md\:sticky{position:sticky}.md\:inset-0{inset:calc(var(--spacing) * 0)}.md\:top-0{top:calc(var(--spacing) * 0)}.md\:right-0{right:calc(var(--spacing) * 0)}.md\:bottom-0{bottom:calc(var(--spacing) * 0)}.md\:left-0{left:calc(var(--spacing) * 0)}.md\:order-1{order:1}.md\:order-2{order:2}.md\:order-3{order:3}.md\:order-first{order:-9999}.md\:order-last{order:9999}.md\:order-none{order:0}.md\:col-span-1{grid-column:span 1/span 1}.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-3{grid-column:span 3/span 3}.md\:col-span-4{grid-column:span 4/span 4}.md\:col-span-5{grid-column:span 5/span 5}.md\:col-span-6{grid-column:span 6/span 6}.md\:col-span-12{grid-column:span 12/span 12}.md\:col-span-full{grid-column:1/-1}.md\:row-span-1{grid-row:span 1/span 1}.md\:row-span-2{grid-row:span 2/span 2}.md\:row-span-3{grid-row:span 3/span 3}.md\:row-span-4{grid-row:span 4/span 4}.md\:row-span-full{grid-row:1/-1}.md\:m-0{margin:calc(var(--spacing) * 0)}.md\:m-1{margin:calc(var(--spacing) * 1)}.md\:m-2{margin:calc(var(--spacing) * 2)}.md\:m-3{margin:calc(var(--spacing) * 3)}.md\:m-4{margin:calc(var(--spacing) * 4)}.md\:m-auto{margin:auto}.md\:mx-auto{margin-inline:auto}.md\:my-auto{margin-block:auto}.md\:mt-0{margin-top:calc(var(--spacing) * 0)}.md\:mt-1{margin-top:calc(var(--spacing) * 1)}.md\:mt-2{margin-top:calc(var(--spacing) * 2)}.md\:mt-3{margin-top:calc(var(--spacing) * 3)}.md\:mt-4{margin-top:calc(var(--spacing) * 4)}.md\:mt-6{margin-top:calc(var(--spacing) * 6)}.md\:mt-8{margin-top:calc(var(--spacing) * 8)}.md\:mr-0{margin-right:calc(var(--spacing) * 0)}.md\:mr-1{margin-right:calc(var(--spacing) * 1)}.md\:mr-2{margin-right:calc(var(--spacing) * 2)}.md\:mr-3{margin-right:calc(var(--spacing) * 3)}.md\:mr-4{margin-right:calc(var(--spacing) * 4)}.md\:mr-6{margin-right:calc(var(--spacing) * 6)}.md\:mr-8{margin-right:calc(var(--spacing) * 8)}.md\:mr-auto{margin-right:auto}.md\:mb-0{margin-bottom:calc(var(--spacing) * 0)}.md\:mb-1{margin-bottom:calc(var(--spacing) * 1)}.md\:mb-2{margin-bottom:calc(var(--spacing) * 2)}.md\:mb-3{margin-bottom:calc(var(--spacing) * 3)}.md\:mb-4{margin-bottom:calc(var(--spacing) * 4)}.md\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.md\:mb-8{margin-bottom:calc(var(--spacing) * 8)}.md\:ml-0{margin-left:calc(var(--spacing) * 0)}.md\:ml-1{margin-left:calc(var(--spacing) * 1)}.md\:ml-2{margin-left:calc(var(--spacing) * 2)}.md\:ml-3{margin-left:calc(var(--spacing) * 3)}.md\:ml-4{margin-left:calc(var(--spacing) * 4)}.md\:ml-6{margin-left:calc(var(--spacing) * 6)}.md\:ml-8{margin-left:calc(var(--spacing) * 8)}.md\:ml-auto{margin-left:auto}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:inline{display:inline}.md\:inline-block{display:inline-block}.md\:inline-flex{display:inline-flex}.md\:h-20{height:calc(var(--spacing) * 20)}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.3333%}.md\:w-1\/4{width:25%}.md\:w-1\/5{width:20%}.md\:w-2\/3{width:66.6667%}.md\:w-2\/5{width:40%}.md\:w-3\/4{width:75%}.md\:w-3\/5{width:60%}.md\:w-4\/5{width:80%}.md\:w-20{width:calc(var(--spacing) * 20)}.md\:w-40{width:calc(var(--spacing) * 40)}.md\:w-48{width:calc(var(--spacing) * 48)}.md\:w-64{width:calc(var(--spacing) * 64)}.md\:w-72{width:calc(var(--spacing) * 72)}.md\:w-80{width:calc(var(--spacing) * 80)}.md\:w-auto{width:auto}.md\:w-full{width:100%}.md\:max-w-2xl{max-width:var(--container-2xl)}.md\:max-w-3xl{max-width:var(--container-3xl)}.md\:max-w-4xl{max-width:var(--container-4xl)}.md\:max-w-5xl{max-width:var(--container-5xl)}.md\:max-w-6xl{max-width:var(--container-6xl)}.md\:max-w-7xl{max-width:var(--container-7xl)}.md\:max-w-full{max-width:100%}.md\:max-w-lg{max-width:var(--container-lg)}.md\:max-w-md{max-width:var(--container-md)}.md\:max-w-none{max-width:none}.md\:max-w-sm{max-width:var(--container-sm)}.md\:max-w-xl{max-width:var(--container-xl)}.md\:max-w-xs{max-width:var(--container-xs)}.md\:flex-1{flex:1}.md\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.md\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.md\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.md\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.md\:grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.md\:grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.md\:flex-col{flex-direction:column}.md\:flex-row{flex-direction:row}.md\:items-baseline{align-items:baseline}.md\:items-center{align-items:center}.md\:items-end{align-items:flex-end}.md\:items-start{align-items:flex-start}.md\:items-stretch{align-items:stretch}.md\:justify-around{justify-content:space-around}.md\:justify-between{justify-content:space-between}.md\:justify-center{justify-content:center}.md\:justify-end{justify-content:flex-end}.md\:justify-start{justify-content:flex-start}.md\:gap-0{gap:calc(var(--spacing) * 0)}.md\:gap-1{gap:calc(var(--spacing) * 1)}.md\:gap-2{gap:calc(var(--spacing) * 2)}.md\:gap-3{gap:calc(var(--spacing) * 3)}.md\:gap-4{gap:calc(var(--spacing) * 4)}.md\:gap-5{gap:calc(var(--spacing) * 5)}.md\:gap-6{gap:calc(var(--spacing) * 6)}.md\:gap-8{gap:calc(var(--spacing) * 8)}.md\:gap-10{gap:calc(var(--spacing) * 10)}.md\:gap-12{gap:calc(var(--spacing) * 12)}:where(.md\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.md\: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(.md\: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(.md\: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(.md\: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(.md\: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(.md\: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(.md\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.md\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.md\:rounded-2xl{border-radius:var(--radius-2xl)}.md\:p-0{padding:calc(var(--spacing) * 0)}.md\:p-1{padding:calc(var(--spacing) * 1)}.md\:p-2{padding:calc(var(--spacing) * 2)}.md\:p-3{padding:calc(var(--spacing) * 3)}.md\:p-4{padding:calc(var(--spacing) * 4)}.md\:p-5{padding:calc(var(--spacing) * 5)}.md\:p-6{padding:calc(var(--spacing) * 6)}.md\:p-8{padding:calc(var(--spacing) * 8)}.md\:p-10{padding:calc(var(--spacing) * 10)}.md\:p-12{padding:calc(var(--spacing) * 12)}.md\:px-0{padding-inline:calc(var(--spacing) * 0)}.md\:px-1{padding-inline:calc(var(--spacing) * 1)}.md\:px-2{padding-inline:calc(var(--spacing) * 2)}.md\:px-3{padding-inline:calc(var(--spacing) * 3)}.md\:px-4{padding-inline:calc(var(--spacing) * 4)}.md\:px-5{padding-inline:calc(var(--spacing) * 5)}.md\:px-6{padding-inline:calc(var(--spacing) * 6)}.md\:px-8{padding-inline:calc(var(--spacing) * 8)}.md\:px-10{padding-inline:calc(var(--spacing) * 10)}.md\:px-12{padding-inline:calc(var(--spacing) * 12)}.md\:py-0{padding-block:calc(var(--spacing) * 0)}.md\:py-1{padding-block:calc(var(--spacing) * 1)}.md\:py-2{padding-block:calc(var(--spacing) * 2)}.md\:py-3{padding-block:calc(var(--spacing) * 3)}.md\:py-4{padding-block:calc(var(--spacing) * 4)}.md\:py-5{padding-block:calc(var(--spacing) * 5)}.md\:py-6{padding-block:calc(var(--spacing) * 6)}.md\:py-8{padding-block:calc(var(--spacing) * 8)}.md\:py-10{padding-block:calc(var(--spacing) * 10)}.md\:py-12{padding-block:calc(var(--spacing) * 12)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.md\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:64rem){.lg\:absolute{position:absolute}.lg\:fixed{position:fixed}.lg\:relative{position:relative}.lg\:static{position:static}.lg\:sticky{position:sticky}.lg\:inset-0{inset:calc(var(--spacing) * 0)}.lg\:top-0{top:calc(var(--spacing) * 0)}.lg\:right-0{right:calc(var(--spacing) * 0)}.lg\:bottom-0{bottom:calc(var(--spacing) * 0)}.lg\:left-0{left:calc(var(--spacing) * 0)}.lg\:order-1{order:1}.lg\:order-2{order:2}.lg\:order-3{order:3}.lg\:order-4{order:4}.lg\:order-first{order:-9999}.lg\:order-last{order:9999}.lg\:order-none{order:0}.lg\:col-span-1{grid-column:span 1/span 1}.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-span-3{grid-column:span 3/span 3}.lg\:col-span-4{grid-column:span 4/span 4}.lg\:col-span-5{grid-column:span 5/span 5}.lg\:col-span-6{grid-column:span 6/span 6}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:col-span-8{grid-column:span 8/span 8}.lg\:col-span-12{grid-column:span 12/span 12}.lg\:col-span-full{grid-column:1/-1}.lg\:row-span-1{grid-row:span 1/span 1}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:row-span-3{grid-row:span 3/span 3}.lg\:row-span-4{grid-row:span 4/span 4}.lg\:row-span-full{grid-row:1/-1}.lg\:m-0{margin:calc(var(--spacing) * 0)}.lg\:m-1{margin:calc(var(--spacing) * 1)}.lg\:m-2{margin:calc(var(--spacing) * 2)}.lg\:m-3{margin:calc(var(--spacing) * 3)}.lg\:m-4{margin:calc(var(--spacing) * 4)}.lg\:m-auto{margin:auto}.lg\:mx-auto{margin-inline:auto}.lg\:my-auto{margin-block:auto}.lg\:mt-0{margin-top:calc(var(--spacing) * 0)}.lg\:mt-1{margin-top:calc(var(--spacing) * 1)}.lg\:mt-2{margin-top:calc(var(--spacing) * 2)}.lg\:mt-3{margin-top:calc(var(--spacing) * 3)}.lg\:mt-4{margin-top:calc(var(--spacing) * 4)}.lg\:mt-6{margin-top:calc(var(--spacing) * 6)}.lg\:mt-8{margin-top:calc(var(--spacing) * 8)}.lg\:mt-10{margin-top:calc(var(--spacing) * 10)}.lg\:mt-12{margin-top:calc(var(--spacing) * 12)}.lg\:mr-0{margin-right:calc(var(--spacing) * 0)}.lg\:mr-1{margin-right:calc(var(--spacing) * 1)}.lg\:mr-2{margin-right:calc(var(--spacing) * 2)}.lg\:mr-3{margin-right:calc(var(--spacing) * 3)}.lg\:mr-4{margin-right:calc(var(--spacing) * 4)}.lg\:mr-6{margin-right:calc(var(--spacing) * 6)}.lg\:mr-8{margin-right:calc(var(--spacing) * 8)}.lg\:mr-auto{margin-right:auto}.lg\:mb-0{margin-bottom:calc(var(--spacing) * 0)}.lg\:mb-1{margin-bottom:calc(var(--spacing) * 1)}.lg\:mb-2{margin-bottom:calc(var(--spacing) * 2)}.lg\:mb-3{margin-bottom:calc(var(--spacing) * 3)}.lg\:mb-4{margin-bottom:calc(var(--spacing) * 4)}.lg\:mb-6{margin-bottom:calc(var(--spacing) * 6)}.lg\:mb-8{margin-bottom:calc(var(--spacing) * 8)}.lg\:mb-10{margin-bottom:calc(var(--spacing) * 10)}.lg\:mb-12{margin-bottom:calc(var(--spacing) * 12)}.lg\:ml-0{margin-left:calc(var(--spacing) * 0)}.lg\:ml-1{margin-left:calc(var(--spacing) * 1)}.lg\:ml-2{margin-left:calc(var(--spacing) * 2)}.lg\:ml-3{margin-left:calc(var(--spacing) * 3)}.lg\:ml-4{margin-left:calc(var(--spacing) * 4)}.lg\:ml-6{margin-left:calc(var(--spacing) * 6)}.lg\:ml-8{margin-left:calc(var(--spacing) * 8)}.lg\:ml-auto{margin-left:auto}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:inline{display:inline}.lg\:inline-block{display:inline-block}.lg\:inline-flex{display:inline-flex}.lg\:w-1\/2{width:50%}.lg\:w-1\/3{width:33.3333%}.lg\:w-1\/4{width:25%}.lg\:w-1\/5{width:20%}.lg\:w-1\/6{width:16.6667%}.lg\:w-2\/3{width:66.6667%}.lg\:w-2\/5{width:40%}.lg\:w-3\/4{width:75%}.lg\:w-3\/5{width:60%}.lg\:w-4\/5{width:80%}.lg\:w-5\/6{width:83.3333%}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:max-w-2xl{max-width:var(--container-2xl)}.lg\:max-w-3xl{max-width:var(--container-3xl)}.lg\:max-w-4xl{max-width:var(--container-4xl)}.lg\:max-w-5xl{max-width:var(--container-5xl)}.lg\:max-w-6xl{max-width:var(--container-6xl)}.lg\:max-w-7xl{max-width:var(--container-7xl)}.lg\:max-w-full{max-width:100%}.lg\:max-w-lg{max-width:var(--container-lg)}.lg\:max-w-md{max-width:var(--container-md)}.lg\:max-w-none{max-width:none}.lg\:max-w-sm{max-width:var(--container-sm)}.lg\:max-w-xl{max-width:var(--container-xl)}.lg\:max-w-xs{max-width:var(--container-xs)}.lg\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.lg\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.lg\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.lg\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.lg\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.lg\:grid-rows-5{grid-template-rows:repeat(5,minmax(0,1fr))}.lg\:grid-rows-6{grid-template-rows:repeat(6,minmax(0,1fr))}.lg\:flex-col{flex-direction:column}.lg\:flex-row{flex-direction:row}.lg\:items-baseline{align-items:baseline}.lg\:items-center{align-items:center}.lg\:items-end{align-items:flex-end}.lg\:items-start{align-items:flex-start}.lg\:items-stretch{align-items:stretch}.lg\:justify-around{justify-content:space-around}.lg\:justify-between{justify-content:space-between}.lg\:justify-center{justify-content:center}.lg\:justify-end{justify-content:flex-end}.lg\:justify-evenly{justify-content:space-evenly}.lg\:justify-start{justify-content:flex-start}.lg\:gap-0{gap:calc(var(--spacing) * 0)}.lg\:gap-1{gap:calc(var(--spacing) * 1)}.lg\:gap-2{gap:calc(var(--spacing) * 2)}.lg\:gap-3{gap:calc(var(--spacing) * 3)}.lg\:gap-4{gap:calc(var(--spacing) * 4)}.lg\:gap-5{gap:calc(var(--spacing) * 5)}.lg\:gap-6{gap:calc(var(--spacing) * 6)}.lg\:gap-8{gap:calc(var(--spacing) * 8)}.lg\:gap-10{gap:calc(var(--spacing) * 10)}.lg\:gap-12{gap:calc(var(--spacing) * 12)}.lg\:gap-16{gap:calc(var(--spacing) * 16)}:where(.lg\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\: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(.lg\: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(.lg\: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(.lg\: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(.lg\: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(.lg\: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(.lg\:space-y-10>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 10) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-y-reverse)))}:where(.lg\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}:where(.lg\:space-x-10>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 10) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 10) * calc(1 - var(--tw-space-x-reverse)))}.lg\:p-0{padding:calc(var(--spacing) * 0)}.lg\:p-1{padding:calc(var(--spacing) * 1)}.lg\:p-2{padding:calc(var(--spacing) * 2)}.lg\:p-3{padding:calc(var(--spacing) * 3)}.lg\:p-4{padding:calc(var(--spacing) * 4)}.lg\:p-5{padding:calc(var(--spacing) * 5)}.lg\:p-6{padding:calc(var(--spacing) * 6)}.lg\:p-8{padding:calc(var(--spacing) * 8)}.lg\:p-10{padding:calc(var(--spacing) * 10)}.lg\:p-12{padding:calc(var(--spacing) * 12)}.lg\:p-16{padding:calc(var(--spacing) * 16)}.lg\:px-0{padding-inline:calc(var(--spacing) * 0)}.lg\:px-1{padding-inline:calc(var(--spacing) * 1)}.lg\:px-2{padding-inline:calc(var(--spacing) * 2)}.lg\:px-3{padding-inline:calc(var(--spacing) * 3)}.lg\:px-4{padding-inline:calc(var(--spacing) * 4)}.lg\:px-5{padding-inline:calc(var(--spacing) * 5)}.lg\:px-6{padding-inline:calc(var(--spacing) * 6)}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}.lg\:px-10{padding-inline:calc(var(--spacing) * 10)}.lg\:px-12{padding-inline:calc(var(--spacing) * 12)}.lg\:px-16{padding-inline:calc(var(--spacing) * 16)}.lg\:py-0{padding-block:calc(var(--spacing) * 0)}.lg\:py-1{padding-block:calc(var(--spacing) * 1)}.lg\:py-2{padding-block:calc(var(--spacing) * 2)}.lg\:py-3{padding-block:calc(var(--spacing) * 3)}.lg\:py-4{padding-block:calc(var(--spacing) * 4)}.lg\:py-5{padding-block:calc(var(--spacing) * 5)}.lg\:py-6{padding-block:calc(var(--spacing) * 6)}.lg\:py-8{padding-block:calc(var(--spacing) * 8)}.lg\:py-10{padding-block:calc(var(--spacing) * 10)}.lg\:py-12{padding-block:calc(var(--spacing) * 12)}.lg\:py-16{padding-block:calc(var(--spacing) * 16)}.lg\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.lg\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.lg\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.lg\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.lg\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.lg\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.lg\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.lg\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.lg\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:80rem){.xl\:col-span-1{grid-column:span 1/span 1}.xl\:col-span-2{grid-column:span 2/span 2}.xl\:col-span-3{grid-column:span 3/span 3}.xl\:col-span-4{grid-column:span 4/span 4}.xl\:col-span-5{grid-column:span 5/span 5}.xl\:col-span-6{grid-column:span 6/span 6}.xl\:col-span-12{grid-column:span 12/span 12}.xl\:col-span-full{grid-column:1/-1}.xl\:row-span-1{grid-row:span 1/span 1}.xl\:row-span-2{grid-row:span 2/span 2}.xl\:row-span-3{grid-row:span 3/span 3}.xl\:row-span-full{grid-row:1/-1}.xl\:m-0{margin:calc(var(--spacing) * 0)}.xl\:m-auto{margin:auto}.xl\:mx-auto{margin-inline:auto}.xl\:my-auto{margin-block:auto}.xl\:block{display:block}.xl\:flex{display:flex}.xl\:hidden{display:none}.xl\:inline{display:inline}.xl\:inline-block{display:inline-block}.xl\:inline-flex{display:inline-flex}.xl\:w-1\/2{width:50%}.xl\:w-1\/3{width:33.3333%}.xl\:w-1\/4{width:25%}.xl\:w-1\/5{width:20%}.xl\:w-2\/3{width:66.6667%}.xl\:w-2\/5{width:40%}.xl\:w-3\/4{width:75%}.xl\:w-3\/5{width:60%}.xl\:w-4\/5{width:80%}.xl\:w-auto{width:auto}.xl\:w-full{width:100%}.xl\:max-w-2xl{max-width:var(--container-2xl)}.xl\:max-w-3xl{max-width:var(--container-3xl)}.xl\:max-w-4xl{max-width:var(--container-4xl)}.xl\:max-w-5xl{max-width:var(--container-5xl)}.xl\:max-w-6xl{max-width:var(--container-6xl)}.xl\:max-w-7xl{max-width:var(--container-7xl)}.xl\:max-w-full{max-width:100%}.xl\:max-w-lg{max-width:var(--container-lg)}.xl\:max-w-md{max-width:var(--container-md)}.xl\:max-w-none{max-width:none}.xl\:max-w-sm{max-width:var(--container-sm)}.xl\:max-w-xl{max-width:var(--container-xl)}.xl\:max-w-xs{max-width:var(--container-xs)}.xl\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.xl\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.xl\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.xl\:grid-rows-2{grid-template-rows:repeat(2,minmax(0,1fr))}.xl\:grid-rows-3{grid-template-rows:repeat(3,minmax(0,1fr))}.xl\:grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.xl\:flex-col{flex-direction:column}.xl\:flex-row{flex-direction:row}.xl\:items-center{align-items:center}.xl\:items-end{align-items:flex-end}.xl\:items-start{align-items:flex-start}.xl\:justify-between{justify-content:space-between}.xl\:justify-center{justify-content:center}.xl\:justify-end{justify-content:flex-end}.xl\:justify-start{justify-content:flex-start}.xl\:gap-0{gap:calc(var(--spacing) * 0)}.xl\:gap-1{gap:calc(var(--spacing) * 1)}.xl\:gap-2{gap:calc(var(--spacing) * 2)}.xl\:gap-3{gap:calc(var(--spacing) * 3)}.xl\:gap-4{gap:calc(var(--spacing) * 4)}.xl\:gap-5{gap:calc(var(--spacing) * 5)}.xl\:gap-6{gap:calc(var(--spacing) * 6)}.xl\:gap-8{gap:calc(var(--spacing) * 8)}.xl\:gap-10{gap:calc(var(--spacing) * 10)}.xl\:gap-12{gap:calc(var(--spacing) * 12)}.xl\:gap-16{gap:calc(var(--spacing) * 16)}:where(.xl\:space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)))}:where(.xl\: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(.xl\: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(.xl\: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(.xl\: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(.xl\: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(.xl\: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(.xl\:space-x-0>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 0) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 6) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)))}:where(.xl\:space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.xl\:p-0{padding:calc(var(--spacing) * 0)}.xl\:p-1{padding:calc(var(--spacing) * 1)}.xl\:p-2{padding:calc(var(--spacing) * 2)}.xl\:p-3{padding:calc(var(--spacing) * 3)}.xl\:p-4{padding:calc(var(--spacing) * 4)}.xl\:p-5{padding:calc(var(--spacing) * 5)}.xl\:p-6{padding:calc(var(--spacing) * 6)}.xl\:p-8{padding:calc(var(--spacing) * 8)}.xl\:p-10{padding:calc(var(--spacing) * 10)}.xl\:p-12{padding:calc(var(--spacing) * 12)}.xl\:p-16{padding:calc(var(--spacing) * 16)}.xl\:px-0{padding-inline:calc(var(--spacing) * 0)}.xl\:px-1{padding-inline:calc(var(--spacing) * 1)}.xl\:px-2{padding-inline:calc(var(--spacing) * 2)}.xl\:px-3{padding-inline:calc(var(--spacing) * 3)}.xl\:px-4{padding-inline:calc(var(--spacing) * 4)}.xl\:px-5{padding-inline:calc(var(--spacing) * 5)}.xl\:px-6{padding-inline:calc(var(--spacing) * 6)}.xl\:px-8{padding-inline:calc(var(--spacing) * 8)}.xl\:px-10{padding-inline:calc(var(--spacing) * 10)}.xl\:px-12{padding-inline:calc(var(--spacing) * 12)}.xl\:px-16{padding-inline:calc(var(--spacing) * 16)}.xl\:py-0{padding-block:calc(var(--spacing) * 0)}.xl\:py-1{padding-block:calc(var(--spacing) * 1)}.xl\:py-2{padding-block:calc(var(--spacing) * 2)}.xl\:py-3{padding-block:calc(var(--spacing) * 3)}.xl\:py-4{padding-block:calc(var(--spacing) * 4)}.xl\:py-5{padding-block:calc(var(--spacing) * 5)}.xl\:py-6{padding-block:calc(var(--spacing) * 6)}.xl\:py-8{padding-block:calc(var(--spacing) * 8)}.xl\:py-10{padding-block:calc(var(--spacing) * 10)}.xl\:py-12{padding-block:calc(var(--spacing) * 12)}.xl\:py-16{padding-block:calc(var(--spacing) * 16)}.xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.xl\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.xl\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.xl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.xl\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.xl\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.xl\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.xl\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}@media(min-width:96rem){.\32xl\:col-span-1{grid-column:span 1/span 1}.\32xl\:col-span-2{grid-column:span 2/span 2}.\32xl\:col-span-3{grid-column:span 3/span 3}.\32xl\:col-span-4{grid-column:span 4/span 4}.\32xl\:col-span-6{grid-column:span 6/span 6}.\32xl\:col-span-12{grid-column:span 12/span 12}.\32xl\:block{display:block}.\32xl\:flex{display:flex}.\32xl\:hidden{display:none}.\32xl\:w-1\/2{width:50%}.\32xl\:w-1\/3{width:33.3333%}.\32xl\:w-1\/4{width:25%}.\32xl\:w-2\/3{width:66.6667%}.\32xl\:w-3\/4{width:75%}.\32xl\:w-auto{width:auto}.\32xl\:w-full{width:100%}.\32xl\:max-w-2xl{max-width:var(--container-2xl)}.\32xl\:max-w-3xl{max-width:var(--container-3xl)}.\32xl\:max-w-4xl{max-width:var(--container-4xl)}.\32xl\:max-w-5xl{max-width:var(--container-5xl)}.\32xl\:max-w-6xl{max-width:var(--container-6xl)}.\32xl\:max-w-7xl{max-width:var(--container-7xl)}.\32xl\:max-w-full{max-width:100%}.\32xl\:max-w-lg{max-width:var(--container-lg)}.\32xl\:max-w-md{max-width:var(--container-md)}.\32xl\:max-w-sm{max-width:var(--container-sm)}.\32xl\:max-w-xl{max-width:var(--container-xl)}.\32xl\:max-w-xs{max-width:var(--container-xs)}.\32xl\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.\32xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.\32xl\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.\32xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.\32xl\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.\32xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.\32xl\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.\32xl\:gap-0{gap:calc(var(--spacing) * 0)}.\32xl\:gap-4{gap:calc(var(--spacing) * 4)}.\32xl\:gap-6{gap:calc(var(--spacing) * 6)}.\32xl\:gap-8{gap:calc(var(--spacing) * 8)}.\32xl\:gap-12{gap:calc(var(--spacing) * 12)}.\32xl\:gap-16{gap:calc(var(--spacing) * 16)}.\32xl\:p-0{padding:calc(var(--spacing) * 0)}.\32xl\:p-4{padding:calc(var(--spacing) * 4)}.\32xl\:p-6{padding:calc(var(--spacing) * 6)}.\32xl\:p-8{padding:calc(var(--spacing) * 8)}.\32xl\:p-12{padding:calc(var(--spacing) * 12)}.\32xl\:p-16{padding:calc(var(--spacing) * 16)}.\32xl\:px-0{padding-inline:calc(var(--spacing) * 0)}.\32xl\:px-4{padding-inline:calc(var(--spacing) * 4)}.\32xl\:px-6{padding-inline:calc(var(--spacing) * 6)}.\32xl\:px-8{padding-inline:calc(var(--spacing) * 8)}.\32xl\:px-12{padding-inline:calc(var(--spacing) * 12)}.\32xl\:px-16{padding-inline:calc(var(--spacing) * 16)}.\32xl\:py-0{padding-block:calc(var(--spacing) * 0)}.\32xl\:py-4{padding-block:calc(var(--spacing) * 4)}.\32xl\:py-6{padding-block:calc(var(--spacing) * 6)}.\32xl\:py-8{padding-block:calc(var(--spacing) * 8)}.\32xl\:py-12{padding-block:calc(var(--spacing) * 12)}.\32xl\:py-16{padding-block:calc(var(--spacing) * 16)}.\32xl\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.\32xl\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.\32xl\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.\32xl\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.\32xl\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.\32xl\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.\32xl\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\32xl\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.\32xl\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}}.dark .dark\:block{display:block}.dark .dark\:flex{display:flex}.dark .dark\:flex-1{flex:1}.dark .dark\:flex-auto{flex:auto}.dark .dark\:flex-none{flex:none}.dark .dark\:flex-col{flex-direction:column}.dark .dark\:flex-row{flex-direction:row}.dark .dark\:flex-wrap{flex-wrap:wrap}.dark .dark\:items-center{align-items:center}.dark .dark\:items-end{align-items:flex-end}.dark .dark\:items-start{align-items:flex-start}.dark .dark\:items-stretch{align-items:stretch}.dark .dark\:justify-around{justify-content:space-around}.dark .dark\:justify-between{justify-content:space-between}.dark .dark\:justify-center{justify-content:center}.dark .dark\:justify-end{justify-content:flex-end}.dark .dark\:justify-start{justify-content:flex-start}:where(.dark .dark\:divide-gray-50>:not(:last-child)){border-color:var(--color-gray-50)}:where(.dark .dark\:divide-gray-100>:not(:last-child)){border-color:var(--color-gray-100)}:where(.dark .dark\:divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}:where(.dark .dark\:divide-gray-300>:not(:last-child)){border-color:var(--color-gray-300)}:where(.dark .dark\:divide-gray-400>:not(:last-child)){border-color:var(--color-gray-400)}:where(.dark .dark\:divide-gray-500>:not(:last-child)){border-color:var(--color-gray-500)}:where(.dark .dark\:divide-gray-600>:not(:last-child)){border-color:var(--color-gray-600)}:where(.dark .dark\:divide-gray-700>:not(:last-child)){border-color:var(--color-gray-700)}:where(.dark .dark\:divide-gray-800>:not(:last-child)){border-color:var(--color-gray-800)}:where(.dark .dark\:divide-gray-900>:not(:last-child)){border-color:var(--color-gray-900)}:where(.dark .dark\:divide-purple-800>:not(:last-child)){border-color:var(--color-purple-800)}:where(.dark .dark\:divide-slate-700>:not(:last-child)){border-color:var(--color-slate-700)}.dark .dark\:self-center{align-self:center}.dark .dark\:self-end{align-self:flex-end}.dark .dark\:self-start{align-self:flex-start}.dark .dark\:self-stretch{align-self:stretch}.dark .dark\:overflow-auto{overflow:auto}.dark .dark\:overflow-hidden{overflow:hidden}.dark .dark\:overflow-visible{overflow:visible}.dark .dark\:overflow-x-auto{overflow-x:auto}.dark .dark\:overflow-y-auto{overflow-y:auto}.dark .dark\:rounded{border-radius:.25rem}.dark .dark\:rounded-full{border-radius:3.40282e38px}.dark .dark\:rounded-lg{border-radius:var(--radius-lg)}.dark .dark\:rounded-none{border-radius:0}.dark .dark\:rounded-sm{border-radius:var(--radius-sm)}.dark .dark\:rounded-xl{border-radius:var(--radius-xl)}.dark .dark\:border{border-style:var(--tw-border-style);border-width:1px}.dark .dark\:border-0{border-style:var(--tw-border-style);border-width:0}.dark .dark\:border-dashed{--tw-border-style:dashed;border-style:dashed}.dark .dark\:border-dotted{--tw-border-style:dotted;border-style:dotted}.dark .dark\:border-solid{--tw-border-style:solid;border-style:solid}.dark .dark\:border-amber-50{border-color:var(--color-amber-50)}.dark .dark\:border-amber-100{border-color:var(--color-amber-100)}.dark .dark\:border-amber-200{border-color:var(--color-amber-200)}.dark .dark\:border-amber-300{border-color:var(--color-amber-300)}.dark .dark\:border-amber-400{border-color:var(--color-amber-400)}.dark .dark\:border-amber-500{border-color:var(--color-amber-500)}.dark .dark\:border-amber-600{border-color:var(--color-amber-600)}.dark .dark\:border-amber-700{border-color:var(--color-amber-700)}.dark .dark\:border-amber-800{border-color:var(--color-amber-800)}.dark .dark\:border-amber-900{border-color:var(--color-amber-900)}.dark .dark\:border-black{border-color:var(--color-black)}.dark .dark\:border-blue-50{border-color:var(--color-blue-50)}.dark .dark\:border-blue-100{border-color:var(--color-blue-100)}.dark .dark\:border-blue-200{border-color:var(--color-blue-200)}.dark .dark\:border-blue-300{border-color:var(--color-blue-300)}.dark .dark\:border-blue-400{border-color:var(--color-blue-400)}.dark .dark\:border-blue-500{border-color:var(--color-blue-500)}.dark .dark\:border-blue-600{border-color:var(--color-blue-600)}.dark .dark\:border-blue-700{border-color:var(--color-blue-700)}.dark .dark\:border-blue-800{border-color:var(--color-blue-800)}.dark .dark\:border-blue-900{border-color:var(--color-blue-900)}.dark .dark\:border-gray-50{border-color:var(--color-gray-50)}.dark .dark\:border-gray-100{border-color:var(--color-gray-100)}.dark .dark\:border-gray-200{border-color:var(--color-gray-200)}.dark .dark\:border-gray-300{border-color:var(--color-gray-300)}.dark .dark\:border-gray-400{border-color:var(--color-gray-400)}.dark .dark\:border-gray-500{border-color:var(--color-gray-500)}.dark .dark\:border-gray-600{border-color:var(--color-gray-600)}.dark .dark\:border-gray-700{border-color:var(--color-gray-700)}.dark .dark\:border-gray-800{border-color:var(--color-gray-800)}.dark .dark\:border-gray-900{border-color:var(--color-gray-900)}.dark .dark\:border-gray-950{border-color:var(--color-gray-950)}.dark .dark\:border-green-50{border-color:var(--color-green-50)}.dark .dark\:border-green-100{border-color:var(--color-green-100)}.dark .dark\:border-green-200{border-color:var(--color-green-200)}.dark .dark\:border-green-300{border-color:var(--color-green-300)}.dark .dark\:border-green-400{border-color:var(--color-green-400)}.dark .dark\:border-green-500{border-color:var(--color-green-500)}.dark .dark\:border-green-600{border-color:var(--color-green-600)}.dark .dark\:border-green-700{border-color:var(--color-green-700)}.dark .dark\:border-green-800{border-color:var(--color-green-800)}.dark .dark\:border-green-900{border-color:var(--color-green-900)}.dark .dark\:border-indigo-400{border-color:var(--color-indigo-400)}.dark .dark\:border-orange-50{border-color:var(--color-orange-50)}.dark .dark\:border-orange-100{border-color:var(--color-orange-100)}.dark .dark\:border-orange-200{border-color:var(--color-orange-200)}.dark .dark\:border-orange-300{border-color:var(--color-orange-300)}.dark .dark\:border-orange-400{border-color:var(--color-orange-400)}.dark .dark\:border-orange-500{border-color:var(--color-orange-500)}.dark .dark\:border-orange-600{border-color:var(--color-orange-600)}.dark .dark\:border-orange-700{border-color:var(--color-orange-700)}.dark .dark\:border-orange-800{border-color:var(--color-orange-800)}.dark .dark\:border-orange-900{border-color:var(--color-orange-900)}.dark .dark\:border-pink-50{border-color:var(--color-pink-50)}.dark .dark\:border-pink-100{border-color:var(--color-pink-100)}.dark .dark\:border-pink-200{border-color:var(--color-pink-200)}.dark .dark\:border-pink-300{border-color:var(--color-pink-300)}.dark .dark\:border-pink-400{border-color:var(--color-pink-400)}.dark .dark\:border-pink-500{border-color:var(--color-pink-500)}.dark .dark\:border-pink-600{border-color:var(--color-pink-600)}.dark .dark\:border-pink-700{border-color:var(--color-pink-700)}.dark .dark\:border-pink-800{border-color:var(--color-pink-800)}.dark .dark\:border-pink-900{border-color:var(--color-pink-900)}.dark .dark\:border-purple-50{border-color:var(--color-purple-50)}.dark .dark\:border-purple-100{border-color:var(--color-purple-100)}.dark .dark\:border-purple-200{border-color:var(--color-purple-200)}.dark .dark\:border-purple-300{border-color:var(--color-purple-300)}.dark .dark\:border-purple-400{border-color:var(--color-purple-400)}.dark .dark\:border-purple-500{border-color:var(--color-purple-500)}.dark .dark\:border-purple-600{border-color:var(--color-purple-600)}.dark .dark\:border-purple-700{border-color:var(--color-purple-700)}.dark .dark\:border-purple-800{border-color:var(--color-purple-800)}.dark .dark\:border-purple-900{border-color:var(--color-purple-900)}.dark .dark\:border-red-50{border-color:var(--color-red-50)}.dark .dark\:border-red-100{border-color:var(--color-red-100)}.dark .dark\:border-red-200{border-color:var(--color-red-200)}.dark .dark\:border-red-300{border-color:var(--color-red-300)}.dark .dark\:border-red-400{border-color:var(--color-red-400)}.dark .dark\:border-red-500{border-color:var(--color-red-500)}.dark .dark\:border-red-600{border-color:var(--color-red-600)}.dark .dark\:border-red-700{border-color:var(--color-red-700)}.dark .dark\:border-red-800{border-color:var(--color-red-800)}.dark .dark\:border-red-900{border-color:var(--color-red-900)}.dark .dark\:border-slate-50{border-color:var(--color-slate-50)}.dark .dark\:border-slate-100{border-color:var(--color-slate-100)}.dark .dark\:border-slate-200{border-color:var(--color-slate-200)}.dark .dark\:border-slate-300{border-color:var(--color-slate-300)}.dark .dark\:border-slate-400{border-color:var(--color-slate-400)}.dark .dark\:border-slate-500{border-color:var(--color-slate-500)}.dark .dark\:border-slate-600{border-color:var(--color-slate-600)}.dark .dark\:border-slate-700{border-color:var(--color-slate-700)}.dark .dark\:border-slate-800{border-color:var(--color-slate-800)}.dark .dark\:border-slate-900{border-color:var(--color-slate-900)}.dark .dark\:border-slate-950{border-color:var(--color-slate-950)}.dark .dark\:border-transparent{border-color:#0000}.dark .dark\:border-white{border-color:var(--color-white)}.dark .dark\:border-yellow-50{border-color:var(--color-yellow-50)}.dark .dark\:border-yellow-100{border-color:var(--color-yellow-100)}.dark .dark\:border-yellow-200{border-color:var(--color-yellow-200)}.dark .dark\:border-yellow-300{border-color:var(--color-yellow-300)}.dark .dark\:border-yellow-400{border-color:var(--color-yellow-400)}.dark .dark\:border-yellow-500{border-color:var(--color-yellow-500)}.dark .dark\:border-yellow-600{border-color:var(--color-yellow-600)}.dark .dark\:border-yellow-700{border-color:var(--color-yellow-700)}.dark .dark\:border-yellow-800{border-color:var(--color-yellow-800)}.dark .dark\:border-yellow-900{border-color:var(--color-yellow-900)}.dark .dark\:bg-amber-50{background-color:var(--color-amber-50)}.dark .dark\:bg-amber-100{background-color:var(--color-amber-100)}.dark .dark\:bg-amber-200{background-color:var(--color-amber-200)}.dark .dark\:bg-amber-300{background-color:var(--color-amber-300)}.dark .dark\:bg-amber-400{background-color:var(--color-amber-400)}.dark .dark\:bg-amber-500{background-color:var(--color-amber-500)}.dark .dark\:bg-amber-600{background-color:var(--color-amber-600)}.dark .dark\:bg-amber-700{background-color:var(--color-amber-700)}.dark .dark\:bg-amber-800{background-color:var(--color-amber-800)}.dark .dark\:bg-amber-900{background-color:var(--color-amber-900)}.dark .dark\:bg-amber-900\/20{background-color:#7b330633}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/20{background-color:color-mix(in oklab,var(--color-amber-900) 20%,transparent)}}.dark .dark\:bg-amber-900\/30{background-color:#7b33064d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/30{background-color:color-mix(in oklab,var(--color-amber-900) 30%,transparent)}}.dark .dark\:bg-amber-900\/40{background-color:#7b330666}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-amber-900\/40{background-color:color-mix(in oklab,var(--color-amber-900) 40%,transparent)}}.dark .dark\:bg-amber-950{background-color:var(--color-amber-950)}.dark .dark\:bg-black{background-color:var(--color-black)}.dark .dark\:bg-blue-50{background-color:var(--color-blue-50)}.dark .dark\:bg-blue-100{background-color:var(--color-blue-100)}.dark .dark\:bg-blue-200{background-color:var(--color-blue-200)}.dark .dark\:bg-blue-300{background-color:var(--color-blue-300)}.dark .dark\:bg-blue-400{background-color:var(--color-blue-400)}.dark .dark\:bg-blue-500{background-color:var(--color-blue-500)}.dark .dark\:bg-blue-600{background-color:var(--color-blue-600)}.dark .dark\:bg-blue-700{background-color:var(--color-blue-700)}.dark .dark\:bg-blue-800{background-color:var(--color-blue-800)}.dark .dark\:bg-blue-900{background-color:var(--color-blue-900)}.dark .dark\:bg-blue-900\/20{background-color:#1c398e33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/20{background-color:color-mix(in oklab,var(--color-blue-900) 20%,transparent)}}.dark .dark\:bg-blue-900\/30{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/30{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .dark\:bg-blue-900\/40{background-color:#1c398e66}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/40{background-color:color-mix(in oklab,var(--color-blue-900) 40%,transparent)}}.dark .dark\:bg-blue-900\/50{background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-blue-900\/50{background-color:color-mix(in oklab,var(--color-blue-900) 50%,transparent)}}.dark .dark\:bg-blue-950{background-color:var(--color-blue-950)}.dark .dark\:bg-cyan-50{background-color:var(--color-cyan-50)}.dark .dark\:bg-cyan-100{background-color:var(--color-cyan-100)}.dark .dark\:bg-cyan-200{background-color:var(--color-cyan-200)}.dark .dark\:bg-cyan-300{background-color:var(--color-cyan-300)}.dark .dark\:bg-cyan-400{background-color:var(--color-cyan-400)}.dark .dark\:bg-cyan-500{background-color:var(--color-cyan-500)}.dark .dark\:bg-cyan-600{background-color:var(--color-cyan-600)}.dark .dark\:bg-cyan-700{background-color:var(--color-cyan-700)}.dark .dark\:bg-cyan-800{background-color:var(--color-cyan-800)}.dark .dark\:bg-cyan-900{background-color:var(--color-cyan-900)}.dark .dark\:bg-cyan-900\/30{background-color:#104e644d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-cyan-900\/30{background-color:color-mix(in oklab,var(--color-cyan-900) 30%,transparent)}}.dark .dark\:bg-cyan-950{background-color:var(--color-cyan-950)}.dark .dark\:bg-emerald-50{background-color:var(--color-emerald-50)}.dark .dark\:bg-emerald-100{background-color:var(--color-emerald-100)}.dark .dark\:bg-emerald-200{background-color:var(--color-emerald-200)}.dark .dark\:bg-emerald-300{background-color:var(--color-emerald-300)}.dark .dark\:bg-emerald-400{background-color:var(--color-emerald-400)}.dark .dark\:bg-emerald-500{background-color:var(--color-emerald-500)}.dark .dark\:bg-emerald-600{background-color:var(--color-emerald-600)}.dark .dark\:bg-emerald-700{background-color:var(--color-emerald-700)}.dark .dark\:bg-emerald-800{background-color:var(--color-emerald-800)}.dark .dark\:bg-emerald-900{background-color:var(--color-emerald-900)}.dark .dark\:bg-emerald-950{background-color:var(--color-emerald-950)}.dark .dark\:bg-gray-50{background-color:var(--color-gray-50)}.dark .dark\:bg-gray-100{background-color:var(--color-gray-100)}.dark .dark\:bg-gray-200{background-color:var(--color-gray-200)}.dark .dark\:bg-gray-300{background-color:var(--color-gray-300)}.dark .dark\:bg-gray-400{background-color:var(--color-gray-400)}.dark .dark\:bg-gray-500{background-color:var(--color-gray-500)}.dark .dark\:bg-gray-600{background-color:var(--color-gray-600)}.dark .dark\:bg-gray-700{background-color:var(--color-gray-700)}.dark .dark\:bg-gray-700\/30{background-color:#3641534d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-700\/30{background-color:color-mix(in oklab,var(--color-gray-700) 30%,transparent)}}.dark .dark\:bg-gray-700\/50{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-700\/50{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.dark .dark\:bg-gray-800{background-color:var(--color-gray-800)}.dark .dark\:bg-gray-800\/50{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-800\/50{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.dark .dark\:bg-gray-800\/90{background-color:#1e2939e6}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-800\/90{background-color:color-mix(in oklab,var(--color-gray-800) 90%,transparent)}}.dark .dark\:bg-gray-900{background-color:var(--color-gray-900)}.dark .dark\:bg-gray-900\/50{background-color:#10182880}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-gray-900\/50{background-color:color-mix(in oklab,var(--color-gray-900) 50%,transparent)}}.dark .dark\:bg-gray-950{background-color:var(--color-gray-950)}.dark .dark\:bg-green-50{background-color:var(--color-green-50)}.dark .dark\:bg-green-100{background-color:var(--color-green-100)}.dark .dark\:bg-green-200{background-color:var(--color-green-200)}.dark .dark\:bg-green-300{background-color:var(--color-green-300)}.dark .dark\:bg-green-400{background-color:var(--color-green-400)}.dark .dark\:bg-green-500{background-color:var(--color-green-500)}.dark .dark\:bg-green-600{background-color:var(--color-green-600)}.dark .dark\:bg-green-700{background-color:var(--color-green-700)}.dark .dark\:bg-green-800{background-color:var(--color-green-800)}.dark .dark\:bg-green-900{background-color:var(--color-green-900)}.dark .dark\:bg-green-900\/20{background-color:#0d542b33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-green-900\/20{background-color:color-mix(in oklab,var(--color-green-900) 20%,transparent)}}.dark .dark\:bg-green-900\/30{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-green-900\/30{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .dark\:bg-green-950{background-color:var(--color-green-950)}.dark .dark\:bg-indigo-50{background-color:var(--color-indigo-50)}.dark .dark\:bg-indigo-100{background-color:var(--color-indigo-100)}.dark .dark\:bg-indigo-200{background-color:var(--color-indigo-200)}.dark .dark\:bg-indigo-300{background-color:var(--color-indigo-300)}.dark .dark\:bg-indigo-400{background-color:var(--color-indigo-400)}.dark .dark\:bg-indigo-500{background-color:var(--color-indigo-500)}.dark .dark\:bg-indigo-600{background-color:var(--color-indigo-600)}.dark .dark\:bg-indigo-700{background-color:var(--color-indigo-700)}.dark .dark\:bg-indigo-800{background-color:var(--color-indigo-800)}.dark .dark\:bg-indigo-900{background-color:var(--color-indigo-900)}.dark .dark\:bg-indigo-900\/30{background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-indigo-900\/30{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .dark\:bg-indigo-950{background-color:var(--color-indigo-950)}.dark .dark\:bg-orange-50{background-color:var(--color-orange-50)}.dark .dark\:bg-orange-100{background-color:var(--color-orange-100)}.dark .dark\:bg-orange-200{background-color:var(--color-orange-200)}.dark .dark\:bg-orange-300{background-color:var(--color-orange-300)}.dark .dark\:bg-orange-400{background-color:var(--color-orange-400)}.dark .dark\:bg-orange-500{background-color:var(--color-orange-500)}.dark .dark\:bg-orange-600{background-color:var(--color-orange-600)}.dark .dark\:bg-orange-700{background-color:var(--color-orange-700)}.dark .dark\:bg-orange-800{background-color:var(--color-orange-800)}.dark .dark\:bg-orange-900{background-color:var(--color-orange-900)}.dark .dark\:bg-orange-900\/20{background-color:#7e2a0c33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-orange-900\/20{background-color:color-mix(in oklab,var(--color-orange-900) 20%,transparent)}}.dark .dark\:bg-orange-900\/30{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-orange-900\/30{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.dark .dark\:bg-orange-950{background-color:var(--color-orange-950)}.dark .dark\:bg-pink-50{background-color:var(--color-pink-50)}.dark .dark\:bg-pink-100{background-color:var(--color-pink-100)}.dark .dark\:bg-pink-200{background-color:var(--color-pink-200)}.dark .dark\:bg-pink-300{background-color:var(--color-pink-300)}.dark .dark\:bg-pink-400{background-color:var(--color-pink-400)}.dark .dark\:bg-pink-500{background-color:var(--color-pink-500)}.dark .dark\:bg-pink-600{background-color:var(--color-pink-600)}.dark .dark\:bg-pink-700{background-color:var(--color-pink-700)}.dark .dark\:bg-pink-800{background-color:var(--color-pink-800)}.dark .dark\:bg-pink-900{background-color:var(--color-pink-900)}.dark .dark\:bg-pink-950{background-color:var(--color-pink-950)}.dark .dark\:bg-purple-50{background-color:var(--color-purple-50)}.dark .dark\:bg-purple-100{background-color:var(--color-purple-100)}.dark .dark\:bg-purple-200{background-color:var(--color-purple-200)}.dark .dark\:bg-purple-300{background-color:var(--color-purple-300)}.dark .dark\:bg-purple-400{background-color:var(--color-purple-400)}.dark .dark\:bg-purple-500{background-color:var(--color-purple-500)}.dark .dark\:bg-purple-600{background-color:var(--color-purple-600)}.dark .dark\:bg-purple-700{background-color:var(--color-purple-700)}.dark .dark\:bg-purple-800{background-color:var(--color-purple-800)}.dark .dark\:bg-purple-800\/50{background-color:#6e11b080}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-800\/50{background-color:color-mix(in oklab,var(--color-purple-800) 50%,transparent)}}.dark .dark\:bg-purple-900{background-color:var(--color-purple-900)}.dark .dark\:bg-purple-900\/20{background-color:#59168b33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/20{background-color:color-mix(in oklab,var(--color-purple-900) 20%,transparent)}}.dark .dark\:bg-purple-900\/30{background-color:#59168b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/30{background-color:color-mix(in oklab,var(--color-purple-900) 30%,transparent)}}.dark .dark\:bg-purple-900\/50{background-color:#59168b80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-purple-900\/50{background-color:color-mix(in oklab,var(--color-purple-900) 50%,transparent)}}.dark .dark\:bg-purple-950{background-color:var(--color-purple-950)}.dark .dark\:bg-red-50{background-color:var(--color-red-50)}.dark .dark\:bg-red-100{background-color:var(--color-red-100)}.dark .dark\:bg-red-200{background-color:var(--color-red-200)}.dark .dark\:bg-red-300{background-color:var(--color-red-300)}.dark .dark\:bg-red-400{background-color:var(--color-red-400)}.dark .dark\:bg-red-500{background-color:var(--color-red-500)}.dark .dark\:bg-red-600{background-color:var(--color-red-600)}.dark .dark\:bg-red-700{background-color:var(--color-red-700)}.dark .dark\:bg-red-800{background-color:var(--color-red-800)}.dark .dark\:bg-red-900{background-color:var(--color-red-900)}.dark .dark\:bg-red-900\/20{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-red-900\/20{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .dark\:bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-red-900\/30{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .dark\:bg-red-950{background-color:var(--color-red-950)}.dark .dark\:bg-rose-50{background-color:var(--color-rose-50)}.dark .dark\:bg-rose-100{background-color:var(--color-rose-100)}.dark .dark\:bg-rose-200{background-color:var(--color-rose-200)}.dark .dark\:bg-rose-300{background-color:var(--color-rose-300)}.dark .dark\:bg-rose-400{background-color:var(--color-rose-400)}.dark .dark\:bg-rose-500{background-color:var(--color-rose-500)}.dark .dark\:bg-rose-600{background-color:var(--color-rose-600)}.dark .dark\:bg-rose-700{background-color:var(--color-rose-700)}.dark .dark\:bg-rose-800{background-color:var(--color-rose-800)}.dark .dark\:bg-rose-900{background-color:var(--color-rose-900)}.dark .dark\:bg-rose-950{background-color:var(--color-rose-950)}.dark .dark\:bg-slate-50{background-color:var(--color-slate-50)}.dark .dark\:bg-slate-100{background-color:var(--color-slate-100)}.dark .dark\:bg-slate-200{background-color:var(--color-slate-200)}.dark .dark\:bg-slate-300{background-color:var(--color-slate-300)}.dark .dark\:bg-slate-400{background-color:var(--color-slate-400)}.dark .dark\:bg-slate-500{background-color:var(--color-slate-500)}.dark .dark\:bg-slate-600{background-color:var(--color-slate-600)}.dark .dark\:bg-slate-700{background-color:var(--color-slate-700)}.dark .dark\:bg-slate-700\/50{background-color:#31415880}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-slate-700\/50{background-color:color-mix(in oklab,var(--color-slate-700) 50%,transparent)}}.dark .dark\:bg-slate-800{background-color:var(--color-slate-800)}.dark .dark\:bg-slate-900{background-color:var(--color-slate-900)}.dark .dark\:bg-slate-950{background-color:var(--color-slate-950)}.dark .dark\:bg-teal-50{background-color:var(--color-teal-50)}.dark .dark\:bg-teal-100{background-color:var(--color-teal-100)}.dark .dark\:bg-teal-200{background-color:var(--color-teal-200)}.dark .dark\:bg-teal-300{background-color:var(--color-teal-300)}.dark .dark\:bg-teal-400{background-color:var(--color-teal-400)}.dark .dark\:bg-teal-500{background-color:var(--color-teal-500)}.dark .dark\:bg-teal-600{background-color:var(--color-teal-600)}.dark .dark\:bg-teal-700{background-color:var(--color-teal-700)}.dark .dark\:bg-teal-800{background-color:var(--color-teal-800)}.dark .dark\:bg-teal-900{background-color:var(--color-teal-900)}.dark .dark\:bg-teal-900\/30{background-color:#0b4f4a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-teal-900\/30{background-color:color-mix(in oklab,var(--color-teal-900) 30%,transparent)}}.dark .dark\:bg-teal-950{background-color:var(--color-teal-950)}.dark .dark\:bg-white{background-color:var(--color-white)}.dark .dark\:bg-yellow-50{background-color:var(--color-yellow-50)}.dark .dark\:bg-yellow-100{background-color:var(--color-yellow-100)}.dark .dark\:bg-yellow-200{background-color:var(--color-yellow-200)}.dark .dark\:bg-yellow-300{background-color:var(--color-yellow-300)}.dark .dark\:bg-yellow-400{background-color:var(--color-yellow-400)}.dark .dark\:bg-yellow-500{background-color:var(--color-yellow-500)}.dark .dark\:bg-yellow-600{background-color:var(--color-yellow-600)}.dark .dark\:bg-yellow-700{background-color:var(--color-yellow-700)}.dark .dark\:bg-yellow-800{background-color:var(--color-yellow-800)}.dark .dark\:bg-yellow-900{background-color:var(--color-yellow-900)}.dark .dark\:bg-yellow-900\/20{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/20{background-color:color-mix(in oklab,var(--color-yellow-900) 20%,transparent)}}.dark .dark\:bg-yellow-900\/30{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/30{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .dark\:bg-yellow-900\/40{background-color:#733e0a66}@supports (color:color-mix(in lab,red,red)){.dark .dark\:bg-yellow-900\/40{background-color:color-mix(in oklab,var(--color-yellow-900) 40%,transparent)}}.dark .dark\:bg-yellow-950{background-color:var(--color-yellow-950)}.dark .dark\:from-blue-900{--tw-gradient-from:var(--color-blue-900);--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))}.dark .dark\:from-gray-50{--tw-gradient-from:var(--color-gray-50);--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))}.dark .dark\:from-gray-100{--tw-gradient-from:var(--color-gray-100);--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))}.dark .dark\:from-gray-200{--tw-gradient-from:var(--color-gray-200);--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))}.dark .dark\:from-gray-300{--tw-gradient-from:var(--color-gray-300);--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))}.dark .dark\:from-gray-400{--tw-gradient-from:var(--color-gray-400);--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))}.dark .dark\:from-gray-500{--tw-gradient-from:var(--color-gray-500);--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))}.dark .dark\:from-gray-600{--tw-gradient-from:var(--color-gray-600);--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))}.dark .dark\:from-gray-700{--tw-gradient-from:var(--color-gray-700);--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))}.dark .dark\:from-gray-800{--tw-gradient-from:var(--color-gray-800);--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))}.dark .dark\:from-gray-900{--tw-gradient-from:var(--color-gray-900);--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))}.dark .dark\:to-gray-50{--tw-gradient-to:var(--color-gray-50);--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))}.dark .dark\:to-gray-100{--tw-gradient-to:var(--color-gray-100);--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))}.dark .dark\:to-gray-200{--tw-gradient-to:var(--color-gray-200);--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))}.dark .dark\:to-gray-300{--tw-gradient-to:var(--color-gray-300);--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))}.dark .dark\:to-gray-400{--tw-gradient-to:var(--color-gray-400);--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))}.dark .dark\:to-gray-500{--tw-gradient-to:var(--color-gray-500);--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))}.dark .dark\:to-gray-600{--tw-gradient-to:var(--color-gray-600);--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))}.dark .dark\:to-gray-700{--tw-gradient-to:var(--color-gray-700);--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))}.dark .dark\:to-gray-800{--tw-gradient-to:var(--color-gray-800);--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))}.dark .dark\:to-gray-900{--tw-gradient-to:var(--color-gray-900);--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))}.dark .dark\:to-indigo-900{--tw-gradient-to:var(--color-indigo-900);--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))}.dark .dark\:text-center{text-align:center}.dark .dark\:text-justify{text-align:justify}.dark .dark\:text-left{text-align:left}.dark .dark\:text-right{text-align:right}.dark .dark\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.dark .dark\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.dark .dark\:text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.dark .dark\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.dark .dark\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.dark .dark\:text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.dark .dark\:font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.dark .dark\:font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.dark .dark\:font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.dark .dark\:whitespace-normal{white-space:normal}.dark .dark\:whitespace-nowrap{white-space:nowrap}.dark .dark\:whitespace-pre-wrap{white-space:pre-wrap}.dark .dark\:text-amber-50{color:var(--color-amber-50)}.dark .dark\:text-amber-100{color:var(--color-amber-100)}.dark .dark\:text-amber-200{color:var(--color-amber-200)}.dark .dark\:text-amber-300{color:var(--color-amber-300)}.dark .dark\:text-amber-400{color:var(--color-amber-400)}.dark .dark\:text-amber-500{color:var(--color-amber-500)}.dark .dark\:text-amber-600{color:var(--color-amber-600)}.dark .dark\:text-amber-700{color:var(--color-amber-700)}.dark .dark\:text-amber-800{color:var(--color-amber-800)}.dark .dark\:text-amber-900{color:var(--color-amber-900)}.dark .dark\:text-black{color:var(--color-black)}.dark .dark\:text-blue-50{color:var(--color-blue-50)}.dark .dark\:text-blue-100{color:var(--color-blue-100)}.dark .dark\:text-blue-200{color:var(--color-blue-200)}.dark .dark\:text-blue-300{color:var(--color-blue-300)}.dark .dark\:text-blue-400{color:var(--color-blue-400)}.dark .dark\:text-blue-500{color:var(--color-blue-500)}.dark .dark\:text-blue-600{color:var(--color-blue-600)}.dark .dark\:text-blue-700{color:var(--color-blue-700)}.dark .dark\:text-blue-800{color:var(--color-blue-800)}.dark .dark\:text-blue-900{color:var(--color-blue-900)}.dark .dark\:text-cyan-50{color:var(--color-cyan-50)}.dark .dark\:text-cyan-100{color:var(--color-cyan-100)}.dark .dark\:text-cyan-200{color:var(--color-cyan-200)}.dark .dark\:text-cyan-300{color:var(--color-cyan-300)}.dark .dark\:text-cyan-400{color:var(--color-cyan-400)}.dark .dark\:text-cyan-500{color:var(--color-cyan-500)}.dark .dark\:text-cyan-600{color:var(--color-cyan-600)}.dark .dark\:text-cyan-700{color:var(--color-cyan-700)}.dark .dark\:text-cyan-800{color:var(--color-cyan-800)}.dark .dark\:text-cyan-900{color:var(--color-cyan-900)}.dark .dark\:text-emerald-50{color:var(--color-emerald-50)}.dark .dark\:text-emerald-100{color:var(--color-emerald-100)}.dark .dark\:text-emerald-200{color:var(--color-emerald-200)}.dark .dark\:text-emerald-300{color:var(--color-emerald-300)}.dark .dark\:text-emerald-400{color:var(--color-emerald-400)}.dark .dark\:text-emerald-500{color:var(--color-emerald-500)}.dark .dark\:text-emerald-600{color:var(--color-emerald-600)}.dark .dark\:text-emerald-700{color:var(--color-emerald-700)}.dark .dark\:text-emerald-800{color:var(--color-emerald-800)}.dark .dark\:text-emerald-900{color:var(--color-emerald-900)}.dark .dark\:text-gray-50{color:var(--color-gray-50)}.dark .dark\:text-gray-100{color:var(--color-gray-100)}.dark .dark\:text-gray-200{color:var(--color-gray-200)}.dark .dark\:text-gray-300{color:var(--color-gray-300)}.dark .dark\:text-gray-400{color:var(--color-gray-400)}.dark .dark\:text-gray-500{color:var(--color-gray-500)}.dark .dark\:text-gray-600{color:var(--color-gray-600)}.dark .dark\:text-gray-700{color:var(--color-gray-700)}.dark .dark\:text-gray-800{color:var(--color-gray-800)}.dark .dark\:text-gray-900{color:var(--color-gray-900)}.dark .dark\:text-green-50{color:var(--color-green-50)}.dark .dark\:text-green-100{color:var(--color-green-100)}.dark .dark\:text-green-200{color:var(--color-green-200)}.dark .dark\:text-green-300{color:var(--color-green-300)}.dark .dark\:text-green-400{color:var(--color-green-400)}.dark .dark\:text-green-500{color:var(--color-green-500)}.dark .dark\:text-green-600{color:var(--color-green-600)}.dark .dark\:text-green-700{color:var(--color-green-700)}.dark .dark\:text-green-800{color:var(--color-green-800)}.dark .dark\:text-green-900{color:var(--color-green-900)}.dark .dark\:text-indigo-50{color:var(--color-indigo-50)}.dark .dark\:text-indigo-100{color:var(--color-indigo-100)}.dark .dark\:text-indigo-200{color:var(--color-indigo-200)}.dark .dark\:text-indigo-300{color:var(--color-indigo-300)}.dark .dark\:text-indigo-400{color:var(--color-indigo-400)}.dark .dark\:text-indigo-500{color:var(--color-indigo-500)}.dark .dark\:text-indigo-600{color:var(--color-indigo-600)}.dark .dark\:text-indigo-700{color:var(--color-indigo-700)}.dark .dark\:text-indigo-800{color:var(--color-indigo-800)}.dark .dark\:text-indigo-900{color:var(--color-indigo-900)}.dark .dark\:text-orange-50{color:var(--color-orange-50)}.dark .dark\:text-orange-100{color:var(--color-orange-100)}.dark .dark\:text-orange-200{color:var(--color-orange-200)}.dark .dark\:text-orange-300{color:var(--color-orange-300)}.dark .dark\:text-orange-400{color:var(--color-orange-400)}.dark .dark\:text-orange-500{color:var(--color-orange-500)}.dark .dark\:text-orange-600{color:var(--color-orange-600)}.dark .dark\:text-orange-700{color:var(--color-orange-700)}.dark .dark\:text-orange-800{color:var(--color-orange-800)}.dark .dark\:text-orange-900{color:var(--color-orange-900)}.dark .dark\:text-pink-50{color:var(--color-pink-50)}.dark .dark\:text-pink-100{color:var(--color-pink-100)}.dark .dark\:text-pink-200{color:var(--color-pink-200)}.dark .dark\:text-pink-300{color:var(--color-pink-300)}.dark .dark\:text-pink-400{color:var(--color-pink-400)}.dark .dark\:text-pink-500{color:var(--color-pink-500)}.dark .dark\:text-pink-600{color:var(--color-pink-600)}.dark .dark\:text-pink-700{color:var(--color-pink-700)}.dark .dark\:text-pink-800{color:var(--color-pink-800)}.dark .dark\:text-pink-900{color:var(--color-pink-900)}.dark .dark\:text-purple-50{color:var(--color-purple-50)}.dark .dark\:text-purple-100{color:var(--color-purple-100)}.dark .dark\:text-purple-200{color:var(--color-purple-200)}.dark .dark\:text-purple-300{color:var(--color-purple-300)}.dark .dark\:text-purple-400{color:var(--color-purple-400)}.dark .dark\:text-purple-500{color:var(--color-purple-500)}.dark .dark\:text-purple-600{color:var(--color-purple-600)}.dark .dark\:text-purple-700{color:var(--color-purple-700)}.dark .dark\:text-purple-800{color:var(--color-purple-800)}.dark .dark\:text-purple-900{color:var(--color-purple-900)}.dark .dark\:text-red-50{color:var(--color-red-50)}.dark .dark\:text-red-100{color:var(--color-red-100)}.dark .dark\:text-red-200{color:var(--color-red-200)}.dark .dark\:text-red-300{color:var(--color-red-300)}.dark .dark\:text-red-400{color:var(--color-red-400)}.dark .dark\:text-red-500{color:var(--color-red-500)}.dark .dark\:text-red-600{color:var(--color-red-600)}.dark .dark\:text-red-700{color:var(--color-red-700)}.dark .dark\:text-red-800{color:var(--color-red-800)}.dark .dark\:text-red-900{color:var(--color-red-900)}.dark .dark\:text-slate-50{color:var(--color-slate-50)}.dark .dark\:text-slate-100{color:var(--color-slate-100)}.dark .dark\:text-slate-200{color:var(--color-slate-200)}.dark .dark\:text-slate-300{color:var(--color-slate-300)}.dark .dark\:text-slate-400{color:var(--color-slate-400)}.dark .dark\:text-slate-500{color:var(--color-slate-500)}.dark .dark\:text-slate-600{color:var(--color-slate-600)}.dark .dark\:text-slate-700{color:var(--color-slate-700)}.dark .dark\:text-slate-800{color:var(--color-slate-800)}.dark .dark\:text-slate-900{color:var(--color-slate-900)}.dark .dark\:text-teal-50{color:var(--color-teal-50)}.dark .dark\:text-teal-100{color:var(--color-teal-100)}.dark .dark\:text-teal-200{color:var(--color-teal-200)}.dark .dark\:text-teal-300{color:var(--color-teal-300)}.dark .dark\:text-teal-400{color:var(--color-teal-400)}.dark .dark\:text-teal-500{color:var(--color-teal-500)}.dark .dark\:text-teal-600{color:var(--color-teal-600)}.dark .dark\:text-teal-700{color:var(--color-teal-700)}.dark .dark\:text-teal-800{color:var(--color-teal-800)}.dark .dark\:text-teal-900{color:var(--color-teal-900)}.dark .dark\:text-white{color:var(--color-white)}.dark .dark\:text-yellow-50{color:var(--color-yellow-50)}.dark .dark\:text-yellow-100{color:var(--color-yellow-100)}.dark .dark\:text-yellow-200{color:var(--color-yellow-200)}.dark .dark\:text-yellow-300{color:var(--color-yellow-300)}.dark .dark\:text-yellow-400{color:var(--color-yellow-400)}.dark .dark\:text-yellow-500{color:var(--color-yellow-500)}.dark .dark\:text-yellow-600{color:var(--color-yellow-600)}.dark .dark\:text-yellow-700{color:var(--color-yellow-700)}.dark .dark\:text-yellow-800{color:var(--color-yellow-800)}.dark .dark\:text-yellow-900{color:var(--color-yellow-900)}.dark .dark\:italic{font-style:italic}.dark .dark\:underline{text-decoration-line:underline}.dark .dark\:placeholder-gray-400::placeholder{color:var(--color-gray-400)}.dark .dark\:placeholder-gray-500::placeholder{color:var(--color-gray-500)}.dark .dark\:placeholder-gray-600::placeholder{color:var(--color-gray-600)}.dark .dark\:opacity-0{opacity:0}.dark .dark\:opacity-25{opacity:.25}.dark .dark\:opacity-50{opacity:.5}.dark .dark\:opacity-75{opacity:.75}.dark .dark\:opacity-100{opacity:1}.dark .dark\: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)}.dark .dark\:shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\:shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\: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)}.dark .dark\:shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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)}.dark .dark\:shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.dark .dark\: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)}.dark .dark\:shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px 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)}.dark .dark\:shadow-purple-900\/50{--tw-shadow-color:#59168b80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:shadow-purple-900\/50{--tw-shadow-color:color-mix(in oklab, color-mix(in oklab, var(--color-purple-900) 50%, transparent) var(--tw-shadow-alpha), transparent)}}.dark .dark\:ring-blue-50{--tw-ring-color:var(--color-blue-50)}.dark .dark\:ring-blue-100{--tw-ring-color:var(--color-blue-100)}.dark .dark\:ring-blue-200{--tw-ring-color:var(--color-blue-200)}.dark .dark\:ring-blue-300{--tw-ring-color:var(--color-blue-300)}.dark .dark\:ring-blue-400{--tw-ring-color:var(--color-blue-400)}.dark .dark\:ring-blue-500{--tw-ring-color:var(--color-blue-500)}.dark .dark\:ring-blue-600{--tw-ring-color:var(--color-blue-600)}.dark .dark\:ring-blue-700{--tw-ring-color:var(--color-blue-700)}.dark .dark\:ring-blue-800{--tw-ring-color:var(--color-blue-800)}.dark .dark\:ring-blue-900{--tw-ring-color:var(--color-blue-900)}.dark .dark\:ring-gray-50{--tw-ring-color:var(--color-gray-50)}.dark .dark\:ring-gray-100{--tw-ring-color:var(--color-gray-100)}.dark .dark\:ring-gray-200{--tw-ring-color:var(--color-gray-200)}.dark .dark\:ring-gray-300{--tw-ring-color:var(--color-gray-300)}.dark .dark\:ring-gray-400{--tw-ring-color:var(--color-gray-400)}.dark .dark\:ring-gray-500{--tw-ring-color:var(--color-gray-500)}.dark .dark\:ring-gray-600{--tw-ring-color:var(--color-gray-600)}.dark .dark\:ring-gray-700{--tw-ring-color:var(--color-gray-700)}.dark .dark\:ring-gray-800{--tw-ring-color:var(--color-gray-800)}.dark .dark\:ring-gray-900{--tw-ring-color:var(--color-gray-900)}.dark .dark\:ring-offset-gray-800{--tw-ring-offset-color:var(--color-gray-800)}.dark .dark\:ring-offset-gray-900{--tw-ring-offset-color:var(--color-gray-900)}@media(hover:hover){.dark .dark\:group-hover\:bg-gray-700:is(:where(.group):hover *){background-color:var(--color-gray-700)}.dark .dark\:group-hover\:bg-gray-800:is(:where(.group):hover *){background-color:var(--color-gray-800)}.dark .dark\:group-hover\:text-gray-100:is(:where(.group):hover *){color:var(--color-gray-100)}.dark .dark\:group-hover\:text-white:is(:where(.group):hover *){color:var(--color-white)}}.dark .dark\:selection\:bg-blue-800 ::selection{background-color:var(--color-blue-800)}.dark .dark\:selection\:bg-blue-800::selection{background-color:var(--color-blue-800)}.dark .dark\:selection\:text-blue-100 ::selection{color:var(--color-blue-100)}.dark .dark\:selection\:text-blue-100::selection{color:var(--color-blue-100)}.dark .dark\:odd\:bg-gray-800:nth-child(odd){background-color:var(--color-gray-800)}.dark .dark\:odd\:bg-gray-900:nth-child(odd){background-color:var(--color-gray-900)}.dark .dark\:even\:bg-gray-800:nth-child(2n){background-color:var(--color-gray-800)}.dark .dark\:even\:bg-gray-900:nth-child(2n){background-color:var(--color-gray-900)}@media(hover:hover){.dark .dark\:hover\:border-amber-50:hover{border-color:var(--color-amber-50)}.dark .dark\:hover\:border-amber-100:hover{border-color:var(--color-amber-100)}.dark .dark\:hover\:border-amber-200:hover{border-color:var(--color-amber-200)}.dark .dark\:hover\:border-amber-300:hover{border-color:var(--color-amber-300)}.dark .dark\:hover\:border-amber-400:hover{border-color:var(--color-amber-400)}.dark .dark\:hover\:border-amber-500:hover{border-color:var(--color-amber-500)}.dark .dark\:hover\:border-amber-600:hover{border-color:var(--color-amber-600)}.dark .dark\:hover\:border-amber-700:hover{border-color:var(--color-amber-700)}.dark .dark\:hover\:border-amber-800:hover{border-color:var(--color-amber-800)}.dark .dark\:hover\:border-amber-900:hover{border-color:var(--color-amber-900)}.dark .dark\:hover\:border-blue-50:hover{border-color:var(--color-blue-50)}.dark .dark\:hover\:border-blue-100:hover{border-color:var(--color-blue-100)}.dark .dark\:hover\:border-blue-200:hover{border-color:var(--color-blue-200)}.dark .dark\:hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.dark .dark\:hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.dark .dark\:hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.dark .dark\:hover\:border-blue-600:hover{border-color:var(--color-blue-600)}.dark .dark\:hover\:border-blue-700:hover{border-color:var(--color-blue-700)}.dark .dark\:hover\:border-blue-800:hover{border-color:var(--color-blue-800)}.dark .dark\:hover\:border-blue-900:hover{border-color:var(--color-blue-900)}.dark .dark\:hover\:border-gray-50:hover{border-color:var(--color-gray-50)}.dark .dark\:hover\:border-gray-100:hover{border-color:var(--color-gray-100)}.dark .dark\:hover\:border-gray-200:hover{border-color:var(--color-gray-200)}.dark .dark\:hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.dark .dark\:hover\:border-gray-400:hover{border-color:var(--color-gray-400)}.dark .dark\:hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.dark .dark\:hover\:border-gray-600:hover{border-color:var(--color-gray-600)}.dark .dark\:hover\:border-gray-700:hover{border-color:var(--color-gray-700)}.dark .dark\:hover\:border-gray-800:hover{border-color:var(--color-gray-800)}.dark .dark\:hover\:border-gray-900:hover{border-color:var(--color-gray-900)}.dark .dark\:hover\:bg-amber-50:hover{background-color:var(--color-amber-50)}.dark .dark\:hover\:bg-amber-100:hover{background-color:var(--color-amber-100)}.dark .dark\:hover\:bg-amber-200:hover{background-color:var(--color-amber-200)}.dark .dark\:hover\:bg-amber-300:hover{background-color:var(--color-amber-300)}.dark .dark\:hover\:bg-amber-400:hover{background-color:var(--color-amber-400)}.dark .dark\:hover\:bg-amber-500:hover{background-color:var(--color-amber-500)}.dark .dark\:hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.dark .dark\:hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.dark .dark\:hover\:bg-amber-800:hover{background-color:var(--color-amber-800)}.dark .dark\:hover\:bg-amber-900:hover{background-color:var(--color-amber-900)}.dark .dark\:hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.dark .dark\:hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.dark .dark\:hover\:bg-blue-200:hover{background-color:var(--color-blue-200)}.dark .dark\:hover\:bg-blue-300:hover{background-color:var(--color-blue-300)}.dark .dark\:hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.dark .dark\:hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.dark .dark\:hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.dark .dark\:hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.dark .dark\:hover\:bg-blue-800:hover{background-color:var(--color-blue-800)}.dark .dark\:hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.dark .dark\:hover\:bg-blue-900\/30:hover{background-color:#1c398e4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-blue-900\/30:hover{background-color:color-mix(in oklab,var(--color-blue-900) 30%,transparent)}}.dark .dark\:hover\:bg-blue-900\/50:hover{background-color:#1c398e80}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-blue-900\/50:hover{background-color:color-mix(in oklab,var(--color-blue-900) 50%,transparent)}}.dark .dark\:hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.dark .dark\:hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.dark .dark\:hover\:bg-gray-200:hover{background-color:var(--color-gray-200)}.dark .dark\:hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.dark .dark\:hover\:bg-gray-400:hover{background-color:var(--color-gray-400)}.dark .dark\:hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.dark .dark\:hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.dark .dark\:hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}.dark .dark\:hover\:bg-gray-700\/30:hover{background-color:#3641534d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-700\/30:hover{background-color:color-mix(in oklab,var(--color-gray-700) 30%,transparent)}}.dark .dark\:hover\:bg-gray-700\/50:hover{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-700\/50:hover{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}.dark .dark\:hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.dark .dark\:hover\:bg-gray-800\/50:hover{background-color:#1e293980}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-gray-800\/50:hover{background-color:color-mix(in oklab,var(--color-gray-800) 50%,transparent)}}.dark .dark\:hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.dark .dark\:hover\:bg-green-50:hover{background-color:var(--color-green-50)}.dark .dark\:hover\:bg-green-100:hover{background-color:var(--color-green-100)}.dark .dark\:hover\:bg-green-200:hover{background-color:var(--color-green-200)}.dark .dark\:hover\:bg-green-300:hover{background-color:var(--color-green-300)}.dark .dark\:hover\:bg-green-400:hover{background-color:var(--color-green-400)}.dark .dark\:hover\:bg-green-500:hover{background-color:var(--color-green-500)}.dark .dark\:hover\:bg-green-600:hover{background-color:var(--color-green-600)}.dark .dark\:hover\:bg-green-700:hover{background-color:var(--color-green-700)}.dark .dark\:hover\:bg-green-800:hover{background-color:var(--color-green-800)}.dark .dark\:hover\:bg-green-900:hover{background-color:var(--color-green-900)}.dark .dark\:hover\:bg-green-900\/30:hover{background-color:#0d542b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-green-900\/30:hover{background-color:color-mix(in oklab,var(--color-green-900) 30%,transparent)}}.dark .dark\:hover\:bg-indigo-50:hover{background-color:var(--color-indigo-50)}.dark .dark\:hover\:bg-indigo-100:hover{background-color:var(--color-indigo-100)}.dark .dark\:hover\:bg-indigo-200:hover{background-color:var(--color-indigo-200)}.dark .dark\:hover\:bg-indigo-300:hover{background-color:var(--color-indigo-300)}.dark .dark\:hover\:bg-indigo-400:hover{background-color:var(--color-indigo-400)}.dark .dark\:hover\:bg-indigo-500:hover{background-color:var(--color-indigo-500)}.dark .dark\:hover\:bg-indigo-600:hover{background-color:var(--color-indigo-600)}.dark .dark\:hover\:bg-indigo-700:hover{background-color:var(--color-indigo-700)}.dark .dark\:hover\:bg-indigo-800:hover{background-color:var(--color-indigo-800)}.dark .dark\:hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.dark .dark\:hover\:bg-indigo-900\/30:hover{background-color:#312c854d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-indigo-900\/30:hover{background-color:color-mix(in oklab,var(--color-indigo-900) 30%,transparent)}}.dark .dark\:hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.dark .dark\:hover\:bg-orange-100:hover{background-color:var(--color-orange-100)}.dark .dark\:hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.dark .dark\:hover\:bg-orange-300:hover{background-color:var(--color-orange-300)}.dark .dark\:hover\:bg-orange-400:hover{background-color:var(--color-orange-400)}.dark .dark\:hover\:bg-orange-500:hover{background-color:var(--color-orange-500)}.dark .dark\:hover\:bg-orange-600:hover{background-color:var(--color-orange-600)}.dark .dark\:hover\:bg-orange-700:hover{background-color:var(--color-orange-700)}.dark .dark\:hover\:bg-orange-800:hover{background-color:var(--color-orange-800)}.dark .dark\:hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.dark .dark\:hover\:bg-orange-900\/30:hover{background-color:#7e2a0c4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-orange-900\/30:hover{background-color:color-mix(in oklab,var(--color-orange-900) 30%,transparent)}}.dark .dark\:hover\:bg-pink-50:hover{background-color:var(--color-pink-50)}.dark .dark\:hover\:bg-pink-100:hover{background-color:var(--color-pink-100)}.dark .dark\:hover\:bg-pink-200:hover{background-color:var(--color-pink-200)}.dark .dark\:hover\:bg-pink-300:hover{background-color:var(--color-pink-300)}.dark .dark\:hover\:bg-pink-400:hover{background-color:var(--color-pink-400)}.dark .dark\:hover\:bg-pink-500:hover{background-color:var(--color-pink-500)}.dark .dark\:hover\:bg-pink-600:hover{background-color:var(--color-pink-600)}.dark .dark\:hover\:bg-pink-700:hover{background-color:var(--color-pink-700)}.dark .dark\:hover\:bg-pink-800:hover{background-color:var(--color-pink-800)}.dark .dark\:hover\:bg-pink-900:hover{background-color:var(--color-pink-900)}.dark .dark\:hover\:bg-purple-50:hover{background-color:var(--color-purple-50)}.dark .dark\:hover\:bg-purple-100:hover{background-color:var(--color-purple-100)}.dark .dark\:hover\:bg-purple-200:hover{background-color:var(--color-purple-200)}.dark .dark\:hover\:bg-purple-300:hover{background-color:var(--color-purple-300)}.dark .dark\:hover\:bg-purple-400:hover{background-color:var(--color-purple-400)}.dark .dark\:hover\:bg-purple-500:hover{background-color:var(--color-purple-500)}.dark .dark\:hover\:bg-purple-600:hover{background-color:var(--color-purple-600)}.dark .dark\:hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.dark .dark\:hover\:bg-purple-800:hover{background-color:var(--color-purple-800)}.dark .dark\:hover\:bg-purple-900:hover{background-color:var(--color-purple-900)}.dark .dark\:hover\:bg-purple-900\/30:hover{background-color:#59168b4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-purple-900\/30:hover{background-color:color-mix(in oklab,var(--color-purple-900) 30%,transparent)}}.dark .dark\:hover\:bg-red-50:hover{background-color:var(--color-red-50)}.dark .dark\:hover\:bg-red-100:hover{background-color:var(--color-red-100)}.dark .dark\:hover\:bg-red-200:hover{background-color:var(--color-red-200)}.dark .dark\:hover\:bg-red-300:hover{background-color:var(--color-red-300)}.dark .dark\:hover\:bg-red-400:hover{background-color:var(--color-red-400)}.dark .dark\:hover\:bg-red-500:hover{background-color:var(--color-red-500)}.dark .dark\:hover\:bg-red-600:hover{background-color:var(--color-red-600)}.dark .dark\:hover\:bg-red-700:hover{background-color:var(--color-red-700)}.dark .dark\:hover\:bg-red-800:hover{background-color:var(--color-red-800)}.dark .dark\:hover\:bg-red-900:hover{background-color:var(--color-red-900)}.dark .dark\:hover\:bg-red-900\/20:hover{background-color:#82181a33}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-red-900\/20:hover{background-color:color-mix(in oklab,var(--color-red-900) 20%,transparent)}}.dark .dark\:hover\:bg-red-900\/30:hover{background-color:#82181a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-red-900\/30:hover{background-color:color-mix(in oklab,var(--color-red-900) 30%,transparent)}}.dark .dark\:hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.dark .dark\:hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.dark .dark\:hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.dark .dark\:hover\:bg-slate-300:hover{background-color:var(--color-slate-300)}.dark .dark\:hover\:bg-slate-400:hover{background-color:var(--color-slate-400)}.dark .dark\:hover\:bg-slate-500:hover{background-color:var(--color-slate-500)}.dark .dark\:hover\:bg-slate-600:hover{background-color:var(--color-slate-600)}.dark .dark\:hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.dark .dark\:hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.dark .dark\:hover\:bg-slate-900:hover{background-color:var(--color-slate-900)}.dark .dark\:hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.dark .dark\:hover\:bg-yellow-500:hover{background-color:var(--color-yellow-500)}.dark .dark\:hover\:bg-yellow-800:hover{background-color:var(--color-yellow-800)}.dark .dark\:hover\:bg-yellow-900\/30:hover{background-color:#733e0a4d}@supports (color:color-mix(in lab,red,red)){.dark .dark\:hover\:bg-yellow-900\/30:hover{background-color:color-mix(in oklab,var(--color-yellow-900) 30%,transparent)}}.dark .dark\:hover\:text-black:hover{color:var(--color-black)}.dark .dark\:hover\:text-blue-50:hover{color:var(--color-blue-50)}.dark .dark\:hover\:text-blue-100:hover{color:var(--color-blue-100)}.dark .dark\:hover\:text-blue-200:hover{color:var(--color-blue-200)}.dark .dark\:hover\:text-blue-300:hover{color:var(--color-blue-300)}.dark .dark\:hover\:text-blue-400:hover{color:var(--color-blue-400)}.dark .dark\:hover\:text-blue-500:hover{color:var(--color-blue-500)}.dark .dark\:hover\:text-blue-600:hover{color:var(--color-blue-600)}.dark .dark\:hover\:text-blue-700:hover{color:var(--color-blue-700)}.dark .dark\:hover\:text-blue-800:hover{color:var(--color-blue-800)}.dark .dark\:hover\:text-blue-900:hover{color:var(--color-blue-900)}.dark .dark\:hover\:text-gray-50:hover{color:var(--color-gray-50)}.dark .dark\:hover\:text-gray-100:hover{color:var(--color-gray-100)}.dark .dark\:hover\:text-gray-200:hover{color:var(--color-gray-200)}.dark .dark\:hover\:text-gray-300:hover{color:var(--color-gray-300)}.dark .dark\:hover\:text-gray-400:hover{color:var(--color-gray-400)}.dark .dark\:hover\:text-gray-500:hover{color:var(--color-gray-500)}.dark .dark\:hover\:text-gray-600:hover{color:var(--color-gray-600)}.dark .dark\:hover\:text-gray-700:hover{color:var(--color-gray-700)}.dark .dark\:hover\:text-gray-800:hover{color:var(--color-gray-800)}.dark .dark\:hover\:text-gray-900:hover{color:var(--color-gray-900)}.dark .dark\:hover\:text-green-50:hover{color:var(--color-green-50)}.dark .dark\:hover\:text-green-100:hover{color:var(--color-green-100)}.dark .dark\:hover\:text-green-200:hover{color:var(--color-green-200)}.dark .dark\:hover\:text-green-300:hover{color:var(--color-green-300)}.dark .dark\:hover\:text-green-400:hover{color:var(--color-green-400)}.dark .dark\:hover\:text-green-500:hover{color:var(--color-green-500)}.dark .dark\:hover\:text-green-600:hover{color:var(--color-green-600)}.dark .dark\:hover\:text-green-700:hover{color:var(--color-green-700)}.dark .dark\:hover\:text-green-800:hover{color:var(--color-green-800)}.dark .dark\:hover\:text-green-900:hover{color:var(--color-green-900)}.dark .dark\:hover\:text-purple-200:hover{color:var(--color-purple-200)}.dark .dark\:hover\:text-purple-300:hover{color:var(--color-purple-300)}.dark .dark\:hover\:text-red-50:hover{color:var(--color-red-50)}.dark .dark\:hover\:text-red-100:hover{color:var(--color-red-100)}.dark .dark\:hover\:text-red-200:hover{color:var(--color-red-200)}.dark .dark\:hover\:text-red-300:hover{color:var(--color-red-300)}.dark .dark\:hover\:text-red-400:hover{color:var(--color-red-400)}.dark .dark\:hover\:text-red-500:hover{color:var(--color-red-500)}.dark .dark\:hover\:text-red-600:hover{color:var(--color-red-600)}.dark .dark\:hover\:text-red-700:hover{color:var(--color-red-700)}.dark .dark\:hover\:text-red-800:hover{color:var(--color-red-800)}.dark .dark\:hover\:text-red-900:hover{color:var(--color-red-900)}.dark .dark\:hover\:text-slate-800:hover{color:var(--color-slate-800)}.dark .dark\:hover\:text-white:hover{color:var(--color-white)}}.dark .dark\:focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.dark .dark\:focus\:border-gray-500:focus{border-color:var(--color-gray-500)}.dark .dark\:focus\:border-gray-600:focus{border-color:var(--color-gray-600)}.dark .dark\:focus\:border-gray-700:focus{border-color:var(--color-gray-700)}.dark .dark\:focus\:bg-gray-700:focus{background-color:var(--color-gray-700)}.dark .dark\:focus\:bg-gray-800:focus{background-color:var(--color-gray-800)}.dark .dark\:focus\:bg-gray-900:focus{background-color:var(--color-gray-900)}.dark .dark\:focus\:text-gray-100:focus{color:var(--color-gray-100)}.dark .dark\:focus\:text-gray-200:focus{color:var(--color-gray-200)}.dark .dark\:focus\:text-gray-300:focus{color:var(--color-gray-300)}.dark .dark\:focus\:text-white:focus{color:var(--color-white)}.dark .dark\:focus\:ring-amber-400:focus{--tw-ring-color:var(--color-amber-400)}.dark .dark\:focus\:ring-amber-500:focus{--tw-ring-color:var(--color-amber-500)}.dark .dark\:focus\:ring-amber-600:focus{--tw-ring-color:var(--color-amber-600)}.dark .dark\:focus\:ring-blue-400:focus{--tw-ring-color:var(--color-blue-400)}.dark .dark\:focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.dark .dark\:focus\:ring-blue-600:focus{--tw-ring-color:var(--color-blue-600)}.dark .dark\:focus\:ring-blue-700:focus{--tw-ring-color:var(--color-blue-700)}.dark .dark\:focus\:ring-gray-400:focus{--tw-ring-color:var(--color-gray-400)}.dark .dark\:focus\:ring-gray-500:focus{--tw-ring-color:var(--color-gray-500)}.dark .dark\:focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.dark .dark\:focus\:ring-gray-700:focus{--tw-ring-color:var(--color-gray-700)}.dark .dark\:focus\:ring-indigo-400:focus{--tw-ring-color:var(--color-indigo-400)}.dark .dark\:focus\:ring-red-400:focus{--tw-ring-color:var(--color-red-400)}.dark .dark\:focus\:ring-red-500:focus{--tw-ring-color:var(--color-red-500)}.dark .dark\:focus\:ring-slate-500:focus{--tw-ring-color:var(--color-slate-500)}.dark .dark\:focus\:ring-offset-gray-800:focus{--tw-ring-offset-color:var(--color-gray-800)}.dark .dark\:focus\:ring-offset-gray-900:focus{--tw-ring-offset-color:var(--color-gray-900)}.dark .dark\:active\:bg-gray-600:active{background-color:var(--color-gray-600)}.dark .dark\:active\:bg-gray-700:active{background-color:var(--color-gray-700)}.dark .dark\:active\:bg-gray-800:active{background-color:var(--color-gray-800)}.dark .dark\:disabled\:bg-gray-700:disabled{background-color:var(--color-gray-700)}.dark .dark\:disabled\:bg-gray-800:disabled{background-color:var(--color-gray-800)}.dark .dark\:disabled\:bg-gray-900:disabled{background-color:var(--color-gray-900)}.dark .dark\:disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.dark .dark\:disabled\:text-gray-600:disabled{color:var(--color-gray-600)}@media(hover:hover){.dark .dark\:disabled\:hover\:bg-indigo-500:disabled:hover{background-color:var(--color-indigo-500)}}.\[\&_table\]\:w-full table{width:100%}.\[\&_table\]\:border-collapse table{border-collapse:collapse}.\[\&_table\]\:border table{border-style:var(--tw-border-style);border-width:1px}.\[\&_table\]\:border-gray-300 table{border-color:var(--color-gray-300)}.dark .dark\:\[\&_table\]\:border-gray-600 table{border-color:var(--color-gray-600)}.\[\&_td\]\:border td{border-style:var(--tw-border-style);border-width:1px}.\[\&_td\]\:border-gray-300 td{border-color:var(--color-gray-300)}.\[\&_td\]\:px-4 td{padding-inline:calc(var(--spacing) * 4)}.\[\&_td\]\:py-2 td{padding-block:calc(var(--spacing) * 2)}.dark .dark\:\[\&_td\]\:border-gray-600 td{border-color:var(--color-gray-600)}.\[\&_th\]\:border th{border-style:var(--tw-border-style);border-width:1px}.\[\&_th\]\:border-gray-300 th{border-color:var(--color-gray-300)}.\[\&_th\]\:bg-gray-100 th{background-color:var(--color-gray-100)}.\[\&_th\]\:px-4 th{padding-inline:calc(var(--spacing) * 4)}.\[\&_th\]\:py-2 th{padding-block:calc(var(--spacing) * 2)}.\[\&_th\]\:text-left th{text-align:left}.dark .dark\:\[\&_th\]\:border-gray-600 th{border-color:var(--color-gray-600)}.dark .dark\:\[\&_th\]\:bg-gray-800 th{background-color:var(--color-gray-800)}.\[\&_tr\:hover\]\:bg-gray-50 tr:hover{background-color:var(--color-gray-50)}.dark .dark\:\[\&_tr\:hover\]\:bg-gray-700\/50 tr:hover{background-color:#36415380}@supports (color:color-mix(in lab,red,red)){.dark .dark\:\[\&_tr\:hover\]\:bg-gray-700\/50 tr:hover{background-color:color-mix(in oklab,var(--color-gray-700) 50%,transparent)}}}.dashboard-content{max-width:80rem;margin:0 auto}.dashboard-stats-grid{gap:var(--spacing-5);margin-bottom:var(--spacing-8);grid-template-columns:1fr;display:grid}@media(min-width:768px){.dashboard-stats-grid{grid-template-columns:repeat(2,1fr)}}@media(min-width:1024px){.dashboard-stats-grid{grid-template-columns:repeat(4,1fr)}}.dashboard-main-grid{gap:var(--spacing-6);grid-template-columns:1fr;display:grid}@media(min-width:1024px){.dashboard-main-grid{grid-template-columns:repeat(5,1fr)}}.dashboard-column-left{gap:var(--spacing-6);flex-direction:column;display:flex}@media(min-width:1024px){.dashboard-column-left{grid-column:span 3}}.dashboard-column-right{gap:var(--spacing-6);flex-direction:column;display:flex}@media(min-width:1024px){.dashboard-column-right{grid-column:span 2}}.progress-bar-container{background-color:var(--color-slate-100);border-radius:var(--radius-full);width:100%;height:.5rem;overflow:hidden}.dark .progress-bar-container{background-color:var(--color-slate-700)}.progress-bar-fill{border-radius:var(--radius-full);height:.5rem;transition:width var(--transition-normal) var(--ease-in-out)}.progress-bar-emerald{background-color:var(--color-emerald-500)}.progress-bar-blue{background-color:var(--color-blue-500)}.progress-bar-slate{background-color:var(--color-slate-600)}.dark .progress-bar-slate{background-color:var(--color-slate-500)}.module-list{gap:var(--spacing-3);flex-direction:column;display:flex}.module-item{padding:var(--spacing-2) 0;justify-content:space-between;align-items:center;display:flex}.module-info{flex-direction:column;display:flex}.module-name{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .module-name{color:#fff}.module-version{font-size:var(--text-xs);color:var(--color-slate-500)}.dark .module-version{color:var(--color-slate-400)}.module-badge-active{padding:var(--spacing-1) var(--spacing-2);font-size:var(--text-xs);font-weight:var(--font-medium);background-color:var(--color-green-100);color:var(--color-green-700);border-radius:var(--radius-full)}.dark .module-badge-active{background-color:var(--color-green-900);color:var(--color-green-400)}.module-badge-inactive{padding:var(--spacing-1) var(--spacing-2);font-size:var(--text-xs);font-weight:var(--font-medium);background-color:var(--color-slate-100);color:var(--color-slate-600);border-radius:var(--radius-full)}.dark .module-badge-inactive{background-color:var(--color-slate-700);color:var(--color-slate-400)}.activity-list{gap:var(--spacing-4);flex-direction:column;display:flex}.activity-item{align-items:flex-start;gap:var(--spacing-3);display:flex}.activity-dot{width:.5rem;height:.5rem;margin-top:var(--spacing-2);border-radius:var(--radius-full);background-color:var(--color-blue-500);flex-shrink:0}.activity-content{flex:1;min-width:0}.activity-title{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .activity-title{color:#fff}.activity-desc{font-size:var(--text-sm);color:var(--color-slate-500)}.dark .activity-desc{color:var(--color-slate-400)}.activity-time{font-size:var(--text-xs);color:var(--color-slate-400);white-space:nowrap}.dark .activity-time{color:var(--color-slate-500)}.alert-list{gap:var(--spacing-3);flex-direction:column;display:flex}.alert-item{align-items:flex-start;gap:var(--spacing-3);padding:var(--spacing-3);border-radius:var(--radius-lg);display:flex}.alert-bg-info{background-color:var(--color-blue-50)}.dark .alert-bg-info{background-color:var(--color-slate-800)}.alert-bg-warning{background-color:var(--color-yellow-50)}.dark .alert-bg-warning{background-color:var(--color-slate-800)}.alert-bg-error{background-color:var(--color-red-50)}.dark .alert-bg-error{background-color:var(--color-slate-800)}.alert-bg-success{background-color:var(--color-green-50)}.dark .alert-bg-success{background-color:var(--color-slate-800)}.alert-dot{width:.5rem;height:.5rem;margin-top:var(--spacing-2);border-radius:var(--radius-full);flex-shrink:0}.alert-dot-info{background-color:var(--color-blue-500)}.alert-dot-warning{background-color:var(--color-yellow-500)}.alert-dot-error{background-color:var(--color-red-500)}.alert-dot-success{background-color:var(--color-green-500)}.alert-content{flex:1}.alert-title{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-900)}.dark .alert-title{color:#fff}.alert-time{font-size:var(--text-xs);color:var(--color-slate-500);margin-top:var(--spacing-1)}.dark .alert-time{color:var(--color-slate-400)}.text-subtitle{font-size:var(--text-sm);color:var(--color-slate-500)}.dark .text-subtitle{color:var(--color-slate-400)}.section-margin{margin-bottom:var(--spacing-6)}.flex-between{justify-content:space-between;align-items:center;display:flex}.flex-between-mb{margin-bottom:var(--spacing-2);justify-content:space-between;align-items:center;display:flex}.resource-label{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-700)}.dark .resource-label{color:var(--color-slate-300)}.resource-value{font-size:var(--text-sm);font-weight:var(--font-medium);color:var(--color-slate-700)}.dark .resource-value{color:var(--color-slate-300)}.icon-stat{width:1rem;height:1rem}.icon-trend{width:.875rem;height:.875rem}@media(max-width:640px){.input-split{flex-wrap:wrap}.input-split input{flex:1;min-width:0}.page-header{flex-direction:column;align-items:flex-start;gap:1rem}.page-actions{justify-content:flex-start;width:100%}}@media(min-width:640px){.responsive-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:1024px){.responsive-grid,.grid-cols-1-lg-3{grid-template-columns:repeat(3,minmax(0,1fr))}.col-span-1-lg{grid-column:span 1/span 1}.col-span-2-lg{grid-column:span 2/span 2}}@media(max-width:480px){.responsive-grid{grid-template-columns:1fr!important}}@media(max-width:768px){.table-mobile-cards{display:block}.table-mobile-cards>*+*{margin-top:1rem}.table-desktop{display:none!important}}@media(max-width:640px){.modal{max-width:calc(100% - 2rem);max-height:calc(100vh - 2rem);margin-left:1rem;margin-right:1rem;overflow-y:auto}.modal-overlay{align-items:flex-start;padding:2rem 1rem 1rem}.modal-footer{flex-direction:column;gap:.5rem}.modal-footer .btn,.card-account{width:100%}}.sidebar-scrollbar{scrollbar-width:thin;scrollbar-color:transparent transparent;transition:scrollbar-color .3s}.sidebar-scrollbar:hover{scrollbar-color:#d1d5db transparent}.dark .sidebar-scrollbar:hover{scrollbar-color:#4b5563 transparent}.sidebar-scrollbar::-webkit-scrollbar{width:6px}.sidebar-scrollbar::-webkit-scrollbar-track{background:0 0}.sidebar-scrollbar::-webkit-scrollbar-thumb{background:0 0;border-radius:3px;transition:background .3s}.sidebar-scrollbar:hover::-webkit-scrollbar-thumb{background:#d1d5db}.dark .sidebar-scrollbar:hover::-webkit-scrollbar-thumb{background:#4b5563}.sidebar-scrollbar::-webkit-scrollbar-thumb:hover{background:#9ca3af}.dark .sidebar-scrollbar::-webkit-scrollbar-thumb:hover{background:#6b7280}@property --tw-border-spacing-x{syntax:"";inherits:false;initial-value:0}@property --tw-border-spacing-y{syntax:"";inherits:false;initial-value:0}@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-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@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-pan-x{syntax:"*";inherits:false}@property --tw-pan-y{syntax:"*";inherits:false}@property --tw-pinch-zoom{syntax:"*";inherits:false}@property --tw-scroll-snap-strictness{syntax:"*";inherits:false;initial-value:proximity}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@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-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{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-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}.yarl__fullsize{height:100%;width:100%}.yarl__relative{position:relative}.yarl__portal{inset:0;opacity:0;overflow:hidden;pointer-events:auto;position:fixed;transition:opacity var(--yarl__fade_animation_duration,.25s) var(--yarl__fade_animation_timing_function,ease);z-index:var(--yarl__portal_zindex,9999)}.yarl__portal_open{opacity:1}.yarl__container{background-color:var(--yarl__container_background_color,var(--yarl__color_backdrop,#000));inset:0;outline:none;overflow:hidden;overscroll-behavior:var(--yarl__controller_overscroll_behavior,contain);position:absolute;touch-action:var(--yarl__controller_touch_action,none);-webkit-user-select:none;-moz-user-select:none;user-select:none}.yarl__carousel{align-content:center;align-items:stretch;display:flex;flex:0 0 auto;height:100%;justify-content:center;opacity:var(--yarl__pull_opacity,1);transform:translate(var(--yarl__swipe_offset,0),var(--yarl__pull_offset,0));width:calc(100% + (var(--yarl__carousel_slides_count) - 1)*(100% + var(--yarl__carousel_spacing_px, 0)*1px + var(--yarl__carousel_spacing_percent, 0)*1%))}.yarl__carousel_with_slides{-moz-column-gap:calc(var(--yarl__carousel_spacing_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_spacing_percent, 0)*1%);column-gap:calc(var(--yarl__carousel_spacing_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_spacing_percent, 0)*1%)}.yarl__flex_center{align-content:center;align-items:center;display:flex;justify-content:center}.yarl__slide{flex:1;overflow:hidden;padding:calc(var(--yarl__carousel_padding_px, 0)*1px + 100/(var(--yarl__carousel_slides_count)*100 + (var(--yarl__carousel_slides_count) - 1)*var(--yarl__carousel_spacing_percent, 0))*var(--yarl__carousel_padding_percent, 0)*1%);position:relative}[dir=rtl] .yarl__slide{--yarl__direction:-1}.yarl__slide_image{max-height:100%;max-width:100%;-o-object-fit:contain;object-fit:contain;touch-action:var(--yarl__controller_touch_action,none);-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-touch-callout:none}.yarl__slide_image_cover{height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.yarl__slide_image_loading{opacity:0}@media screen and (min-width:800px){.yarl__slide_wrapper:not(.yarl__slide_wrapper_interactive) .yarl__slide_image{-webkit-backface-visibility:hidden;-webkit-transform:translateZ(0);-webkit-transform-style:preserve-3d}}.yarl__slide_placeholder{left:50%;line-height:0;position:absolute;top:50%;transform:translate(-50%) translateY(-50%)}.yarl__slide_loading{animation:yarl__delayed_fadein 1s linear;color:var(--yarl__slide_icon_loading_color,var(--yarl__color_button,hsla(0,0%,100%,.8)))}.yarl__slide_loading line{animation:yarl__stroke_opacity 1s linear infinite}.yarl__slide_loading line:first-of-type{animation-delay:-1.875s}.yarl__slide_loading line:nth-of-type(2){animation-delay:-1.75s}.yarl__slide_loading line:nth-of-type(3){animation-delay:-1.625s}.yarl__slide_loading line:nth-of-type(4){animation-delay:-1.5s}.yarl__slide_loading line:nth-of-type(5){animation-delay:-1.375s}.yarl__slide_loading line:nth-of-type(6){animation-delay:-1.25s}.yarl__slide_loading line:nth-of-type(7){animation-delay:-1.125s}.yarl__slide_loading line:nth-of-type(8){animation-delay:-1s}.yarl__slide_error{color:var(--yarl__slide_icon_error_color,red);height:var(--yarl__slide_icon_error_size,48px);width:var(--yarl__slide_icon_error_size,48px)}@media(prefers-reduced-motion){.yarl__portal,.yarl__slide{transition:unset}.yarl__slide_loading,.yarl__slide_loading line{animation:unset}}.yarl__toolbar{display:flex;justify-content:flex-end;inset:0 0 auto auto;padding:var(--yarl__toolbar_padding,8px);position:absolute}[dir=rtl] .yarl__toolbar{inset:0 auto auto 0}.yarl__icon{height:var(--yarl__icon_size,32px);width:var(--yarl__icon_size,32px)}.yarl__button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--yarl__button_background_color,transparent);border:var(--yarl__button_border,0);color:var(--yarl__color_button,hsla(0,0%,100%,.8));cursor:pointer;filter:var(--yarl__button_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));line-height:0;margin:var(--yarl__button_margin,0);outline:none;padding:var(--yarl__button_padding,8px);-webkit-tap-highlight-color:transparent}.yarl__button:focus{color:var(--yarl__color_button_active,#fff)}.yarl__button:focus:not(:focus-visible){color:var(--yarl__color_button,hsla(0,0%,100%,.8))}.yarl__button:focus-visible{color:var(--yarl__color_button_active,#fff)}@media(hover:hover){.yarl__button:focus-visible:hover,.yarl__button:focus:hover,.yarl__button:hover{color:var(--yarl__color_button_active,#fff)}}.yarl__button:disabled{color:var(--yarl__color_button_disabled,hsla(0,0%,100%,.4));cursor:default}.yarl__navigation_next,.yarl__navigation_prev{padding:var(--yarl__navigation_button_padding,24px 16px);position:absolute;top:50%;transform:translateY(-50%)}.yarl__navigation_prev{left:0}[dir=rtl] .yarl__navigation_prev{left:unset;right:0;transform:translateY(-50%) rotate(180deg)}.yarl__navigation_next{right:0}[dir=rtl] .yarl__navigation_next{left:0;right:unset;transform:translateY(-50%) rotate(180deg)}.yarl__no_scroll{height:100%;overflow:hidden;overscroll-behavior:none}@keyframes yarl__delayed_fadein{0%{opacity:0}80%{opacity:0}to{opacity:1}}@keyframes yarl__stroke_opacity{0%{stroke-opacity:1}to{stroke-opacity:.125}}.yarl__counter{bottom:var(--yarl__counter_bottom,unset);color:var(--yarl__counter_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));filter:var(--yarl__counter_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));left:var(--yarl__counter_left,0);line-height:var(--yarl__counter_line_height,var(--yarl__icon_size,32px));margin:var(--yarl__counter_margin,var(--yarl__toolbar_padding,8px));padding:var(--yarl__counter_padding,var(--yarl__button_padding,8px));position:var(--yarl__counter_position,absolute);right:var(--yarl__counter_right,unset);top:var(--yarl__counter_top,0);-webkit-user-select:var(--yarl__counter_user_select,none);-moz-user-select:var(--yarl__counter_user_select,none);user-select:var(--yarl__counter_user_select,none)}[dir=rtl] .yarl__counter{left:var(--yarl__counter_rtl_left,unset);right:var(--yarl__counter_rtl_right,0)}.yarl__thumbnails{display:flex;height:100%}.yarl__thumbnails_bottom,.yarl__thumbnails_end .yarl__thumbnails_track,.yarl__thumbnails_start .yarl__thumbnails_track,.yarl__thumbnails_top{flex-direction:column}.yarl__thumbnails_wrapper{flex:1;position:relative}.yarl__thumbnails_container{background-color:var(--yarl__thumbnails_container_background_color,var(--yarl__color_backdrop,#000));flex:0 0 auto;overflow:hidden;padding:var(--yarl__thumbnails_container_padding,16px);position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-touch-callout:none}.yarl__thumbnails_vignette{pointer-events:none;position:absolute;--yarl__thumbnails_vignette_size:12%}@media(min-width:1200px){.yarl__thumbnails_vignette{--yarl__thumbnails_vignette_size:8%}}@media(min-width:2000px){.yarl__thumbnails_vignette{--yarl__thumbnails_vignette_size:5%}}.yarl__thumbnails_bottom .yarl__thumbnails_vignette,.yarl__thumbnails_top .yarl__thumbnails_vignette{background:linear-gradient(to right,var(--yarl__color_backdrop,#000) 0,transparent var(--yarl__thumbnails_vignette_size,12%) calc(100% - var(--yarl__thumbnails_vignette_size, 12%)),var(--yarl__color_backdrop,#000) 100%);height:100%;left:0;right:0}.yarl__thumbnails_end .yarl__thumbnails_vignette,.yarl__thumbnails_start .yarl__thumbnails_vignette{background:linear-gradient(to bottom,var(--yarl__color_backdrop,#000) 0,transparent var(--yarl__thumbnails_vignette_size,12%) calc(100% - var(--yarl__thumbnails_vignette_size, 12%)),var(--yarl__color_backdrop,#000) 100%);bottom:0;top:0;width:100%}.yarl__thumbnails_track{gap:var(--yarl__thumbnails_thumbnail_gap,16px);outline:none}.yarl__thumbnails_thumbnail{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:var(--yarl__thumbnails_thumbnail_background,#000);border-color:var(--yarl__thumbnails_thumbnail_border_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));border-radius:var(--yarl__thumbnails_thumbnail_border_radius,4px);border-style:var(--yarl__thumbnails_thumbnail_border_style,solid);border-width:var(--yarl__thumbnails_thumbnail_border,1px);cursor:pointer;flex:0 0 auto;-webkit-tap-highlight-color:transparent;box-sizing:content-box;height:var(--yarl__thumbnails_thumbnail_height,80px);outline:none;overflow:hidden;padding:var(--yarl__thumbnails_thumbnail_padding,4px);position:relative;width:var(--yarl__thumbnails_thumbnail_width,120px)}.yarl__thumbnails_thumbnail_active{border-color:var(--yarl__thumbnails_thumbnail_active_border_color,var(--yarl__color_button_active,#fff))}.yarl__thumbnails_thumbnail_fadein{animation:yarl__thumbnails_thumbnail_fadein var(--yarl__thumbnails_thumbnail_fadein_duration,.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadein_delay,0s) forwards;opacity:0}.yarl__thumbnails_thumbnail_fadeout{animation:yarl__thumbnails_thumbnail_fadeout var(--yarl__thumbnails_thumbnail_fadeout_duration,.5s) ease-in-out var(--yarl__thumbnails_thumbnail_fadeout_delay,0s) forwards;cursor:unset}.yarl__thumbnails_thumbnail_placeholder{cursor:unset;visibility:hidden}.yarl__thumbnails_thumbnail:focus{box-shadow:var(--yarl__thumbnails_thumbnail_focus_box_shadow,#000 0 0 0 2px,var(--yarl__color_button,hsla(0,0%,100%,.8)) 0 0 0 4px)}.yarl__thumbnails_thumbnail:focus:not(:focus-visible){box-shadow:unset}.yarl__thumbnails_thumbnail:focus-visible{box-shadow:var(--yarl__thumbnails_thumbnail_focus_box_shadow,#000 0 0 0 2px,var(--yarl__color_button,hsla(0,0%,100%,.8)) 0 0 0 4px)}.yarl__thumbnails_thumbnail_icon{color:var(--yarl__thumbnails_thumbnail_icon_color,var(--yarl__color_button,hsla(0,0%,100%,.8)));filter:var(--yarl__thumbnails_thumbnail_icon_filter,drop-shadow(2px 2px 2px rgba(0,0,0,.8)));height:var(--yarl__thumbnails_thumbnail_icon_size,32px);left:50%;position:absolute;top:50%;transform:translate(-50%) translateY(-50%);width:var(--yarl__thumbnails_thumbnail_icon_size,32px)}@keyframes yarl__thumbnails_thumbnail_fadein{0%{opacity:0}to{opacity:1}}@keyframes yarl__thumbnails_thumbnail_fadeout{0%{opacity:1}to{opacity:0}} diff --git a/templates/_bundled/sirsoft-admin_basic/dist/js/components.iife.js b/templates/_bundled/sirsoft-admin_basic/dist/js/components.iife.js index da6748c7..a6896f26 100644 --- a/templates/_bundled/sirsoft-admin_basic/dist/js/components.iife.js +++ b/templates/_bundled/sirsoft-admin_basic/dist/js/components.iife.js @@ -1,20 +1,19 @@ -var SirsoftAdminBasic=(function(R,h,v,$n){"use strict";var nA=Object.defineProperty;var rA=(R,h,v)=>h in R?nA(R,h,{enumerable:!0,configurable:!0,writable:!0,value:v}):R[h]=v;var $e=(R,h,v)=>rA(R,typeof h!="symbol"?h+"":h,v);var Ha,wr;function fb(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const n in e)if(n!=="default"){const r=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(t,n,r.get?r:{enumerable:!0,get:()=>e[n]})}}return t.default=e,Object.freeze(t)}const F=fb(v),hb=()=>window.G7Core,ie=v.forwardRef(({children:e,variant:t="primary",size:n="md",className:r="",...s},o)=>{const i="button",a=hb()?.style?.mergeClasses?.(i,r)??`${i} ${r}`;return h.jsx("button",{ref:o,className:a,...s,children:e})});ie.displayName="Button";const $d=v.forwardRef(({accept:e,maxSize:t,onChange:n,onError:r,buttonText:s="Browse",placeholder:o="No file selected",className:i="",disabled:a,...l},c)=>{const d=v.useRef(null),u=c||d,[f,g]=v.useState(""),m={},p={};Object.entries(l).forEach(([x,M])=>{x.startsWith("data-editor-")||x==="id"||x==="onMouseMove"||x==="onMouseLeave"?m[x]=M:p[x]=M});const y=v.useCallback(()=>{!a&&u.current&&u.current.click()},[a,u]),b=v.useCallback(x=>{const M=x.target.files?.[0]||null;if(!M){g(""),n?.({target:{value:null,name:""}});return}if(t&&M.size>t*1024*1024){const C=`File size exceeds ${t}MB limit`;r?.(C),u.current&&(u.current.value=""),g("");return}g(M.name),n?.({target:{value:M,name:M.name}})},[t,n,r,u]),_=v.useCallback(x=>{x.stopPropagation(),u.current&&(u.current.value=""),g(""),n?.({target:{value:null,name:""}})},[n,u]);return h.jsxs("div",{className:`flex items-center gap-2 ${i}`,...m,children:[h.jsx("input",{ref:u,type:"file",accept:e,onChange:b,disabled:a,className:"hidden",...p}),h.jsx("button",{type:"button",onClick:y,disabled:a,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed",children:s}),h.jsx("span",{className:"flex-1 text-sm text-gray-600 dark:text-gray-400 truncate",children:f||o}),f&&!a&&h.jsx("button",{type:"button",onClick:_,className:"p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200","aria-label":"Clear file",children:h.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})});$d.displayName="FileInput";const gb=e=>e==="checkbox"?"checkbox":e==="radio"?"radio":"input",pb=(e,t)=>{const n=gb(e);return t?t.split(/\s+/).filter(Boolean).includes(n)?t:`${n} ${t}`:n},_t=v.forwardRef(({label:e,error:t,className:n="",onChange:r,onKeyPress:s,value:o,defaultValue:i,type:a="text",checked:l,defaultChecked:c,...d},u)=>{const f=a==="radio"||a==="checkbox",g=pb(a,n),m=v.useRef(!1),p=v.useRef(null);v.useImperativeHandle(u,()=>p.current);const[y,b]=v.useState(o??i??"");v.useEffect(()=>{if(!f&&!m.current&&o!==void 0){if(p.current&&document.activeElement===p.current)return;b(o)}},[o,f]);const _=v.useCallback(()=>{m.current=!0},[]),x=v.useCallback(E=>{if(m.current=!1,r){const D=E.target.value;b(D);const P=new Event("change",{bubbles:!0});Object.defineProperty(P,"target",{writable:!1,value:{value:D}}),Object.defineProperty(P,"currentTarget",{writable:!1,value:{value:D}}),r(P)}},[r]),M=v.useCallback(E=>{const D=E.target.value;b(D),!m.current&&r&&r(E)},[r]),C=v.useCallback(E=>{m.current||s&&s(E)},[s]),{loadingactions:k,formdata:N,...S}=d;if(f)return h.jsx("input",{ref:p,type:a,className:g,onChange:r,checked:l,defaultChecked:c,value:o,...S});const w=o!==void 0?{value:y}:{defaultValue:i};return h.jsx("input",{ref:p,type:a,className:g,onChange:M,onKeyPress:C,onCompositionStart:_,onCompositionEnd:x,...w,...S})});_t.displayName="Input";const ir=({label:e,error:t,className:n="",onChange:r,value:s,defaultValue:o,...i})=>{const a=v.useRef(!1),l=v.useRef(null),[c,d]=v.useState(s??o??"");v.useEffect(()=>{!a.current&&s!==void 0&&d(s)},[s]);const u=v.useCallback(()=>{a.current=!0},[]),f=v.useCallback(M=>{if(a.current=!1,r){const C=M.target.value;d(C);const k=new Event("change",{bubbles:!0});Object.defineProperty(k,"target",{writable:!1,value:{value:C}}),Object.defineProperty(k,"currentTarget",{writable:!1,value:{value:C}}),r(k)}},[r]),g=v.useCallback(M=>{const C=M.target.value;d(C),!a.current&&r&&r(M)},[r]),{loadingactions:m,formdata:p,...y}=i,b=s!==void 0?{value:c}:{defaultValue:o},x=(n??"").split(/\s+/).filter(Boolean).includes("textarea")?n??"":["textarea",n].filter(Boolean).join(" ");return h.jsx("textarea",{ref:l,className:x,onChange:g,onCompositionStart:u,onCompositionEnd:f,...b,...y})},dn=({children:e,className:t="",...n})=>h.jsx("label",{className:t,...n,children:e}),I=v.forwardRef(({children:e,className:t="",...n},r)=>h.jsx("div",{ref:r,className:t,...n,children:e})),Q=({children:e,className:t="",...n})=>h.jsx("span",{className:t,...n,children:e}),wt=({children:e,className:t="",...n})=>h.jsx("p",{className:t,...n,children:e}),Sn=({className:e="",alt:t="",...n})=>h.jsx("img",{className:e,alt:t,...n}),ja=({children:e,className:t="",...n})=>h.jsx("h1",{className:t,...n,children:e}),ar=({children:e,className:t="",...n})=>h.jsx("h2",{className:t,...n,children:e}),Xr=({children:e,className:t="",...n})=>h.jsx("h3",{className:t,...n,children:e}),Fd=({children:e,className:t="",...n})=>h.jsx("h4",{className:t,...n,children:e}),Wa=({children:e,className:t="",...n})=>h.jsx("ul",{className:t,...n,children:e}),mb=({children:e,className:t="",...n})=>h.jsx("ol",{className:t,...n,children:e}),Ga=({children:e,className:t="",...n})=>h.jsx("li",{className:t,...n,children:e}),Kr=({children:e,className:t="",...n})=>h.jsx("a",{className:t,...n,children:e}),Ya=({children:e,className:t="",...n})=>h.jsx("form",{className:t,...n,children:e}),Qn=({children:e,className:t="",...n})=>h.jsx("svg",{className:t,...n,children:e}),bb=window.G7Core?.createLogger?.("Comp:Select")??{log:(...e)=>console.log("[Comp:Select]",...e),warn:(...e)=>console.warn("[Comp:Select]",...e),error:(...e)=>console.error("[Comp:Select]",...e)};function yb(e){const t=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[e];return t||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[e]||e}const qr=({children:e,label:t,error:n,options:r,className:s="",value:o,onChange:i,disabled:a,searchable:l=!1,searchPlaceholder:c,editorAttrs:d,...u})=>{const[f,g]=v.useState(!1),[m,p]=v.useState(""),[y,b]=v.useState(null),_=v.useRef(null),x=v.useRef(null),M=v.useRef(null),C=v.useRef(null),k=v.useMemo(()=>r?Array.isArray(r)?r.length===0?[]:typeof r[0]=="string"?r.map(T=>({value:T,label:yb(T)})):r:(bb.warn("options prop is not an array:",r),null):null,[r]),N=v.useMemo(()=>k&&k.find(z=>String(z.value)===String(o))?.label||"",[k,o]),S=v.useMemo(()=>{if(!k)return null;if(!l||m.trim()==="")return k;const T=m.trim().toLowerCase();return k.filter(z=>z.label.toLowerCase().includes(T)||String(z.value).toLowerCase().includes(T))},[k,l,m]);v.useEffect(()=>{if(f&&l){p("");const T=setTimeout(()=>M.current?.focus(),0);return()=>clearTimeout(T)}f||p("")},[f,l]);const w=v.useCallback(()=>{if(!x.current)return;const T=x.current.getBoundingClientRect(),z=window.innerHeight-T.bottom,j=z<280&&T.top>z;b(j?{bottom:window.innerHeight-T.top+4,left:T.left,width:T.width}:{top:T.bottom+4,left:T.left,width:T.width})},[]);v.useEffect(()=>{if(!f){b(null);return}return w(),window.addEventListener("scroll",w,!0),window.addEventListener("resize",w),()=>{window.removeEventListener("scroll",w,!0),window.removeEventListener("resize",w)}},[f,w]),v.useEffect(()=>{const T=z=>{const H=z.target;_.current&&!_.current.contains(H)&&(!C.current||!C.current.contains(H))&&g(!1)};return f&&document.addEventListener("mousedown",T),()=>{document.removeEventListener("mousedown",T)}},[f]),v.useEffect(()=>{const T=z=>{z.key==="Escape"&&f&&(g(!1),x.current?.focus())};return document.addEventListener("keydown",T),()=>document.removeEventListener("keydown",T)},[f]);const E=()=>{a||g(!f)},D=T=>{i&&i({target:{value:T},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),x.current?.focus()};if(!k){const z=(s??"").split(/\s+/).filter(Boolean).includes("select")?s??"":["select",s].filter(Boolean).join(" ");return h.jsx("select",{className:z,value:o,onChange:i,disabled:a,...d,...u,children:e})}const P=(s??"").split(/\s+/).filter(T=>T&&!T.includes(":")).join(" "),L=P.includes("bg-"),$=/text-(gray|red|blue|green|yellow|white|black|indigo|purple|pink|orange|amber|emerald|teal|cyan|slate)-\d+|text-white|text-black/.test(P),A=L?`${s}${$?"":" text-gray-700 dark:text-gray-200"}`:`w-full px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl text-sm text-gray-700 dark:text-gray-200 font-medium focus:ring-2 focus:ring-blue-500 focus:outline-none ${s}`,O={...d,...u};return h.jsxs(I,{ref:_,className:"relative",...O,children:[h.jsxs(ie,{ref:x,type:"button",onClick:E,disabled:a,className:`${A} flex items-center justify-between gap-2 text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed`,"aria-haspopup":"listbox","aria-expanded":f,children:[h.jsx(Q,{className:"truncate",children:N||" "}),h.jsx(Qn,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),f&&y&&$n.createPortal(h.jsxs(I,{ref:C,className:"fixed z-[9999] bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",style:{top:y.top,bottom:y.bottom,left:y.left,width:y.width},role:"listbox",children:[l&&h.jsx(I,{className:"p-2 border-b border-gray-200 dark:border-gray-600",children:h.jsx(_t,{ref:M,type:"text",value:m,onChange:T=>p(T.target.value),placeholder:c??"Search...",className:"w-full px-3 py-2 bg-gray-100 dark:bg-gray-700 border-0 rounded-lg text-sm text-gray-700 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:outline-none",role:"searchbox","aria-label":c??"Search"})}),h.jsxs(I,{className:"py-2 max-h-60 overflow-auto",children:[S&&S.length===0&&h.jsx(I,{className:"px-4 py-3 text-sm text-gray-500 dark:text-gray-400 text-center",children:m?"No results":""}),(S??[]).map(T=>{const z=String(T.value)===String(o);return h.jsxs(ie,{type:"button",onClick:()=>!T.disabled&&D(T.value),disabled:T.disabled,className:`w-full px-4 py-3 text-left text-sm flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${z?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-700 dark:text-gray-200"} ${T.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,role:"option","aria-selected":z,children:[h.jsx(Q,{children:T.label}),z&&h.jsx(Qn,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]},T.value)})]})]}),document.body)]})},vb=({children:e,className:t="",...n})=>h.jsx("option",{className:t,...n,children:e}),xb=({children:e,className:t="",...n})=>h.jsx("optgroup",{className:t,...n,children:e}),_n=v.forwardRef(({label:e,className:t="",indeterminate:n=!1,...r},s)=>{const o=v.useRef(null),i=a=>{o.current=a,typeof s=="function"?s(a):s&&(s.current=a)};return v.useEffect(()=>{o.current&&(o.current.indeterminate=n)},[n]),h.jsx("input",{ref:i,type:"checkbox",className:t,...r})});_n.displayName="Checkbox";const Xa=({children:e,className:t="",...n})=>h.jsx("table",{className:t,...n,children:e}),Ka=({children:e,className:t="",...n})=>h.jsx("thead",{className:t,...n,children:e}),qa=({children:e,className:t="",...n})=>h.jsx("tbody",{className:t,...n,children:e}),Za=({children:e,className:t="",...n})=>h.jsx("tfoot",{className:t,...n,children:e}),lr=({children:e,className:t="",...n})=>h.jsx("tr",{className:t,...n,children:e}),Zr=({children:e,className:t="",...n})=>h.jsx("th",{className:t,...n,children:e}),Rt=({children:e,className:t="",...n})=>h.jsx("td",{className:t,...n,children:e}),Xo=({children:e,className:t="",...n})=>h.jsx("nav",{className:t,...n,children:e}),zd=({children:e,className:t="",...n})=>h.jsx("section",{className:t,...n,children:e}),se=({name:e,iconStyle:t="solid",size:n,color:r,spin:s=!1,pulse:o=!1,fixedWidth:i=!1,ariaLabel:a,className:l="",style:c,...d})=>{const u={solid:"fas",regular:"far",light:"fal",duotone:"fad",brands:"fab"},f={"fa-solid":"solid","fa-regular":"regular","fa-light":"light","fa-duotone":"duotone","fa-brands":"brands"},g=typeof e=="string"?e:String(e);let m=t,p=g;for(const[_,x]of Object.entries(f))if(g.startsWith(_+" ")){m=x,p=g.substring(_.length+1);break}p=p.replace(/^fa-/,"");const y=[u[m],`fa-${p}`,n&&`fa-${n}`,s&&"fa-spin",o&&"fa-pulse",i&&"fa-fw",r,l].filter(Boolean).join(" "),b=a||p.replace(/-/g," ");return h.jsx("i",{className:y,style:c,"aria-label":b,role:"img",...d})},wb={xs:"w-3 h-3",sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6",xl:"w-8 h-8"},Vd={xs:"xs",sm:"sm",md:void 0,lg:"lg",xl:"2x"},kb=({icon:e,size:t="md",className:n="",color:r,ariaLabel:s})=>{const o=e.indexOf(":"),i=e.startsWith("fa-")||e.startsWith("fas ")||e.startsWith("far ")||e.startsWith("fab ")||e.startsWith("fal ")||e.startsWith("fad ");let a,l;i?(a="fa",l=e):o>0?(a=e.substring(0,o),l=e.substring(o+1)):(a="fa",l=e);const c=wb[t],d=r||"";switch(a){case"fa":return h.jsx(se,{name:l,size:Vd[t],className:n,color:r,ariaLabel:s});case"svg":return h.jsx("img",{src:l,className:`${c} ${d} ${n}`.trim(),alt:s||"","aria-label":s,role:"img"});case"img":return h.jsx("img",{src:l,className:`${c} ${n}`.trim(),alt:s||"",role:"img"});default:return h.jsx(se,{name:e,size:Vd[t],className:n,color:r,ariaLabel:s})}},Cb=({children:e,className:t="",...n})=>h.jsx("code",{className:t,...n,children:e}),Sb=({children:e,className:t="",...n})=>h.jsx("pre",{className:t,...n,children:e}),_b=({children:e})=>h.jsx(h.Fragment,{children:e}),Eb=({className:e="",...t})=>h.jsx("hr",{className:e,...t}),yt=({className:e="",children:t,style:n,...r})=>h.jsx("i",{className:e,style:n,...r,children:t});var ce=(e=>(e.User="fa-user",e.Users="fa-users",e.UserCircle="fa-user-circle",e.UserPlus="fa-user-plus",e.UserMinus="fa-user-minus",e.UserShield="fa-user-shield",e.Search="fa-search",e.Edit="fa-edit",e.Pencil="fa-pencil-alt",e.Trash="fa-trash",e.Save="fa-save",e.Plus="fa-plus",e.Minus="fa-minus",e.Check="fa-check",e.Times="fa-times",e.Download="fa-download",e.Upload="fa-upload",e.Copy="fa-copy",e.ExternalLink="fa-external-link-alt",e.Home="fa-home",e.CaretRight="fa-caret-right",e.CaretDown="fa-caret-down",e.ChevronLeft="fa-chevron-left",e.ChevronRight="fa-chevron-right",e.ChevronUp="fa-chevron-up",e.ChevronDown="fa-chevron-down",e.AngleLeft="fa-angle-left",e.AngleRight="fa-angle-right",e.AngleUp="fa-angle-up",e.AngleDown="fa-angle-down",e.ArrowUp="fa-arrow-up",e.ArrowDown="fa-arrow-down",e.ArrowLeft="fa-arrow-left",e.ArrowRight="fa-arrow-right",e.Bars="fa-bars",e.EllipsisVertical="fa-ellipsis-v",e.EllipsisHorizontal="fa-ellipsis-h",e.CheckCircle="fa-check-circle",e.TimesCircle="fa-times-circle",e.ExclamationCircle="fa-exclamation-circle",e.ExclamationTriangle="fa-exclamation-triangle",e.InfoCircle="fa-info-circle",e.QuestionCircle="fa-question-circle",e.XCircle="fa-times-circle",e.ShoppingCart="fa-shopping-cart",e.ShoppingBag="fa-shopping-bag",e.CreditCard="fa-credit-card",e.Tag="fa-tag",e.Tags="fa-tags",e.File="fa-file",e.FileAlt="fa-file-alt",e.Folder="fa-folder",e.FolderOpen="fa-folder-open",e.Envelope="fa-envelope",e.Phone="fa-phone",e.Comment="fa-comment",e.Comments="fa-comments",e.Cog="fa-cog",e.Cogs="fa-cogs",e.Settings="fa-cog",e.Wrench="fa-wrench",e.Sliders="fa-sliders-h",e.Image="fa-image",e.Images="fa-images",e.Video="fa-video",e.Camera="fa-camera",e.Heart="fa-heart",e.Star="fa-star",e.ThumbsUp="fa-thumbs-up",e.ThumbsDown="fa-thumbs-down",e.Share="fa-share",e.Clock="fa-clock",e.Calendar="fa-calendar",e.CalendarAlt="fa-calendar-alt",e.Lock="fa-lock",e.Unlock="fa-unlock",e.Eye="fa-eye",e.EyeSlash="fa-eye-slash",e.Bell="fa-bell",e.Flag="fa-flag",e.Map="fa-map",e.MapMarker="fa-map-marker-alt",e.Globe="fa-globe",e.Link="fa-link",e.Unlink="fa-unlink",e.Sun="fa-sun",e.Moon="fa-moon",e.ChartBar="fa-chart-bar",e.ChartLine="fa-chart-line",e.ChartPie="fa-chart-pie",e.ChartArea="fa-chart-area",e.Spinner="fa-spinner",e.CircleNotch="fa-circle-notch",e.CheckBadge="fa-badge-check",e.Cube="fa-cube",e.Cubes="fa-cubes",e.Plug="fa-plug",e.PuzzlePiece="fa-puzzle-piece",e.Palette="fa-palette",e))(ce||{});const Mb=({title:e,content:t,imageUrl:n,imageAlt:r="",className:s="",onClick:o,style:i,id:a,editorAttrs:l})=>h.jsxs(I,{className:`card-clickable ${s}`,onClick:o,style:i,id:a,...l,children:[n&&h.jsx(I,{className:"w-full",children:h.jsx(Sn,{src:n,alt:r,className:"card-image"})}),h.jsxs(I,{className:"p-4",children:[e&&h.jsx(ar,{className:"card-title",children:e}),t&&h.jsx(wt,{className:"card-content",children:t})]})]}),Ps=({items:e,triggerLabel:t="작업",triggerIconName:n=ce.EllipsisVertical,position:r="right",className:s="",style:o,children:i,onItemClick:a,id:l,editorAttrs:c})=>{const[d,u]=v.useState(!1),[f,g]=v.useState({top:0,left:0}),m=v.useRef(null),p=v.useRef(null),y=v.useCallback(()=>{if(!m.current)return;const x=m.current.getBoundingClientRect(),M=224;let C=r==="right"?x.right-M:x.left;const k=x.bottom+8;C<8&&(C=8),C+M>window.innerWidth-8&&(C=window.innerWidth-M-8),g({top:k,left:C})},[r]);v.useEffect(()=>{d&&y()},[d,y]),v.useEffect(()=>{if(!d)return;const x=()=>{u(!1)};return window.addEventListener("scroll",x,!0),window.addEventListener("resize",x),()=>{window.removeEventListener("scroll",x,!0),window.removeEventListener("resize",x)}},[d]),v.useEffect(()=>{const x=M=>{const C=M.target,k=m.current?.contains(C),N=p.current?.contains(C);!k&&!N&&u(!1)};return d&&document.addEventListener("mousedown",x),()=>{document.removeEventListener("mousedown",x)}},[d]);const b=x=>{x.disabled||(x.onClick&&x.onClick(),a&&a(x.id,x),u(!1))},_=d?$n.createPortal(h.jsx("div",{ref:p,className:"fixed z-[9999] w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden",style:{top:f.top,left:f.left},children:e.filter(x=>x.if!==!1).map(x=>{if(x.divider)return h.jsx("div",{className:"border-t border-gray-200 dark:border-gray-600 my-1"},x.id);const M=x.variant==="danger"?"text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700",C=x.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer";return h.jsxs("div",{onClick:()=>b(x),className:`flex items-center gap-3 px-4 py-3 transition-colors ${M} ${C}`,children:[x.iconName&&h.jsx(se,{name:x.iconName,className:"w-4 h-4"}),h.jsx("span",{className:"text-sm font-medium",children:x.label})]},x.id)})}),document.body):null;return h.jsxs(I,{className:`relative inline-block ${s}`,style:o,id:l,...c,children:[i?h.jsx(I,{ref:m,onClick:()=>u(!d),className:"cursor-pointer",children:i}):h.jsxs(ie,{ref:m,onClick:()=>u(!d),className:"flex items-center gap-2 px-2 py-1 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[t&&h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:t}),n&&h.jsx(se,{name:n,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})]}),_]})},Qr=(e,t)=>window.G7Core?.t?.(e,t)??e,Ko=({currentPage:e,totalPages:t,onPageChange:n,maxVisiblePages:r=5,showFirstLast:s=!0,className:o="",style:i,prevText:a,nextText:l,id:c,editorAttrs:d})=>{const u=v.useMemo(()=>{const p=[];if(t<=r+2)for(let y=1;y<=t;y++)p.push(y);else{const y=Math.floor(r/2);let b=Math.max(2,e-y),_=Math.min(t-1,e+y);e<=y+1?_=r:e>=t-y&&(b=t-r+1),p.push(1),b>2&&p.push("...");for(let x=b;x<=_;x++)p.push(x);_1&&p.push(t)}return p},[e,t,r]),f=p=>{p<1||p>t||p===e||n(p)},g=()=>{e>1&&n(e-1)},m=()=>{ef(1),disabled:e===1,className:"pagination-btn","aria-label":Qr("common.first_page"),children:"«"}),h.jsx(ie,{onClick:g,disabled:e===1,className:"pagination-btn","aria-label":Qr("common.prev_page"),children:a||h.jsx(h.Fragment,{children:"‹"})}),h.jsx(I,{className:"pagination",children:u.map((p,y)=>{if(p==="...")return h.jsx(Q,{className:"pagination-ellipsis",children:"..."},`ellipsis-${y}`);const b=p,_=b===e;return h.jsx(ie,{onClick:()=>f(b),className:_?"pagination-btn-active":"pagination-btn","aria-label":Qr("common.page_n",{n:b}),"aria-current":_?"page":void 0,children:b},b)})}),h.jsx(ie,{onClick:m,disabled:e===t,className:"pagination-btn","aria-label":Qr("common.next_page"),children:l||h.jsx(h.Fragment,{children:"›"})}),s&&h.jsx(ie,{onClick:()=>f(t),disabled:e===t,className:"pagination-btn","aria-label":Qr("common.last_page"),children:"»"})]})},Hd=(e,t)=>window.G7Core?.t?.(e,t)??e,Nb=()=>window.G7Core?.locale?.supported?.()??["ko","en"],Tb=()=>window.G7Core?.locale?.current?.()??"ko",Ud=e=>{const t=Hd(`common.language_${e}`);return t===`common.language_${e}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[e]||e.toUpperCase():t},Ob=()=>Nb().map(t=>({code:t,name:Ud(t),nativeName:Ud(t)})),Qa=({value:e={},onChange:t,inputType:n="text",layout:r="inline",availableLocales:s,defaultLocale:o,placeholder:i="",required:a=!1,disabled:l=!1,maxLength:c,rows:d=4,className:u="",name:f="multilingual_input",showCodeOnMobile:g=!1,error:m,id:p,editorAttrs:y})=>{const b=!!m,_=v.useMemo(()=>s??Ob(),[s]),x=v.useMemo(()=>o??Tb(),[o]),M=v.useRef(e),[C,k]=v.useState(e),N=v.useRef(JSON.stringify(e));v.useEffect(()=>{const K=JSON.stringify(e);K!==N.current&&(N.current=K,M.current=e,k(e))},[e]);const S=v.useCallback((K,q)=>{const ne={target:{name:f,value:K}};q&&(ne._changedKeys=q),t?.(ne)},[t,f]),[w,E]=v.useState(()=>{if(r==="tabs"||r==="compact"){const ne=_.map(de=>de.code);return[x,...ne.filter(de=>de!==x)]}const K=Object.keys(e).filter(ne=>e[ne]!==void 0);if(K.length===0)return[x];const q=[];return K.includes(x)&&q.push(x),K.forEach(ne=>{ne!==x&&q.push(ne)}),q}),[D,P]=v.useState(x),L=v.useMemo(()=>_.filter(K=>!w.includes(K.code)),[_,w]),[$,A]=v.useState(!1);v.useEffect(()=>{if(r==="tabs"||r==="compact"){const de=_.map(ae=>ae.code),J=[x,...de.filter(ae=>ae!==x)],le=w.join(","),pe=J.join(",");le!==pe&&E(J);return}const K=Object.keys(e).filter(de=>e[de]!==void 0);if(K.length===0){E([x]);return}const q=[];K.includes(x)&&q.push(x),K.forEach(de=>{de!==x&&q.push(de)});const ne=w.join(","),ee=q.join(",");ne!==ee&&E(q)},[e,x,r,_]);const O=v.useCallback((K,q)=>{const ne={...M.current,[K]:q};M.current=ne,k(ne),S(ne,[K])},[S]),T=v.useCallback(K=>{E(ne=>[...ne,K]),P(K),A(!1);const q={...M.current,[K]:""};M.current=q,k(q),S(q)},[S]),z=v.useCallback(K=>{if(K===x)return;E(ne=>ne.filter(ee=>ee!==K)),D===K&&P(x);const q={...M.current};delete q[K],M.current=q,k(q),S(q)},[D,x,S]),H=v.useCallback(K=>{const q=_.find(ne=>ne.code===K);return q?.nativeName||q?.name||K.toUpperCase()},[_]),j=v.useCallback(K=>!!C[K]?.trim(),[C]),G=v.useCallback(()=>L.length===0?null:h.jsxs(I,{className:"relative",children:[h.jsxs(ie,{type:"button",className:"inline-flex items-center gap-1 px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 hover:bg-blue-100 dark:hover:bg-blue-900/50 rounded-full border border-dashed border-blue-300 dark:border-blue-600 transition-colors",onClick:()=>A(!$),disabled:l,children:[h.jsx(se,{name:"plus",className:"w-3 h-3"}),h.jsx(Q,{children:Hd("common.add_language")})]}),$&&h.jsx(I,{className:"absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-10 min-w-[120px]",children:L.map(K=>h.jsxs(ie,{type:"button",className:"w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 first:rounded-t-lg last:rounded-b-lg transition-colors",onClick:()=>T(K.code),children:[h.jsx(se,{name:"globe",className:"w-3.5 h-3.5 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{children:K.nativeName||K.name})]},K.code))})]}),[L,$,l,T]),U=v.useCallback((K,q=!1)=>{const ne=b?"input input-error":"input",ee=b?"textarea input-error":"textarea",de=K===x,J=`${i}${i?" (":""}${H(K)}${i?")":""}`;return h.jsxs(I,{className:"space-y-2",children:[h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsxs(Q,{className:` +var SirsoftAdminBasic=(function(R,h,v,Tn){"use strict";var F5=Object.defineProperty;var z5=(R,h,v)=>h in R?F5(R,h,{enumerable:!0,configurable:!0,writable:!0,value:v}):R[h]=v;var $e=(R,h,v)=>z5(R,typeof h!="symbol"?h+"":h,v);var Aa,fr;function eb(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const n in t)if(n!=="default"){const r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:()=>t[n]})}}return e.default=t,Object.freeze(e)}const F=eb(v),tb=()=>window.G7Core,oe=v.forwardRef(({children:t,variant:e="primary",size:n="md",className:r="",...s},o)=>{const i="button",a=tb()?.style?.mergeClasses?.(i,r)??`${i} ${r}`;return h.jsx("button",{ref:o,className:a,...s,children:t})});oe.displayName="Button";const Md=v.forwardRef(({accept:t,maxSize:e,onChange:n,onError:r,buttonText:s="Browse",placeholder:o="No file selected",className:i="",disabled:a,...l},c)=>{const d=v.useRef(null),u=c||d,[f,g]=v.useState(""),m={},p={};Object.entries(l).forEach(([x,E])=>{x.startsWith("data-editor-")||x==="id"||x==="onMouseMove"||x==="onMouseLeave"?m[x]=E:p[x]=E});const y=v.useCallback(()=>{!a&&u.current&&u.current.click()},[a,u]),b=v.useCallback(x=>{const E=x.target.files?.[0]||null;if(!E){g(""),n?.({target:{value:null,name:""}});return}if(e&&E.size>e*1024*1024){const C=`File size exceeds ${e}MB limit`;r?.(C),u.current&&(u.current.value=""),g("");return}g(E.name),n?.({target:{value:E,name:E.name}})},[e,n,r,u]),_=v.useCallback(x=>{x.stopPropagation(),u.current&&(u.current.value=""),g(""),n?.({target:{value:null,name:""}})},[n,u]);return h.jsxs("div",{className:`flex items-center gap-2 ${i}`,...m,children:[h.jsx("input",{ref:u,type:"file",accept:t,onChange:b,disabled:a,className:"hidden",...p}),h.jsx("button",{type:"button",onClick:y,disabled:a,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed",children:s}),h.jsx("span",{className:"flex-1 text-sm text-gray-600 dark:text-gray-400 truncate",children:f||o}),f&&!a&&h.jsx("button",{type:"button",onClick:_,className:"p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200","aria-label":"Clear file",children:h.jsx("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]})});Md.displayName="FileInput";const nb=t=>t==="checkbox"?"checkbox":t==="radio"?"radio":"input",rb=(t,e)=>{const n=nb(t);return e?e.split(/\s+/).filter(Boolean).includes(n)?e:`${n} ${e}`:n},kt=v.forwardRef(({label:t,error:e,className:n="",onChange:r,onKeyPress:s,value:o,defaultValue:i,type:a="text",checked:l,defaultChecked:c,...d},u)=>{const f=a==="radio"||a==="checkbox",g=rb(a,n),m=v.useRef(!1),p=v.useRef(null);v.useImperativeHandle(u,()=>p.current);const[y,b]=v.useState(o??i??"");v.useEffect(()=>{if(!f&&!m.current&&o!==void 0){if(p.current&&document.activeElement===p.current)return;b(o)}},[o,f]);const _=v.useCallback(()=>{m.current=!0},[]),x=v.useCallback(M=>{if(m.current=!1,r){const T=M.target.value;b(T);const P=new Event("change",{bubbles:!0});Object.defineProperty(P,"target",{writable:!1,value:{value:T}}),Object.defineProperty(P,"currentTarget",{writable:!1,value:{value:T}}),r(P)}},[r]),E=v.useCallback(M=>{const T=M.target.value;b(T),!m.current&&r&&r(M)},[r]),C=v.useCallback(M=>{m.current||s&&s(M)},[s]),{loadingactions:k,formdata:N,...S}=d;if(f)return h.jsx("input",{ref:p,type:a,className:g,onChange:r,checked:l,defaultChecked:c,value:o,...S});const w=o!==void 0?{value:y}:{defaultValue:i};return h.jsx("input",{ref:p,type:a,className:g,onChange:E,onKeyPress:C,onCompositionStart:_,onCompositionEnd:x,...w,...S})});kt.displayName="Input";const Qn=({label:t,error:e,className:n="",onChange:r,value:s,defaultValue:o,...i})=>{const a=v.useRef(!1),l=v.useRef(null),[c,d]=v.useState(s??o??"");v.useEffect(()=>{!a.current&&s!==void 0&&d(s)},[s]);const u=v.useCallback(()=>{a.current=!0},[]),f=v.useCallback(E=>{if(a.current=!1,r){const C=E.target.value;d(C);const k=new Event("change",{bubbles:!0});Object.defineProperty(k,"target",{writable:!1,value:{value:C}}),Object.defineProperty(k,"currentTarget",{writable:!1,value:{value:C}}),r(k)}},[r]),g=v.useCallback(E=>{const C=E.target.value;d(C),!a.current&&r&&r(E)},[r]),{loadingactions:m,formdata:p,...y}=i,b=s!==void 0?{value:c}:{defaultValue:o},x=(n??"").split(/\s+/).filter(Boolean).includes("textarea")?n??"":["textarea",n].filter(Boolean).join(" ");return h.jsx("textarea",{ref:l,className:x,onChange:g,onCompositionStart:u,onCompositionEnd:f,...b,...y})},sn=({children:t,className:e="",...n})=>h.jsx("label",{className:e,...n,children:t}),I=v.forwardRef(({children:t,className:e="",...n},r)=>h.jsx("div",{ref:r,className:e,...n,children:t})),Q=({children:t,className:e="",...n})=>h.jsx("span",{className:e,...n,children:t}),yt=({children:t,className:e="",...n})=>h.jsx("p",{className:e,...n,children:t}),yn=({className:t="",alt:e="",...n})=>h.jsx("img",{className:t,alt:e,...n}),Da=({children:t,className:e="",...n})=>h.jsx("h1",{className:e,...n,children:t}),Jn=({children:t,className:e="",...n})=>h.jsx("h2",{className:e,...n,children:t}),Ir=({children:t,className:e="",...n})=>h.jsx("h3",{className:e,...n,children:t}),Nd=({children:t,className:e="",...n})=>h.jsx("h4",{className:e,...n,children:t}),Ia=({children:t,className:e="",...n})=>h.jsx("ul",{className:e,...n,children:t}),sb=({children:t,className:e="",...n})=>h.jsx("ol",{className:e,...n,children:t}),La=({children:t,className:e="",...n})=>h.jsx("li",{className:e,...n,children:t}),Lr=({children:t,className:e="",...n})=>h.jsx("a",{className:e,...n,children:t}),Pa=({children:t,className:e="",...n})=>h.jsx("form",{className:e,...n,children:t}),Un=({children:t,className:e="",...n})=>h.jsx("svg",{className:e,...n,children:t}),ob=window.G7Core?.createLogger?.("Comp:Select")??{log:(...t)=>console.log("[Comp:Select]",...t),warn:(...t)=>console.warn("[Comp:Select]",...t),error:(...t)=>console.error("[Comp:Select]",...t)};function ib(t){const e=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[t];return e||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[t]||t}const Pr=({children:t,label:e,error:n,options:r,className:s="",value:o,onChange:i,disabled:a,searchable:l=!1,searchPlaceholder:c,editorAttrs:d,...u})=>{const[f,g]=v.useState(!1),[m,p]=v.useState(""),[y,b]=v.useState(null),_=v.useRef(null),x=v.useRef(null),E=v.useRef(null),C=v.useRef(null),k=v.useMemo(()=>r?Array.isArray(r)?r.length===0?[]:typeof r[0]=="string"?r.map(O=>({value:O,label:ib(O)})):r:(ob.warn("options prop is not an array:",r),null):null,[r]),N=v.useMemo(()=>k&&k.find(z=>String(z.value)===String(o))?.label||"",[k,o]),S=v.useMemo(()=>{if(!k)return null;if(!l||m.trim()==="")return k;const O=m.trim().toLowerCase();return k.filter(z=>z.label.toLowerCase().includes(O)||String(z.value).toLowerCase().includes(O))},[k,l,m]);v.useEffect(()=>{if(f&&l){p("");const O=setTimeout(()=>E.current?.focus(),0);return()=>clearTimeout(O)}f||p("")},[f,l]);const w=v.useCallback(()=>{if(!x.current)return;const O=x.current.getBoundingClientRect(),z=window.innerHeight-O.bottom,j=z<280&&O.top>z;b(j?{bottom:window.innerHeight-O.top+4,left:O.left,width:O.width}:{top:O.bottom+4,left:O.left,width:O.width})},[]);v.useEffect(()=>{if(!f){b(null);return}return w(),window.addEventListener("scroll",w,!0),window.addEventListener("resize",w),()=>{window.removeEventListener("scroll",w,!0),window.removeEventListener("resize",w)}},[f,w]),v.useEffect(()=>{const O=z=>{const H=z.target;_.current&&!_.current.contains(H)&&(!C.current||!C.current.contains(H))&&g(!1)};return f&&document.addEventListener("mousedown",O),()=>{document.removeEventListener("mousedown",O)}},[f]),v.useEffect(()=>{const O=z=>{z.key==="Escape"&&f&&(g(!1),x.current?.focus())};return document.addEventListener("keydown",O),()=>document.removeEventListener("keydown",O)},[f]);const M=()=>{a||g(!f)},T=O=>{i&&i({target:{value:O},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),x.current?.focus()};if(!k){const z=(s??"").split(/\s+/).filter(Boolean).includes("select")?s??"":["select",s].filter(Boolean).join(" ");return h.jsx("select",{className:z,value:o,onChange:i,disabled:a,...d,...u,children:t})}const P=(s??"").split(/\s+/).filter(O=>O&&!O.includes(":")).join(" "),L=P.includes("bg-"),$=/text-(gray|red|blue|green|yellow|white|black|indigo|purple|pink|orange|amber|emerald|teal|cyan|slate)-\d+|text-white|text-black/.test(P),A=L?`${s}${$?"":" text-gray-700 dark:text-gray-200"}`:`w-full px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl text-sm text-gray-700 dark:text-gray-200 font-medium focus:ring-2 focus:ring-blue-500 focus:outline-none ${s}`,D={...d,...u};return h.jsxs(I,{ref:_,className:"relative",...D,children:[h.jsxs(oe,{ref:x,type:"button",onClick:M,disabled:a,className:`${A} flex items-center justify-between gap-2 text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed`,"aria-haspopup":"listbox","aria-expanded":f,children:[h.jsx(Q,{className:"truncate",children:N||" "}),h.jsx(Un,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),f&&y&&Tn.createPortal(h.jsxs(I,{ref:C,className:"fixed z-[9999] bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",style:{top:y.top,bottom:y.bottom,left:y.left,width:y.width},role:"listbox",children:[l&&h.jsx(I,{className:"p-2 border-b border-gray-200 dark:border-gray-600",children:h.jsx(kt,{ref:E,type:"text",value:m,onChange:O=>p(O.target.value),placeholder:c??"Search...",className:"w-full px-3 py-2 bg-gray-100 dark:bg-gray-700 border-0 rounded-lg text-sm text-gray-700 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:outline-none",role:"searchbox","aria-label":c??"Search"})}),h.jsxs(I,{className:"py-2 max-h-60 overflow-auto",children:[S&&S.length===0&&h.jsx(I,{className:"px-4 py-3 text-sm text-gray-500 dark:text-gray-400 text-center",children:m?"No results":""}),(S??[]).map(O=>{const z=String(O.value)===String(o);return h.jsxs(oe,{type:"button",onClick:()=>!O.disabled&&T(O.value),disabled:O.disabled,className:`w-full px-4 py-3 text-left text-sm flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${z?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-700 dark:text-gray-200"} ${O.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,role:"option","aria-selected":z,children:[h.jsx(Q,{children:O.label}),z&&h.jsx(Un,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]},O.value)})]})]}),document.body)]})},ab=({children:t,className:e="",...n})=>h.jsx("option",{className:e,...n,children:t}),lb=({children:t,className:e="",...n})=>h.jsx("optgroup",{className:e,...n,children:t}),vn=v.forwardRef(({label:t,className:e="",indeterminate:n=!1,...r},s)=>{const o=v.useRef(null),i=a=>{o.current=a,typeof s=="function"?s(a):s&&(s.current=a)};return v.useEffect(()=>{o.current&&(o.current.indeterminate=n)},[n]),h.jsx("input",{ref:i,type:"checkbox",className:e,...r})});vn.displayName="Checkbox";const $a=({children:t,className:e="",...n})=>h.jsx("table",{className:e,...n,children:t}),Fa=({children:t,className:e="",...n})=>h.jsx("thead",{className:e,...n,children:t}),za=({children:t,className:e="",...n})=>h.jsx("tbody",{className:e,...n,children:t}),Va=({children:t,className:e="",...n})=>h.jsx("tfoot",{className:e,...n,children:t}),Rn=({children:t,className:e="",...n})=>h.jsx("tr",{className:e,...n,children:t}),$r=({children:t,className:e="",...n})=>h.jsx("th",{className:e,...n,children:t}),Zt=({children:t,className:e="",...n})=>h.jsx("td",{className:e,...n,children:t}),Po=({children:t,className:e="",...n})=>h.jsx("nav",{className:e,...n,children:t}),Od=({children:t,className:e="",...n})=>h.jsx("section",{className:e,...n,children:t}),re=({name:t,iconStyle:e="solid",size:n,color:r,spin:s=!1,pulse:o=!1,fixedWidth:i=!1,ariaLabel:a,className:l="",style:c,...d})=>{const u={solid:"fas",regular:"far",light:"fal",duotone:"fad",brands:"fab"},f={"fa-solid":"solid","fa-regular":"regular","fa-light":"light","fa-duotone":"duotone","fa-brands":"brands"},g=typeof t=="string"?t:String(t);let m=e,p=g;for(const[_,x]of Object.entries(f))if(g.startsWith(_+" ")){m=x,p=g.substring(_.length+1);break}p=p.replace(/^fa-/,"");const y=[u[m],`fa-${p}`,n&&`fa-${n}`,s&&"fa-spin",o&&"fa-pulse",i&&"fa-fw",r,l].filter(Boolean).join(" "),b=a||p.replace(/-/g," ");return h.jsx("i",{className:y,style:c,"aria-label":b,role:"img",...d})},cb={xs:"w-3 h-3",sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6",xl:"w-8 h-8"},Td={xs:"xs",sm:"sm",md:void 0,lg:"lg",xl:"2x"},db=({icon:t,size:e="md",className:n="",color:r,ariaLabel:s})=>{const o=t.indexOf(":"),i=t.startsWith("fa-")||t.startsWith("fas ")||t.startsWith("far ")||t.startsWith("fab ")||t.startsWith("fal ")||t.startsWith("fad ");let a,l;i?(a="fa",l=t):o>0?(a=t.substring(0,o),l=t.substring(o+1)):(a="fa",l=t);const c=cb[e],d=r||"";switch(a){case"fa":return h.jsx(re,{name:l,size:Td[e],className:n,color:r,ariaLabel:s});case"svg":return h.jsx("img",{src:l,className:`${c} ${d} ${n}`.trim(),alt:s||"","aria-label":s,role:"img"});case"img":return h.jsx("img",{src:l,className:`${c} ${n}`.trim(),alt:s||"",role:"img"});default:return h.jsx(re,{name:t,size:Td[e],className:n,color:r,ariaLabel:s})}},ub=({children:t,className:e="",...n})=>h.jsx("code",{className:e,...n,children:t}),fb=({children:t,className:e="",...n})=>h.jsx("pre",{className:e,...n,children:t}),hb=({children:t})=>h.jsx(h.Fragment,{children:t}),gb=({className:t="",...e})=>h.jsx("hr",{className:t,...e}),mt=({className:t="",children:e,style:n,...r})=>h.jsx("i",{className:t,style:n,...r,children:e});var ce=(t=>(t.User="fa-user",t.Users="fa-users",t.UserCircle="fa-user-circle",t.UserPlus="fa-user-plus",t.UserMinus="fa-user-minus",t.UserShield="fa-user-shield",t.Search="fa-search",t.Edit="fa-edit",t.Pencil="fa-pencil-alt",t.Trash="fa-trash",t.Save="fa-save",t.Plus="fa-plus",t.Minus="fa-minus",t.Check="fa-check",t.Times="fa-times",t.Download="fa-download",t.Upload="fa-upload",t.Copy="fa-copy",t.ExternalLink="fa-external-link-alt",t.Home="fa-home",t.CaretRight="fa-caret-right",t.CaretDown="fa-caret-down",t.ChevronLeft="fa-chevron-left",t.ChevronRight="fa-chevron-right",t.ChevronUp="fa-chevron-up",t.ChevronDown="fa-chevron-down",t.AngleLeft="fa-angle-left",t.AngleRight="fa-angle-right",t.AngleUp="fa-angle-up",t.AngleDown="fa-angle-down",t.ArrowUp="fa-arrow-up",t.ArrowDown="fa-arrow-down",t.ArrowLeft="fa-arrow-left",t.ArrowRight="fa-arrow-right",t.Bars="fa-bars",t.EllipsisVertical="fa-ellipsis-v",t.EllipsisHorizontal="fa-ellipsis-h",t.CheckCircle="fa-check-circle",t.TimesCircle="fa-times-circle",t.ExclamationCircle="fa-exclamation-circle",t.ExclamationTriangle="fa-exclamation-triangle",t.InfoCircle="fa-info-circle",t.QuestionCircle="fa-question-circle",t.XCircle="fa-times-circle",t.ShoppingCart="fa-shopping-cart",t.ShoppingBag="fa-shopping-bag",t.CreditCard="fa-credit-card",t.Tag="fa-tag",t.Tags="fa-tags",t.File="fa-file",t.FileAlt="fa-file-alt",t.Folder="fa-folder",t.FolderOpen="fa-folder-open",t.Envelope="fa-envelope",t.Phone="fa-phone",t.Comment="fa-comment",t.Comments="fa-comments",t.Cog="fa-cog",t.Cogs="fa-cogs",t.Settings="fa-cog",t.Wrench="fa-wrench",t.Sliders="fa-sliders-h",t.Image="fa-image",t.Images="fa-images",t.Video="fa-video",t.Camera="fa-camera",t.Heart="fa-heart",t.Star="fa-star",t.ThumbsUp="fa-thumbs-up",t.ThumbsDown="fa-thumbs-down",t.Share="fa-share",t.Clock="fa-clock",t.Calendar="fa-calendar",t.CalendarAlt="fa-calendar-alt",t.Lock="fa-lock",t.Unlock="fa-unlock",t.Eye="fa-eye",t.EyeSlash="fa-eye-slash",t.Bell="fa-bell",t.Flag="fa-flag",t.Map="fa-map",t.MapMarker="fa-map-marker-alt",t.Globe="fa-globe",t.Link="fa-link",t.Unlink="fa-unlink",t.Sun="fa-sun",t.Moon="fa-moon",t.ChartBar="fa-chart-bar",t.ChartLine="fa-chart-line",t.ChartPie="fa-chart-pie",t.ChartArea="fa-chart-area",t.Spinner="fa-spinner",t.CircleNotch="fa-circle-notch",t.CheckBadge="fa-badge-check",t.Cube="fa-cube",t.Cubes="fa-cubes",t.Plug="fa-plug",t.PuzzlePiece="fa-puzzle-piece",t.Palette="fa-palette",t))(ce||{});const pb=({title:t,content:e,imageUrl:n,imageAlt:r="",className:s="",onClick:o,style:i,id:a,editorAttrs:l})=>h.jsxs(I,{className:`card-clickable ${s}`,onClick:o,style:i,id:a,...l,children:[n&&h.jsx(I,{className:"w-full",children:h.jsx(yn,{src:n,alt:r,className:"card-image"})}),h.jsxs(I,{className:"p-4",children:[t&&h.jsx(Jn,{className:"card-title",children:t}),e&&h.jsx(yt,{className:"card-content",children:e})]})]}),ys=({items:t,triggerLabel:e="작업",triggerIconName:n=ce.EllipsisVertical,position:r="right",className:s="",style:o,children:i,onItemClick:a,id:l,editorAttrs:c})=>{const[d,u]=v.useState(!1),[f,g]=v.useState({top:0,left:0}),m=v.useRef(null),p=v.useRef(null),y=v.useCallback(()=>{if(!m.current)return;const x=m.current.getBoundingClientRect(),E=224;let C=r==="right"?x.right-E:x.left;const k=x.bottom+8;C<8&&(C=8),C+E>window.innerWidth-8&&(C=window.innerWidth-E-8),g({top:k,left:C})},[r]);v.useEffect(()=>{d&&y()},[d,y]),v.useEffect(()=>{if(!d)return;const x=()=>{u(!1)};return window.addEventListener("scroll",x,!0),window.addEventListener("resize",x),()=>{window.removeEventListener("scroll",x,!0),window.removeEventListener("resize",x)}},[d]),v.useEffect(()=>{const x=E=>{const C=E.target,k=m.current?.contains(C),N=p.current?.contains(C);!k&&!N&&u(!1)};return d&&document.addEventListener("mousedown",x),()=>{document.removeEventListener("mousedown",x)}},[d]);const b=x=>{x.disabled||(x.onClick&&x.onClick(),a&&a(x.id,x),u(!1))},_=d?Tn.createPortal(h.jsx("div",{ref:p,className:"fixed z-[9999] w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden",style:{top:f.top,left:f.left},children:t.filter(x=>x.if!==!1).map(x=>{if(x.divider)return h.jsx("div",{className:"border-t border-gray-200 dark:border-gray-600 my-1"},x.id);const E=x.variant==="danger"?"text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700",C=x.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer";return h.jsxs("div",{onClick:()=>b(x),className:`flex items-center gap-3 px-4 py-3 transition-colors ${E} ${C}`,children:[x.iconName&&h.jsx(re,{name:x.iconName,className:"w-4 h-4"}),h.jsx("span",{className:"text-sm font-medium",children:x.label})]},x.id)})}),document.body):null;return h.jsxs(I,{className:`relative inline-block ${s}`,style:o,id:l,...c,children:[i?h.jsx(I,{ref:m,onClick:()=>u(!d),className:"cursor-pointer",children:i}):h.jsxs(oe,{ref:m,onClick:()=>u(!d),className:"flex items-center gap-2 px-2 py-1 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[e&&h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:e}),n&&h.jsx(re,{name:n,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})]}),_]})},Fr=(t,e)=>window.G7Core?.t?.(t,e)??t,$o=({currentPage:t,totalPages:e,onPageChange:n,maxVisiblePages:r=5,showFirstLast:s=!0,className:o="",style:i,prevText:a,nextText:l,id:c,editorAttrs:d})=>{const u=v.useMemo(()=>{const p=[];if(e<=r+2)for(let y=1;y<=e;y++)p.push(y);else{const y=Math.floor(r/2);let b=Math.max(2,t-y),_=Math.min(e-1,t+y);t<=y+1?_=r:t>=e-y&&(b=e-r+1),p.push(1),b>2&&p.push("...");for(let x=b;x<=_;x++)p.push(x);_1&&p.push(e)}return p},[t,e,r]),f=p=>{p<1||p>e||p===t||n(p)},g=()=>{t>1&&n(t-1)},m=()=>{tf(1),disabled:t===1,className:"pagination-btn","aria-label":Fr("common.first_page"),children:"«"}),h.jsx(oe,{onClick:g,disabled:t===1,className:"pagination-btn","aria-label":Fr("common.prev_page"),children:a||h.jsx(h.Fragment,{children:"‹"})}),h.jsx(I,{className:"pagination",children:u.map((p,y)=>{if(p==="...")return h.jsx(Q,{className:"pagination-ellipsis",children:"..."},`ellipsis-${y}`);const b=p,_=b===t;return h.jsx(oe,{onClick:()=>f(b),className:_?"pagination-btn-active":"pagination-btn","aria-label":Fr("common.page_n",{n:b}),"aria-current":_?"page":void 0,children:b},b)})}),h.jsx(oe,{onClick:m,disabled:t===e,className:"pagination-btn","aria-label":Fr("common.next_page"),children:l||h.jsx(h.Fragment,{children:"›"})}),s&&h.jsx(oe,{onClick:()=>f(e),disabled:t===e,className:"pagination-btn","aria-label":Fr("common.last_page"),children:"»"})]})},Ad=(t,e)=>window.G7Core?.t?.(t,e)??t,mb=()=>window.G7Core?.locale?.supported?.()??["ko","en"],bb=()=>window.G7Core?.locale?.current?.()??"ko",Dd=t=>{const e=Ad(`common.language_${t}`);return e===`common.language_${t}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[t]||t.toUpperCase():e},yb=()=>mb().map(e=>({code:e,name:Dd(e),nativeName:Dd(e)})),Ha=({value:t={},onChange:e,inputType:n="text",layout:r="inline",availableLocales:s,defaultLocale:o,placeholder:i="",required:a=!1,disabled:l=!1,maxLength:c,rows:d=4,className:u="",name:f="multilingual_input",showCodeOnMobile:g=!1,error:m,id:p,editorAttrs:y})=>{const b=!!m,_=v.useMemo(()=>s??yb(),[s]),x=v.useMemo(()=>o??bb(),[o]),E=v.useRef(t),[C,k]=v.useState(t),N=v.useRef(JSON.stringify(t));v.useEffect(()=>{const X=JSON.stringify(t);X!==N.current&&(N.current=X,E.current=t,k(t))},[t]);const S=v.useCallback((X,Z)=>{const ee={target:{name:f,value:X}};Z&&(ee._changedKeys=Z),e?.(ee)},[e,f]),[w,M]=v.useState(()=>{if(r==="tabs"||r==="compact"){const ee=_.map(he=>he.code);return[x,...ee.filter(he=>he!==x)]}const X=Object.keys(t).filter(ee=>t[ee]!==void 0);if(X.length===0)return[x];const Z=[];return X.includes(x)&&Z.push(x),X.forEach(ee=>{ee!==x&&Z.push(ee)}),Z}),[T,P]=v.useState(x),L=v.useMemo(()=>_.filter(X=>!w.includes(X.code)),[_,w]),[$,A]=v.useState(!1);v.useEffect(()=>{if(r==="tabs"||r==="compact"){const he=_.map(ae=>ae.code),J=[x,...he.filter(ae=>ae!==x)],le=w.join(","),ue=J.join(",");le!==ue&&M(J);return}const X=Object.keys(t).filter(he=>t[he]!==void 0);if(X.length===0){M([x]);return}const Z=[];X.includes(x)&&Z.push(x),X.forEach(he=>{he!==x&&Z.push(he)});const ee=w.join(","),ne=Z.join(",");ee!==ne&&M(Z)},[t,x,r,_]);const D=v.useCallback((X,Z)=>{const ee={...E.current,[X]:Z};E.current=ee,k(ee),S(ee,[X])},[S]),O=v.useCallback(X=>{M(ee=>[...ee,X]),P(X),A(!1);const Z={...E.current,[X]:""};E.current=Z,k(Z),S(Z)},[S]),z=v.useCallback(X=>{if(X===x)return;M(ee=>ee.filter(ne=>ne!==X)),T===X&&P(x);const Z={...E.current};delete Z[X],E.current=Z,k(Z),S(Z)},[T,x,S]),H=v.useCallback(X=>{const Z=_.find(ee=>ee.code===X);return Z?.nativeName||Z?.name||X.toUpperCase()},[_]),j=v.useCallback(X=>!!C[X]?.trim(),[C]),W=v.useCallback(()=>L.length===0?null:h.jsxs(I,{className:"relative",children:[h.jsxs(oe,{type:"button",className:"inline-flex items-center gap-1 px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/30 hover:bg-blue-100 dark:hover:bg-blue-900/50 rounded-full border border-dashed border-blue-300 dark:border-blue-600 transition-colors",onClick:()=>A(!$),disabled:l,children:[h.jsx(re,{name:"plus",className:"w-3 h-3"}),h.jsx(Q,{children:Ad("common.add_language")})]}),$&&h.jsx(I,{className:"absolute top-full left-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-10 min-w-[120px]",children:L.map(X=>h.jsxs(oe,{type:"button",className:"w-full flex items-center gap-2 px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 first:rounded-t-lg last:rounded-b-lg transition-colors",onClick:()=>O(X.code),children:[h.jsx(re,{name:"globe",className:"w-3.5 h-3.5 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{children:X.nativeName||X.name})]},X.code))})]}),[L,$,l,O]),U=v.useCallback((X,Z=!1)=>{const ee=b?"input input-error":"input",ne=b?"textarea input-error":"textarea",he=X===x,J=`${i}${i?" (":""}${H(X)}${i?")":""}`;return h.jsxs(I,{className:"space-y-2",children:[h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsxs(Q,{className:` inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium - ${de?"bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"} - `,children:[h.jsx(se,{name:"globe",className:"w-3 h-3"}),H(K),de&&h.jsx(Q,{className:"text-red-500 dark:text-red-400",children:"*"}),!de&&h.jsx(ie,{type:"button",className:"ml-0.5 p-0.5 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",onClick:()=>z(K),disabled:l,children:h.jsx(se,{name:"xmark",className:"w-3 h-3"})})]}),de&&q&&G()]}),n==="textarea"?h.jsx(ir,{name:`${f}[${K}]`,value:C[K]||"",onChange:le=>O(K,le.target.value),placeholder:J,required:a&&de,disabled:l,maxLength:c,rows:d,className:`${ee} resize-vertical`}):h.jsx(_t,{type:"text",name:`${f}[${K}]`,value:C[K]||"",onChange:le=>O(K,le.target.value),placeholder:J,required:a&&de,disabled:l,maxLength:c,className:ne})]},K)},[f,C,O,i,a,l,c,d,n,H,x,z,G,b]),W=v.useCallback(K=>{const q=b?"input input-error":"input",ne=b?"textarea input-error":"textarea",ee=`${i}${i?" (":""}${H(K)}${i?")":""}`;return h.jsx(I,{children:n==="textarea"?h.jsx(ir,{name:`${f}[${K}]`,value:C[K]||"",onChange:de=>O(K,de.target.value),placeholder:ee,required:a&&K===x,disabled:l,maxLength:c,rows:d,className:`${ne} resize-vertical`}):h.jsx(_t,{type:"text",name:`${f}[${K}]`,value:C[K]||"",onChange:de=>O(K,de.target.value),placeholder:ee,required:a&&K===x,disabled:l,maxLength:c,className:q})},K)},[f,C,O,i,a,l,c,d,n,x,H,b]),Y=()=>h.jsx(I,{className:u,children:h.jsx(I,{className:"space-y-4",children:w.map((K,q)=>U(K,q===0))})}),V=()=>h.jsxs(I,{className:u,children:[h.jsxs(I,{className:"flex flex-wrap items-center gap-2 mb-3",children:[w.map(K=>h.jsxs(ie,{type:"button",className:` + ${he?"bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"} + `,children:[h.jsx(re,{name:"globe",className:"w-3 h-3"}),H(X),he&&h.jsx(Q,{className:"text-red-500 dark:text-red-400",children:"*"}),!he&&h.jsx(oe,{type:"button",className:"ml-0.5 p-0.5 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",onClick:()=>z(X),disabled:l,children:h.jsx(re,{name:"xmark",className:"w-3 h-3"})})]}),he&&Z&&W()]}),n==="textarea"?h.jsx(Qn,{name:`${f}[${X}]`,value:C[X]||"",onChange:le=>D(X,le.target.value),placeholder:J,required:a&&he,disabled:l,maxLength:c,rows:d,className:`${ne} resize-vertical`}):h.jsx(kt,{type:"text",name:`${f}[${X}]`,value:C[X]||"",onChange:le=>D(X,le.target.value),placeholder:J,required:a&&he,disabled:l,maxLength:c,className:ee})]},X)},[f,C,D,i,a,l,c,d,n,H,x,z,W,b]),B=v.useCallback(X=>{const Z=b?"input input-error":"input",ee=b?"textarea input-error":"textarea",ne=`${i}${i?" (":""}${H(X)}${i?")":""}`;return h.jsx(I,{children:n==="textarea"?h.jsx(Qn,{name:`${f}[${X}]`,value:C[X]||"",onChange:he=>D(X,he.target.value),placeholder:ne,required:a&&X===x,disabled:l,maxLength:c,rows:d,className:`${ee} resize-vertical`}):h.jsx(kt,{type:"text",name:`${f}[${X}]`,value:C[X]||"",onChange:he=>D(X,he.target.value),placeholder:ne,required:a&&X===x,disabled:l,maxLength:c,className:Z})},X)},[f,C,D,i,a,l,c,d,n,x,H,b]),G=()=>h.jsx(I,{className:u,children:h.jsx(I,{className:"space-y-4",children:w.map((X,Z)=>U(X,Z===0))})}),V=()=>h.jsxs(I,{className:u,children:[h.jsxs(I,{className:"flex flex-wrap items-center gap-2 mb-3",children:[w.map(X=>h.jsxs(oe,{type:"button",className:` inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold transition-all - ${D===K?K===x?"bg-blue-500 text-white dark:bg-blue-600 dark:text-white shadow-md scale-105":"bg-gray-500 text-white dark:bg-gray-600 dark:text-white shadow-md scale-105":K===x?"bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} - `,onClick:()=>P(K),children:[h.jsx(se,{name:"globe",className:"w-3 h-3"}),g?h.jsxs(h.Fragment,{children:[h.jsx(Q,{className:"hidden md:inline",children:H(K)}),h.jsx(Q,{className:"inline md:hidden",children:K.toUpperCase()})]}):H(K),K===x&&h.jsx(Q,{className:"text-red-500 dark:text-red-400",children:"*"}),j(K)&&D!==K&&h.jsx(se,{name:"check",className:"w-3 h-3 text-green-500 dark:text-green-400"}),K!==x&&h.jsx(ie,{type:"button",className:"ml-0.5 p-0.5 rounded-full hover:bg-gray-300 dark:hover:bg-gray-500 transition-colors",onClick:q=>{q.stopPropagation(),z(K)},disabled:l,children:h.jsx(se,{name:"xmark",className:"w-3 h-3"})})]},K)),G()]}),w.map(K=>h.jsx(I,{className:D===K?"block":"hidden",children:W(K)},K))]}),X=()=>{const K=b?"input input-error":"input",q=b?"textarea input-error":"textarea";return h.jsx(I,{className:u,children:h.jsxs(I,{className:"flex items-center gap-1",children:[h.jsx(I,{className:"flex items-center shrink-0",children:w.map(ne=>h.jsxs(ie,{type:"button",className:` + ${T===X?X===x?"bg-blue-500 text-white dark:bg-blue-600 dark:text-white shadow-md scale-105":"bg-gray-500 text-white dark:bg-gray-600 dark:text-white shadow-md scale-105":X===x?"bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} + `,onClick:()=>P(X),children:[h.jsx(re,{name:"globe",className:"w-3 h-3"}),g?h.jsxs(h.Fragment,{children:[h.jsx(Q,{className:"hidden md:inline",children:H(X)}),h.jsx(Q,{className:"inline md:hidden",children:X.toUpperCase()})]}):H(X),X===x&&h.jsx(Q,{className:"text-red-500 dark:text-red-400",children:"*"}),j(X)&&T!==X&&h.jsx(re,{name:"check",className:"w-3 h-3 text-green-500 dark:text-green-400"}),X!==x&&h.jsx(oe,{type:"button",className:"ml-0.5 p-0.5 rounded-full hover:bg-gray-300 dark:hover:bg-gray-500 transition-colors",onClick:Z=>{Z.stopPropagation(),z(X)},disabled:l,children:h.jsx(re,{name:"xmark",className:"w-3 h-3"})})]},X)),W()]}),w.map(X=>h.jsx(I,{className:T===X?"block":"hidden",children:B(X)},X))]}),K=()=>{const X=b?"input input-error":"input",Z=b?"textarea input-error":"textarea";return h.jsx(I,{className:u,children:h.jsxs(I,{className:"flex items-center gap-1",children:[h.jsx(I,{className:"flex items-center shrink-0",children:w.map(ee=>h.jsxs(oe,{type:"button",className:` px-2 py-1.5 text-xs font-medium border-y border-l first:rounded-l last:rounded-r last:border-r transition-colors - ${D===ne?"bg-blue-500 text-white border-blue-500 dark:bg-blue-600 dark:border-blue-600":"bg-white text-gray-600 border-gray-200 hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700"} - `,onClick:()=>P(ne),title:H(ne),children:[ne.toUpperCase(),ne===x&&h.jsx(Q,{className:"text-red-400 ml-0.5",children:"*"})]},ne))}),h.jsx(I,{className:"flex-1 min-w-0",children:n==="textarea"?h.jsx(ir,{name:`${f}[${D}]`,value:C[D]||"",onChange:ne=>O(D,ne.target.value),placeholder:i,required:a&&D===x,disabled:l,maxLength:c,rows:d,className:q}):h.jsx(_t,{type:"text",name:`${f}[${D}]`,value:C[D]||"",onChange:ne=>O(D,ne.target.value),placeholder:i,required:a&&D===x,disabled:l,maxLength:c,className:K})})]})})};return h.jsxs(I,{className:l?"opacity-50 cursor-not-allowed":void 0,id:p,...y,children:[r==="compact"?X():r==="tabs"?V():Y(),b&&m&&h.jsx(Q,{className:"text-xs text-red-500 dark:text-red-400 mt-1 block",children:m})]})};Qa.displayName="MultilingualInput";const Ab=()=>window.G7Core,Ja=({checked:e,value:t,onChange:n,disabled:r=!1,label:s,description:o,size:i="md",className:a="",name:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(()=>e??t??!1);v.useEffect(()=>{f(e??t??!1)},[e,t]);const g=_=>{const x=_.target.checked;f(x),n&&!r&&n(_)},p={sm:{track:"w-9 h-5",thumb:"after:h-4 after:w-4"},md:{track:"w-11 h-6",thumb:"after:h-5 after:w-5"},lg:{track:"w-14 h-7",thumb:"after:h-6 after:w-6"}}[i],y=()=>{if(!r){const _=!u;if(f(_),n){const x=Ab();x?.createChangeEvent&&n(x.createChangeEvent({checked:_,name:l}))}}},b=_=>{r||_.key!==" "&&_.key!=="Enter"&&_.key!=="Spacebar"||(_.preventDefault(),y())};return h.jsxs("div",{className:`toggle-container ${a}`,id:c,...d,children:[h.jsxs("div",{className:`toggle-switch-wrapper ${r?"cursor-not-allowed opacity-50":"cursor-pointer"}`,role:"switch","aria-checked":u,"aria-disabled":r||void 0,"aria-label":s??l,tabIndex:r?-1:0,onKeyDown:b,onClick:_=>{_.preventDefault(),_.stopPropagation(),y()},children:[h.jsx("input",{type:"checkbox",id:l,name:l,checked:u,onChange:g,disabled:r,className:"sr-only peer",tabIndex:-1}),h.jsx("div",{className:`toggle-switch-track ${p.track} ${p.thumb}`})]}),s&&h.jsx("span",{className:`toggle-label ${r?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:y,children:s}),o&&h.jsx("p",{className:"toggle-description",children:o})]})},qo=({status:e,label:t,showIcon:n=!0,iconName:r,className:s="",style:o,id:i,editorAttrs:a})=>{const l={success:"bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 border-green-200 dark:border-green-800",warning:"bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800",error:"bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800",info:"bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 border-blue-200 dark:border-blue-800",pending:"bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-600",default:"bg-gray-50 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-600"},c={success:ce.CheckCircle,warning:ce.ExclamationTriangle,error:ce.XCircle,info:ce.InfoCircle,pending:ce.Clock,default:ce.QuestionCircle},d={success:"성공",warning:"경고",error:"오류",info:"정보",pending:"대기 중",default:"기본"},u=r||c[e],f=t||d[e],g=l[e];return h.jsxs(Q,{className:`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium border ${g} ${s}`,style:o,id:i,...a,children:[n&&h.jsx(se,{name:u,className:"w-4 h-4"}),h.jsx(Q,{children:f})]})},Bd={blue:"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300",green:"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300",red:"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300",gray:"bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300",yellow:"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300",purple:"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300",orange:"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300",teal:"bg-teal-100 text-teal-800 dark:bg-teal-900/30 dark:text-teal-300",cyan:"bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300"},Db={success:"green",warning:"yellow",danger:"red",error:"red",info:"blue",primary:"blue",secondary:"gray",text:"gray",default:"gray"},jd={sm:"px-1.5 py-0.5 text-[10px]",md:"px-2 py-0.5 text-xs",lg:"px-2.5 py-1 text-sm"},Wd=({color:e,variant:t,text:n,size:r="md",className:s="",style:o,children:i,id:a,editorAttrs:l})=>{const c=e||(t?Db[t]:void 0)||"gray",d=Bd[c]||Bd.gray,u=jd[r]||jd.md;return h.jsxs(Q,{className:`inline-flex items-center rounded-full font-medium ${u} ${d} ${s}`,style:o,id:a,...l,children:[n,i]})};function Gd(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n2?r-2:0),o=2;o1?n-1:0),s=1;s"u"?null:Et(BigInt.prototype.toString),Rd=typeof Symbol>"u"?null:Et(Symbol.prototype.toString),kt=Et(Object.prototype.hasOwnProperty),Fs=Et(Object.prototype.toString),Vt=Et(RegExp.prototype.test),ts=Yb(TypeError);function Et(e){return function(t){t instanceof RegExp&&(t.lastIndex=0);for(var n=arguments.length,r=new Array(n>1?n-1:0),s=1;s2&&arguments[2]!==void 0?arguments[2]:$s;if(Xd&&Xd(e,null),!Zt(t))return e;let r=t.length;for(;r--;){let s=t[r];if(typeof s=="string"){const o=n(s);o!==s&&(zb(t)||(t[r]=o),s=o)}e[s]=!0}return e}function Xb(e){for(let t=0;t/g),ey=gn(/\${[\w\W]*/g),ty=gn(/^data-[\-\w.\u00B7-\uFFFF]+$/),ny=gn(/^aria-[\-\w]+$/),su=gn(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ry=gn(/^(?:\w+script|data):/i),sy=gn(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),oy=gn(/^html$/i),iy=gn(/^[a-z][.\w]*(-[.\w]+)+$/i),zn={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,progressingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},ay=function(){return typeof window>"u"?null:window},ly=function(t,n){if(typeof t!="object"||typeof t.createPolicy!="function")return null;let r=null;const s="data-tt-policy-suffix";n&&n.hasAttribute(s)&&(r=n.getAttribute(s));const o="dompurify"+(r?"#"+r:"");try{return t.createPolicy(o,{createHTML(i){return i},createScriptURL(i){return i}})}catch{return console.warn("TrustedTypes policy "+o+" could not be created."),null}},ou=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}};function iu(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:ay();const t=xe=>iu(xe);if(t.version="3.4.8",t.removed=[],!e||!e.document||e.document.nodeType!==zn.document||!e.Element)return t.isSupported=!1,t;let n=e.document;const r=n,s=r.currentScript;e.DocumentFragment;const o=e.HTMLTemplateElement,i=e.Node,a=e.Element,l=e.NodeFilter,c=e.NamedNodeMap;c===void 0&&(e.NamedNodeMap||e.MozNamedAttrMap),e.HTMLFormElement;const d=e.DOMParser,u=e.trustedTypes,f=a.prototype,g=Fn(f,"cloneNode"),m=Fn(f,"remove"),p=Fn(f,"nextSibling"),y=Fn(f,"childNodes"),b=Fn(f,"parentNode"),_=Fn(f,"shadowRoot"),x=Fn(f,"attributes"),M=i&&i.prototype?Fn(i.prototype,"nodeType"):null,C=i&&i.prototype?Fn(i.prototype,"nodeName"):null;if(typeof o=="function"){const xe=n.createElement("template");xe.content&&xe.content.ownerDocument&&(n=xe.content.ownerDocument)}let k,N="",S=0;const w=function(B){if(S>0)throw ts('The configured TRUSTED_TYPES_POLICY.createHTML must not call DOMPurify.sanitize, as that causes infinite recursion. Do not pass a policy whose createHTML wraps DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted Types" section of the README.');S++;try{return k.createHTML(B)}finally{S--}},E=n,D=E.implementation,P=E.createNodeIterator,L=E.createDocumentFragment,$=E.getElementsByTagName,A=r.importNode;let O=ou();t.isSupported=typeof Yd=="function"&&typeof b=="function"&&D&&D.createHTMLDocument!==void 0;const T=Jb,z=Rb,H=ey,j=ty,G=ny,U=ry,W=sy,Y=iy;let V=su,X=null;const K=He({},[...eu,...nl,...rl,...sl,...tu]);let q=null;const ne=He({},[...nu,...ol,...ru,...Zo]);let ee=Object.seal(Jr(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),de=null,J=null;const le=Object.seal(Jr(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let pe=!0,ae=!0,ye=!1,he=!0,ge=!1,ke=!0,Fe=!1,oe=!1,Me=!1,Ae=!1,De=!1,Ke=!1,pt=!0,mt=!1;const je="user-content-";let tt=!0,We=!1,re={},ue=null;const me=He({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let we=null;const ve=He({},["audio","video","img","source","image","track"]);let Ue=null;const _e=He({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Le="http://www.w3.org/1998/Math/MathML",Ce="http://www.w3.org/2000/svg",Ge="http://www.w3.org/1999/xhtml";let Qe=Ge,ze=!1,Ne=null;const Ve=He({},[Le,Ce,Ge],tl);let qe=He({},["mi","mo","mn","ms","mtext"]),nt=He({},["annotation-xml"]);const lt=He({},["title","style","font","a","script"]);let dt=null;const kn=["application/xhtml+xml","text/html"],vt="text/html";let Be=null,xt=null;const Je=n.createElement("form"),In=function(B){return B instanceof RegExp||B instanceof Function},an=function(){let B=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(xt&&xt===B)return;(!B||typeof B!="object")&&(B={}),B=Bt(B),dt=kn.indexOf(B.PARSER_MEDIA_TYPE)===-1?vt:B.PARSER_MEDIA_TYPE,Be=dt==="application/xhtml+xml"?tl:$s,X=kt(B,"ALLOWED_TAGS")&&Zt(B.ALLOWED_TAGS)?He({},B.ALLOWED_TAGS,Be):K,q=kt(B,"ALLOWED_ATTR")&&Zt(B.ALLOWED_ATTR)?He({},B.ALLOWED_ATTR,Be):ne,Ne=kt(B,"ALLOWED_NAMESPACES")&&Zt(B.ALLOWED_NAMESPACES)?He({},B.ALLOWED_NAMESPACES,tl):Ve,Ue=kt(B,"ADD_URI_SAFE_ATTR")&&Zt(B.ADD_URI_SAFE_ATTR)?He(Bt(_e),B.ADD_URI_SAFE_ATTR,Be):_e,we=kt(B,"ADD_DATA_URI_TAGS")&&Zt(B.ADD_DATA_URI_TAGS)?He(Bt(ve),B.ADD_DATA_URI_TAGS,Be):ve,ue=kt(B,"FORBID_CONTENTS")&&Zt(B.FORBID_CONTENTS)?He({},B.FORBID_CONTENTS,Be):me,de=kt(B,"FORBID_TAGS")&&Zt(B.FORBID_TAGS)?He({},B.FORBID_TAGS,Be):Bt({}),J=kt(B,"FORBID_ATTR")&&Zt(B.FORBID_ATTR)?He({},B.FORBID_ATTR,Be):Bt({}),re=kt(B,"USE_PROFILES")?B.USE_PROFILES&&typeof B.USE_PROFILES=="object"?Bt(B.USE_PROFILES):B.USE_PROFILES:!1,pe=B.ALLOW_ARIA_ATTR!==!1,ae=B.ALLOW_DATA_ATTR!==!1,ye=B.ALLOW_UNKNOWN_PROTOCOLS||!1,he=B.ALLOW_SELF_CLOSE_IN_ATTR!==!1,ge=B.SAFE_FOR_TEMPLATES||!1,ke=B.SAFE_FOR_XML!==!1,Fe=B.WHOLE_DOCUMENT||!1,Ae=B.RETURN_DOM||!1,De=B.RETURN_DOM_FRAGMENT||!1,Ke=B.RETURN_TRUSTED_TYPE||!1,Me=B.FORCE_BODY||!1,pt=B.SANITIZE_DOM!==!1,mt=B.SANITIZE_NAMED_PROPS||!1,tt=B.KEEP_CONTENT!==!1,We=B.IN_PLACE||!1,V=qb(B.ALLOWED_URI_REGEXP)?B.ALLOWED_URI_REGEXP:su,Qe=typeof B.NAMESPACE=="string"?B.NAMESPACE:Ge,qe=kt(B,"MATHML_TEXT_INTEGRATION_POINTS")&&B.MATHML_TEXT_INTEGRATION_POINTS&&typeof B.MATHML_TEXT_INTEGRATION_POINTS=="object"?Bt(B.MATHML_TEXT_INTEGRATION_POINTS):He({},["mi","mo","mn","ms","mtext"]),nt=kt(B,"HTML_INTEGRATION_POINTS")&&B.HTML_INTEGRATION_POINTS&&typeof B.HTML_INTEGRATION_POINTS=="object"?Bt(B.HTML_INTEGRATION_POINTS):He({},["annotation-xml"]);const te=kt(B,"CUSTOM_ELEMENT_HANDLING")&&B.CUSTOM_ELEMENT_HANDLING&&typeof B.CUSTOM_ELEMENT_HANDLING=="object"?Bt(B.CUSTOM_ELEMENT_HANDLING):Jr(null);if(ee=Jr(null),kt(te,"tagNameCheck")&&In(te.tagNameCheck)&&(ee.tagNameCheck=te.tagNameCheck),kt(te,"attributeNameCheck")&&In(te.attributeNameCheck)&&(ee.attributeNameCheck=te.attributeNameCheck),kt(te,"allowCustomizedBuiltInElements")&&typeof te.allowCustomizedBuiltInElements=="boolean"&&(ee.allowCustomizedBuiltInElements=te.allowCustomizedBuiltInElements),ge&&(ae=!1),De&&(Ae=!0),re&&(X=He({},tu),q=Jr(null),re.html===!0&&(He(X,eu),He(q,nu)),re.svg===!0&&(He(X,nl),He(q,ol),He(q,Zo)),re.svgFilters===!0&&(He(X,rl),He(q,ol),He(q,Zo)),re.mathMl===!0&&(He(X,sl),He(q,ru),He(q,Zo))),le.tagCheck=null,le.attributeCheck=null,kt(B,"ADD_TAGS")&&(typeof B.ADD_TAGS=="function"?le.tagCheck=B.ADD_TAGS:Zt(B.ADD_TAGS)&&(X===K&&(X=Bt(X)),He(X,B.ADD_TAGS,Be))),kt(B,"ADD_ATTR")&&(typeof B.ADD_ATTR=="function"?le.attributeCheck=B.ADD_ATTR:Zt(B.ADD_ATTR)&&(q===ne&&(q=Bt(q)),He(q,B.ADD_ATTR,Be))),kt(B,"ADD_URI_SAFE_ATTR")&&Zt(B.ADD_URI_SAFE_ATTR)&&He(Ue,B.ADD_URI_SAFE_ATTR,Be),kt(B,"FORBID_CONTENTS")&&Zt(B.FORBID_CONTENTS)&&(ue===me&&(ue=Bt(ue)),He(ue,B.FORBID_CONTENTS,Be)),kt(B,"ADD_FORBID_CONTENTS")&&Zt(B.ADD_FORBID_CONTENTS)&&(ue===me&&(ue=Bt(ue)),He(ue,B.ADD_FORBID_CONTENTS,Be)),tt&&(X["#text"]=!0),Fe&&He(X,["html","head","body"]),X.table&&(He(X,["tbody"]),delete de.tbody),B.TRUSTED_TYPES_POLICY){if(typeof B.TRUSTED_TYPES_POLICY.createHTML!="function")throw ts('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof B.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw ts('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');const fe=k;k=B.TRUSTED_TYPES_POLICY;try{N=w("")}catch(it){throw k=fe,it}}else k===void 0&&B.TRUSTED_TYPES_POLICY!==null&&(k=ly(u,s)),k&&typeof N=="string"&&(N=w(""));(O.uponSanitizeElement.length>0||O.uponSanitizeAttribute.length>0)&&X===K&&(X=Bt(X)),O.uponSanitizeAttribute.length>0&&q===ne&&(q=Bt(q)),qt&&qt(B),xt=B},ln=He({},[...nl,...rl,...Zb]),qn=He({},[...sl,...Qb]),Wr=function(B){let te=b(B);(!te||!te.tagName)&&(te={namespaceURI:Qe,tagName:"template"});const fe=$s(B.tagName),it=$s(te.tagName);return Ne[B.namespaceURI]?B.namespaceURI===Ce?te.namespaceURI===Ge?fe==="svg":te.namespaceURI===Le?fe==="svg"&&(it==="annotation-xml"||qe[it]):!!ln[fe]:B.namespaceURI===Le?te.namespaceURI===Ge?fe==="math":te.namespaceURI===Ce?fe==="math"&&nt[it]:!!qn[fe]:B.namespaceURI===Ge?te.namespaceURI===Ce&&!nt[it]||te.namespaceURI===Le&&!qe[it]?!1:!qn[fe]&&(lt[fe]||!ln[fe]):!!(dt==="application/xhtml+xml"&&Ne[B.namespaceURI]):!1},cn=function(B){Rr(t.removed,{element:B});try{b(B).removeChild(B)}catch{m(B)}},Zn=function(B,te){try{Rr(t.removed,{attribute:te.getAttributeNode(B),from:te})}catch{Rr(t.removed,{attribute:null,from:te})}if(te.removeAttribute(B),B==="is")if(Ae||De)try{cn(te)}catch{}else try{te.setAttribute(B,"")}catch{}},Go=function(B){let te=null,fe=null;if(Me)B=""+B;else{const ht=Zd(B,/^[\r\n\t ]+/);fe=ht&&ht[0]}dt==="application/xhtml+xml"&&Qe===Ge&&(B=''+B+"");const it=k?w(B):B;if(Qe===Ge)try{te=new d().parseFromString(it,dt)}catch{}if(!te||!te.documentElement){te=D.createDocument(Qe,"template",null);try{te.documentElement.innerHTML=ze?N:it}catch{}}const Ye=te.body||te.documentElement;return B&&fe&&Ye.insertBefore(n.createTextNode(fe),Ye.childNodes[0]||null),Qe===Ge?$.call(te,Fe?"html":"body")[0]:Fe?te.documentElement:Ye},Yo=function(B){return P.call(B.ownerDocument||B,B,l.SHOW_ELEMENT|l.SHOW_COMMENT|l.SHOW_TEXT|l.SHOW_PROCESSING_INSTRUCTION|l.SHOW_CDATA_SECTION,null)},kr=function(B){var te,fe;B.normalize();const it=P.call(B.ownerDocument||B,B,l.SHOW_TEXT|l.SHOW_COMMENT|l.SHOW_CDATA_SECTION|l.SHOW_PROCESSING_INSTRUCTION,null);let Ye=it.nextNode();for(;Ye;){let Dt=Ye.data;Cr([T,z,H],Cn=>{Dt=es(Dt,Cn," ")}),Ye.data=Dt,Ye=it.nextNode()}const ht=(te=(fe=B.querySelectorAll)===null||fe===void 0?void 0:fe.call(B,"template"))!==null&&te!==void 0?te:[];Cr(Array.from(ht),Dt=>{or(Dt.content)&&kr(Dt.content)})},Gr=function(B){const te=C?C(B):null;return typeof te!="string"||Be(te)!=="form"?!1:typeof B.nodeName!="string"||typeof B.textContent!="string"||typeof B.removeChild!="function"||B.attributes!==x(B)||typeof B.removeAttribute!="function"||typeof B.setAttribute!="function"||typeof B.namespaceURI!="string"||typeof B.insertBefore!="function"||typeof B.hasChildNodes!="function"||B.nodeType!==M(B)||B.childNodes!==y(B)},or=function(B){if(!M||typeof B!="object"||B===null)return!1;try{return M(B)===zn.documentFragment}catch{return!1}},Yr=function(B){if(!M||typeof B!="object"||B===null)return!1;try{return typeof M(B)=="number"}catch{return!1}};function Ln(xe,B,te){Cr(xe,fe=>{fe.call(t,B,te,xt)})}const Ua=function(B){let te=null;if(Ln(O.beforeSanitizeElements,B,null),Gr(B))return cn(B),!0;const fe=Be(C?C(B):B.nodeName);if(Ln(O.uponSanitizeElement,B,{tagName:fe,allowedTags:X}),ke&&B.hasChildNodes()&&!Yr(B.firstElementChild)&&Vt(/<[/\w!]/g,B.innerHTML)&&Vt(/<[/\w!]/g,B.textContent)||ke&&B.namespaceURI===Ge&&fe==="style"&&Yr(B.firstElementChild)||B.nodeType===zn.progressingInstruction||ke&&B.nodeType===zn.comment&&Vt(/<[/\w]/g,B.data))return cn(B),!0;if(de[fe]||!(le.tagCheck instanceof Function&&le.tagCheck(fe))&&!X[fe]){if(!de[fe]&&be(fe)&&(ee.tagNameCheck instanceof RegExp&&Vt(ee.tagNameCheck,fe)||ee.tagNameCheck instanceof Function&&ee.tagNameCheck(fe)))return!1;if(tt&&!ue[fe]){const Ye=b(B),ht=y(B);if(ht&&Ye){const Dt=ht.length;for(let Cn=Dt-1;Cn>=0;--Cn){const Pn=g(ht[Cn],!0);Ye.insertBefore(Pn,p(B))}}}return cn(B),!0}return(M?M(B):B.nodeType)===zn.element&&!Wr(B)||(fe==="noscript"||fe==="noembed"||fe==="noframes")&&Vt(/<\/no(script|embed|frames)/i,B.innerHTML)?(cn(B),!0):(ge&&B.nodeType===zn.text&&(te=B.textContent,Cr([T,z,H],Ye=>{te=es(te,Ye," ")}),B.textContent!==te&&(Rr(t.removed,{element:B.cloneNode()}),B.textContent=te)),Ln(O.afterSanitizeElements,B,null),!1)},Ba=function(B,te,fe){if(J[te]||pt&&(te==="id"||te==="name")&&(fe in n||fe in Je))return!1;const it=q[te]||le.attributeCheck instanceof Function&&le.attributeCheck(te,B);if(!(ae&&!J[te]&&Vt(j,te))){if(!(pe&&Vt(G,te))){if(!it||J[te]){if(!(be(B)&&(ee.tagNameCheck instanceof RegExp&&Vt(ee.tagNameCheck,B)||ee.tagNameCheck instanceof Function&&ee.tagNameCheck(B))&&(ee.attributeNameCheck instanceof RegExp&&Vt(ee.attributeNameCheck,te)||ee.attributeNameCheck instanceof Function&&ee.attributeNameCheck(te,B))||te==="is"&&ee.allowCustomizedBuiltInElements&&(ee.tagNameCheck instanceof RegExp&&Vt(ee.tagNameCheck,fe)||ee.tagNameCheck instanceof Function&&ee.tagNameCheck(fe))))return!1}else if(!Ue[te]){if(!Vt(V,es(fe,W,""))){if(!((te==="src"||te==="xlink:href"||te==="href")&&B!=="script"&&Qd(fe,"data:")===0&&we[B])){if(!(ye&&!Vt(U,es(fe,W,"")))){if(fe)return!1}}}}}}return!0},Z=He({},["annotation-xml","color-profile","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","missing-glyph"]),be=function(B){return!Z[$s(B)]&&Vt(Y,B)},Pe=function(B){Ln(O.beforeSanitizeAttributes,B,null);const te=B.attributes;if(!te||Gr(B))return;const fe={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:q,forceKeepAttr:void 0};let it=te.length;for(;it--;){const Ye=te[it],ht=Ye.name,Dt=Ye.namespaceURI,Cn=Ye.value,Pn=Be(ht),Pd=Cn;let zt=ht==="value"?Pd:jb(Pd);if(fe.attrName=Pn,fe.attrValue=zt,fe.keepAttr=!0,fe.forceKeepAttr=void 0,Ln(O.uponSanitizeAttribute,B,fe),zt=fe.attrValue,mt&&(Pn==="id"||Pn==="name")&&Qd(zt,je)!==0&&(Zn(ht,B),zt=je+zt),ke&&Vt(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,zt)){Zn(ht,B);continue}if(Pn==="attributename"&&Zd(zt,"href")){Zn(ht,B);continue}if(fe.forceKeepAttr)continue;if(!fe.keepAttr){Zn(ht,B);continue}if(!he&&Vt(/\/>/i,zt)){Zn(ht,B);continue}ge&&Cr([T,z,H],ub=>{zt=es(zt,ub," ")});const db=Be(B.nodeName);if(!Ba(db,Pn,zt)){Zn(ht,B);continue}if(k&&typeof u=="object"&&typeof u.getAttributeType=="function"&&!Dt)switch(u.getAttributeType(db,Pn)){case"TrustedHTML":{zt=w(zt);break}case"TrustedScriptURL":{zt=k.createScriptURL(zt);break}}if(zt!==Pd)try{Dt?B.setAttributeNS(Dt,ht,zt):B.setAttribute(ht,zt),Gr(B)?cn(B):qd(t.removed)}catch{Zn(ht,B)}}Ln(O.afterSanitizeAttributes,B,null)},rt=function(B){let te=null;const fe=Yo(B);for(Ln(O.beforeSanitizeShadowDOM,B,null);te=fe.nextNode();)if(Ln(O.uponSanitizeShadowNode,te,null),Ua(te),Pe(te),or(te.content)&&rt(te.content),(M?M(te):te.nodeType)===zn.element){const Ye=_?_(te):te.shadowRoot;or(Ye)&&(Ie(Ye),rt(Ye))}Ln(O.afterSanitizeShadowDOM,B,null)},Ie=function(B){const te=M?M(B):B.nodeType;if(te===zn.element){const Ye=_?_(B):B.shadowRoot;or(Ye)&&(Ie(Ye),rt(Ye))}const fe=y?y(B):B.childNodes;if(!fe)return;const it=[];Cr(fe,Ye=>{Rr(it,Ye)});for(const Ye of it)Ie(Ye);if(te===zn.element){const Ye=C?C(B):null;if(typeof Ye=="string"&&Be(Ye)==="template"){const ht=B.content;or(ht)&&Ie(ht)}}};return t.sanitize=function(xe){let B=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},te=null,fe=null,it=null,Ye=null;if(ze=!xe,ze&&(xe=""),typeof xe!="string"&&!Yr(xe)&&(xe=Kb(xe),typeof xe!="string"))throw ts("dirty is not a string, aborting");if(!t.isSupported)return xe;if(oe||an(B),t.removed=[],typeof xe=="string"&&(We=!1),We){const Cn=C?C(xe):xe.nodeName;if(typeof Cn=="string"){const Pn=Be(Cn);if(!X[Pn]||de[Pn])throw ts("root node is forbidden and cannot be sanitized in-place")}if(Gr(xe))throw ts("root node is clobbered and cannot be sanitized in-place");Ie(xe)}else if(Yr(xe))te=Go(""),fe=te.ownerDocument.importNode(xe,!0),fe.nodeType===zn.element&&fe.nodeName==="BODY"||fe.nodeName==="HTML"?te=fe:te.appendChild(fe),Ie(fe);else{if(!Ae&&!ge&&!Fe&&xe.indexOf("<")===-1)return k&&Ke?w(xe):xe;if(te=Go(xe),!te)return Ae?null:Ke?N:""}te&&Me&&cn(te.firstChild);const ht=Yo(We?xe:te);for(;it=ht.nextNode();)Ua(it),Pe(it),or(it.content)&&rt(it.content);if(We)return ge&&kr(xe),xe;if(Ae){if(ge&&kr(te),De)for(Ye=L.call(te.ownerDocument);te.firstChild;)Ye.appendChild(te.firstChild);else Ye=te;return(q.shadowroot||q.shadowrootmode)&&(Ye=A.call(r,Ye,!0)),Ye}let Dt=Fe?te.outerHTML:te.innerHTML;return Fe&&X["!doctype"]&&te.ownerDocument&&te.ownerDocument.doctype&&te.ownerDocument.doctype.name&&Vt(oy,te.ownerDocument.doctype.name)&&(Dt=" -`+Dt),ge&&Cr([T,z,H],Cn=>{Dt=es(Dt,Cn," ")}),k&&Ke?w(Dt):Dt},t.setConfig=function(){let xe=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};an(xe),oe=!0},t.clearConfig=function(){xt=null,oe=!1},t.isValidAttribute=function(xe,B,te){xt||an({});const fe=Be(xe),it=Be(B);return Ba(fe,it,te)},t.addHook=function(xe,B){typeof B=="function"&&Rr(O[xe],B)},t.removeHook=function(xe,B){if(B!==void 0){const te=Ub(O[xe],B);return te===-1?void 0:Bb(O[xe],te,1)[0]}return qd(O[xe])},t.removeHooks=function(xe){O[xe]=[]},t.removeAllHooks=function(){O=ou()},t}var cy=iu();const il=({content:e,text:t,isHtml:n=!0,className:r="",purifyConfig:s,id:o,editorAttrs:i})=>{const a=t??e??"";if(!a||a.trim()==="")return null;if(!n){const u=` + ${T===ee?"bg-blue-500 text-white border-blue-500 dark:bg-blue-600 dark:border-blue-600":"bg-white text-gray-600 border-gray-200 hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700"} + `,onClick:()=>P(ee),title:H(ee),children:[ee.toUpperCase(),ee===x&&h.jsx(Q,{className:"text-red-400 ml-0.5",children:"*"})]},ee))}),h.jsx(I,{className:"flex-1 min-w-0",children:n==="textarea"?h.jsx(Qn,{name:`${f}[${T}]`,value:C[T]||"",onChange:ee=>D(T,ee.target.value),placeholder:i,required:a&&T===x,disabled:l,maxLength:c,rows:d,className:Z}):h.jsx(kt,{type:"text",name:`${f}[${T}]`,value:C[T]||"",onChange:ee=>D(T,ee.target.value),placeholder:i,required:a&&T===x,disabled:l,maxLength:c,className:X})})]})})};return h.jsxs(I,{className:l?"opacity-50 cursor-not-allowed":void 0,id:p,...y,children:[r==="compact"?K():r==="tabs"?V():G(),b&&m&&h.jsx(Q,{className:"text-xs text-red-500 dark:text-red-400 mt-1 block",children:m})]})};Ha.displayName="MultilingualInput";const vb=()=>window.G7Core,Ua=({checked:t,value:e,onChange:n,disabled:r=!1,label:s,description:o,size:i="md",className:a="",name:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(()=>t??e??!1);v.useEffect(()=>{f(t??e??!1)},[t,e]);const g=_=>{const x=_.target.checked;f(x),n&&!r&&n(_)},p={sm:{track:"w-9 h-5",thumb:"after:h-4 after:w-4"},md:{track:"w-11 h-6",thumb:"after:h-5 after:w-5"},lg:{track:"w-14 h-7",thumb:"after:h-6 after:w-6"}}[i],y=()=>{if(!r){const _=!u;if(f(_),n){const x=vb();x?.createChangeEvent&&n(x.createChangeEvent({checked:_,name:l}))}}},b=_=>{r||_.key!==" "&&_.key!=="Enter"&&_.key!=="Spacebar"||(_.preventDefault(),y())};return h.jsxs("div",{className:`toggle-container ${a}`,id:c,...d,children:[h.jsxs("div",{className:`toggle-switch-wrapper ${r?"cursor-not-allowed opacity-50":"cursor-pointer"}`,role:"switch","aria-checked":u,"aria-disabled":r||void 0,"aria-label":s??l,tabIndex:r?-1:0,onKeyDown:b,onClick:_=>{_.preventDefault(),_.stopPropagation(),y()},children:[h.jsx("input",{type:"checkbox",id:l,name:l,checked:u,onChange:g,disabled:r,className:"sr-only peer",tabIndex:-1}),h.jsx("div",{className:`toggle-switch-track ${p.track} ${p.thumb}`})]}),s&&h.jsx("span",{className:`toggle-label ${r?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:y,children:s}),o&&h.jsx("p",{className:"toggle-description",children:o})]})},Fo=({status:t,label:e,showIcon:n=!0,iconName:r,className:s="",style:o,id:i,editorAttrs:a})=>{const l={success:"bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400 border-green-200 dark:border-green-800",warning:"bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800",error:"bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-400 border-red-200 dark:border-red-800",info:"bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-400 border-blue-200 dark:border-blue-800",pending:"bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-200 dark:border-gray-600",default:"bg-gray-50 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border-gray-200 dark:border-gray-600"},c={success:ce.CheckCircle,warning:ce.ExclamationTriangle,error:ce.XCircle,info:ce.InfoCircle,pending:ce.Clock,default:ce.QuestionCircle},d={success:"성공",warning:"경고",error:"오류",info:"정보",pending:"대기 중",default:"기본"},u=r||c[t],f=e||d[t],g=l[t];return h.jsxs(Q,{className:`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium border ${g} ${s}`,style:o,id:i,...a,children:[n&&h.jsx(re,{name:u,className:"w-4 h-4"}),h.jsx(Q,{children:f})]})},Id={blue:"bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300",green:"bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300",red:"bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300",gray:"bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300",yellow:"bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300",purple:"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300",orange:"bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300",teal:"bg-teal-100 text-teal-800 dark:bg-teal-900/30 dark:text-teal-300",cyan:"bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300"},xb={success:"green",warning:"yellow",danger:"red",error:"red",info:"blue",primary:"blue",secondary:"gray",text:"gray",default:"gray"},Ld={sm:"px-1.5 py-0.5 text-[10px]",md:"px-2 py-0.5 text-xs",lg:"px-2.5 py-1 text-sm"},Pd=({color:t,variant:e,text:n,size:r="md",className:s="",style:o,children:i,id:a,editorAttrs:l})=>{const c=t||(e?xb[e]:void 0)||"gray",d=Id[c]||Id.gray,u=Ld[r]||Ld.md;return h.jsxs(Q,{className:`inline-flex items-center rounded-full font-medium ${u} ${d} ${s}`,style:o,id:a,...l,children:[n,i]})};const{entries:$d,setPrototypeOf:Fd,isFrozen:wb,getPrototypeOf:kb,getOwnPropertyDescriptor:Cb}=Object;let{freeze:Gt,seal:cn,create:zd}=Object,{apply:Ba,construct:ja}=typeof Reflect<"u"&&Reflect;Gt||(Gt=function(e){return e}),cn||(cn=function(e){return e}),Ba||(Ba=function(e,n){for(var r=arguments.length,s=new Array(r>2?r-2:0),o=2;o1?n-1:0),s=1;s1?n-1:0),s=1;s2&&arguments[2]!==void 0?arguments[2]:Vo;Fd&&Fd(t,null);let r=e.length;for(;r--;){let s=e[r];if(typeof s=="string"){const o=n(s);o!==s&&(wb(e)||(e[r]=o),s=o)}t[s]=!0}return t}function Ob(t){for(let e=0;e/gm),Lb=cn(/\$\{[\w\W]*/gm),Pb=cn(/^data-[\-\w.\u00B7-\uFFFF]+$/),$b=cn(/^aria-[\-\w]+$/),Wd=cn(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Fb=cn(/^(?:\w+script|data):/i),zb=cn(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Gd=cn(/^html$/i),Vb=cn(/^[a-z][.\w]*(-[.\w]+)+$/i);var Yd=Object.freeze({__proto__:null,ARIA_ATTR:$b,ATTR_WHITESPACE:zb,CUSTOM_ELEMENT:Vb,DATA_ATTR:Pb,DOCTYPE_NAME:Gd,ERB_EXPR:Ib,IS_ALLOWED_URI:Wd,IS_SCRIPT_OR_DATA:Fb,MUSTACHE_EXPR:Db,TMPLIT_EXPR:Lb});const Cs={element:1,text:3,progressingInstruction:7,comment:8,document:9},Hb=function(){return typeof window>"u"?null:window},Ub=function(e,n){if(typeof e!="object"||typeof e.createPolicy!="function")return null;let r=null;const s="data-tt-policy-suffix";n&&n.hasAttribute(s)&&(r=n.getAttribute(s));const o="dompurify"+(r?"#"+r:"");try{return e.createPolicy(o,{createHTML(i){return i},createScriptURL(i){return i}})}catch{return console.warn("TrustedTypes policy "+o+" could not be created."),null}},Xd=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}};function Kd(){let t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:Hb();const e=ke=>Kd(ke);if(e.version="3.2.7",e.removed=[],!t||!t.document||t.document.nodeType!==Cs.document||!t.Element)return e.isSupported=!1,e;let{document:n}=t;const r=n,s=r.currentScript,{DocumentFragment:o,HTMLTemplateElement:i,Node:a,Element:l,NodeFilter:c,NamedNodeMap:d=t.NamedNodeMap||t.MozNamedAttrMap,HTMLFormElement:u,DOMParser:f,trustedTypes:g}=t,m=l.prototype,p=ks(m,"cloneNode"),y=ks(m,"remove"),b=ks(m,"nextSibling"),_=ks(m,"childNodes"),x=ks(m,"parentNode");if(typeof i=="function"){const ke=n.createElement("template");ke.content&&ke.content.ownerDocument&&(n=ke.content.ownerDocument)}let E,C="";const{implementation:k,createNodeIterator:N,createDocumentFragment:S,getElementsByTagName:w}=n,{importNode:M}=r;let T=Xd();e.isSupported=typeof $d=="function"&&typeof x=="function"&&k&&k.createHTMLDocument!==void 0;const{MUSTACHE_EXPR:P,ERB_EXPR:L,TMPLIT_EXPR:$,DATA_ATTR:A,ARIA_ATTR:D,IS_SCRIPT_OR_DATA:O,ATTR_WHITESPACE:z,CUSTOM_ELEMENT:H}=Yd;let{IS_ALLOWED_URI:j}=Yd,W=null;const U=Ge({},[...Hd,...Ya,...Xa,...Ka,...Ud]);let B=null;const G=Ge({},[...Bd,...qa,...jd,...Ho]);let V=Object.seal(zd(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),K=null,X=null,Z=!0,ee=!0,ne=!1,he=!0,J=!1,le=!0,ue=!1,ae=!1,ve=!1,fe=!1,ye=!1,we=!1,He=!0,se=!1;const Oe="user-content-";let Le=!0,Ie=!1,Be={},st=null;const ut=Ge({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let je=null;const nt=Ge({},["audio","video","img","source","image","track"]);let Ye=null;const te=Ge({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),ge="http://www.w3.org/1998/Math/MathML",me="http://www.w3.org/2000/svg",pe="http://www.w3.org/1999/xhtml";let be=pe,Ue=!1,Ce=null;const ze=Ge({},[ge,me,pe],Wa);let Se=Ge({},["mi","mo","mn","ms","mtext"]),Re=Ge({},["annotation-xml"]);const ot=Ge({},["title","style","font","a","script"]);let Pe=null;const Te=["application/xhtml+xml","text/html"],Ve="text/html";let De=null,qe=null;const it=n.createElement("form"),pt=function(Y){return Y instanceof RegExp||Y instanceof Function},rn=function(){let Y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!(qe&&qe===Y)){if((!Y||typeof Y!="object")&&(Y={}),Y=Bn(Y),Pe=Te.indexOf(Y.PARSER_MEDIA_TYPE)===-1?Ve:Y.PARSER_MEDIA_TYPE,De=Pe==="application/xhtml+xml"?Wa:Vo,W=xn(Y,"ALLOWED_TAGS")?Ge({},Y.ALLOWED_TAGS,De):U,B=xn(Y,"ALLOWED_ATTR")?Ge({},Y.ALLOWED_ATTR,De):G,Ce=xn(Y,"ALLOWED_NAMESPACES")?Ge({},Y.ALLOWED_NAMESPACES,Wa):ze,Ye=xn(Y,"ADD_URI_SAFE_ATTR")?Ge(Bn(te),Y.ADD_URI_SAFE_ATTR,De):te,je=xn(Y,"ADD_DATA_URI_TAGS")?Ge(Bn(nt),Y.ADD_DATA_URI_TAGS,De):nt,st=xn(Y,"FORBID_CONTENTS")?Ge({},Y.FORBID_CONTENTS,De):ut,K=xn(Y,"FORBID_TAGS")?Ge({},Y.FORBID_TAGS,De):Bn({}),X=xn(Y,"FORBID_ATTR")?Ge({},Y.FORBID_ATTR,De):Bn({}),Be=xn(Y,"USE_PROFILES")?Y.USE_PROFILES:!1,Z=Y.ALLOW_ARIA_ATTR!==!1,ee=Y.ALLOW_DATA_ATTR!==!1,ne=Y.ALLOW_UNKNOWN_PROTOCOLS||!1,he=Y.ALLOW_SELF_CLOSE_IN_ATTR!==!1,J=Y.SAFE_FOR_TEMPLATES||!1,le=Y.SAFE_FOR_XML!==!1,ue=Y.WHOLE_DOCUMENT||!1,fe=Y.RETURN_DOM||!1,ye=Y.RETURN_DOM_FRAGMENT||!1,we=Y.RETURN_TRUSTED_TYPE||!1,ve=Y.FORCE_BODY||!1,He=Y.SANITIZE_DOM!==!1,se=Y.SANITIZE_NAMED_PROPS||!1,Le=Y.KEEP_CONTENT!==!1,Ie=Y.IN_PLACE||!1,j=Y.ALLOWED_URI_REGEXP||Wd,be=Y.NAMESPACE||pe,Se=Y.MATHML_TEXT_INTEGRATION_POINTS||Se,Re=Y.HTML_INTEGRATION_POINTS||Re,V=Y.CUSTOM_ELEMENT_HANDLING||{},Y.CUSTOM_ELEMENT_HANDLING&&pt(Y.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(V.tagNameCheck=Y.CUSTOM_ELEMENT_HANDLING.tagNameCheck),Y.CUSTOM_ELEMENT_HANDLING&&pt(Y.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(V.attributeNameCheck=Y.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),Y.CUSTOM_ELEMENT_HANDLING&&typeof Y.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(V.allowCustomizedBuiltInElements=Y.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),J&&(ee=!1),ye&&(fe=!0),Be&&(W=Ge({},Ud),B=[],Be.html===!0&&(Ge(W,Hd),Ge(B,Bd)),Be.svg===!0&&(Ge(W,Ya),Ge(B,qa),Ge(B,Ho)),Be.svgFilters===!0&&(Ge(W,Xa),Ge(B,qa),Ge(B,Ho)),Be.mathMl===!0&&(Ge(W,Ka),Ge(B,jd),Ge(B,Ho))),Y.ADD_TAGS&&(W===U&&(W=Bn(W)),Ge(W,Y.ADD_TAGS,De)),Y.ADD_ATTR&&(B===G&&(B=Bn(B)),Ge(B,Y.ADD_ATTR,De)),Y.ADD_URI_SAFE_ATTR&&Ge(Ye,Y.ADD_URI_SAFE_ATTR,De),Y.FORBID_CONTENTS&&(st===ut&&(st=Bn(st)),Ge(st,Y.FORBID_CONTENTS,De)),Le&&(W["#text"]=!0),ue&&Ge(W,["html","head","body"]),W.table&&(Ge(W,["tbody"]),delete K.tbody),Y.TRUSTED_TYPES_POLICY){if(typeof Y.TRUSTED_TYPES_POLICY.createHTML!="function")throw ws('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof Y.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw ws('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');E=Y.TRUSTED_TYPES_POLICY,C=E.createHTML("")}else E===void 0&&(E=Ub(g,s)),E!==null&&typeof C=="string"&&(C=E.createHTML(""));Gt&&Gt(Y),qe=Y}},bt=Ge({},[...Ya,...Xa,...Tb]),wt=Ge({},[...Ka,...Ab]),jt=function(Y){let ie=x(Y);(!ie||!ie.tagName)&&(ie={namespaceURI:be,tagName:"template"});const xe=Vo(Y.tagName),ht=Vo(ie.tagName);return Ce[Y.namespaceURI]?Y.namespaceURI===me?ie.namespaceURI===pe?xe==="svg":ie.namespaceURI===ge?xe==="svg"&&(ht==="annotation-xml"||Se[ht]):!!bt[xe]:Y.namespaceURI===ge?ie.namespaceURI===pe?xe==="math":ie.namespaceURI===me?xe==="math"&&Re[ht]:!!wt[xe]:Y.namespaceURI===pe?ie.namespaceURI===me&&!Re[ht]||ie.namespaceURI===ge&&!Se[ht]?!1:!wt[xe]&&(ot[xe]||!bt[xe]):!!(Pe==="application/xhtml+xml"&&Ce[Y.namespaceURI]):!1},ft=function(Y){vs(e.removed,{element:Y});try{x(Y).removeChild(Y)}catch{y(Y)}},Xe=function(Y,ie){try{vs(e.removed,{attribute:ie.getAttributeNode(Y),from:ie})}catch{vs(e.removed,{attribute:null,from:ie})}if(ie.removeAttribute(Y),Y==="is")if(fe||ye)try{ft(ie)}catch{}else try{ie.setAttribute(Y,"")}catch{}},Nn=function(Y){let ie=null,xe=null;if(ve)Y=""+Y;else{const q=Ga(Y,/^[\r\n\t ]+/);xe=q&&q[0]}Pe==="application/xhtml+xml"&&be===pe&&(Y=''+Y+"");const ht=E?E.createHTML(Y):Y;if(be===pe)try{ie=new f().parseFromString(ht,Pe)}catch{}if(!ie||!ie.documentElement){ie=k.createDocument(be,"template",null);try{ie.documentElement.innerHTML=Ue?C:ht}catch{}}const Lt=ie.body||ie.documentElement;return Y&&xe&&Lt.insertBefore(n.createTextNode(xe),Lt.childNodes[0]||null),be===pe?w.call(ie,ue?"html":"body")[0]:ue?ie.documentElement:Lt},Wt=function(Y){return N.call(Y.ownerDocument||Y,Y,c.SHOW_ELEMENT|c.SHOW_COMMENT|c.SHOW_TEXT|c.SHOW_PROCESSING_INSTRUCTION|c.SHOW_CDATA_SECTION,null)},bn=function(Y){return Y instanceof u&&(typeof Y.nodeName!="string"||typeof Y.textContent!="string"||typeof Y.removeChild!="function"||!(Y.attributes instanceof d)||typeof Y.removeAttribute!="function"||typeof Y.setAttribute!="function"||typeof Y.namespaceURI!="string"||typeof Y.insertBefore!="function"||typeof Y.hasChildNodes!="function")},hr=function(Y){return typeof a=="function"&&Y instanceof a};function On(ke,Y,ie){zo(ke,xe=>{xe.call(e,Y,ie,qe)})}const Ao=function(Y){let ie=null;if(On(T.beforeSanitizeElements,Y,null),bn(Y))return ft(Y),!0;const xe=De(Y.nodeName);if(On(T.uponSanitizeElement,Y,{tagName:xe,allowedTags:W}),le&&Y.hasChildNodes()&&!hr(Y.firstElementChild)&&Yt(/<[/\w!]/g,Y.innerHTML)&&Yt(/<[/\w!]/g,Y.textContent)||Y.nodeType===Cs.progressingInstruction||le&&Y.nodeType===Cs.comment&&Yt(/<[/\w]/g,Y.data))return ft(Y),!0;if(!W[xe]||K[xe]){if(!K[xe]&&Io(xe)&&(V.tagNameCheck instanceof RegExp&&Yt(V.tagNameCheck,xe)||V.tagNameCheck instanceof Function&&V.tagNameCheck(xe)))return!1;if(Le&&!st[xe]){const ht=x(Y)||Y.parentNode,Lt=_(Y)||Y.childNodes;if(Lt&&ht){const q=Lt.length;for(let de=q-1;de>=0;--de){const Ee=p(Lt[de],!0);Ee.__removalCount=(Y.__removalCount||0)+1,ht.insertBefore(Ee,b(Y))}}}return ft(Y),!0}return Y instanceof l&&!jt(Y)||(xe==="noscript"||xe==="noembed"||xe==="noframes")&&Yt(/<\/no(script|embed|frames)/i,Y.innerHTML)?(ft(Y),!0):(J&&Y.nodeType===Cs.text&&(ie=Y.textContent,zo([P,L,$],ht=>{ie=xs(ie,ht," ")}),Y.textContent!==ie&&(vs(e.removed,{element:Y.cloneNode()}),Y.textContent=ie)),On(T.afterSanitizeElements,Y,null),!1)},Do=function(Y,ie,xe){if(He&&(ie==="id"||ie==="name")&&(xe in n||xe in it))return!1;if(!(ee&&!X[ie]&&Yt(A,ie))){if(!(Z&&Yt(D,ie))){if(!B[ie]||X[ie]){if(!(Io(Y)&&(V.tagNameCheck instanceof RegExp&&Yt(V.tagNameCheck,Y)||V.tagNameCheck instanceof Function&&V.tagNameCheck(Y))&&(V.attributeNameCheck instanceof RegExp&&Yt(V.attributeNameCheck,ie)||V.attributeNameCheck instanceof Function&&V.attributeNameCheck(ie,Y))||ie==="is"&&V.allowCustomizedBuiltInElements&&(V.tagNameCheck instanceof RegExp&&Yt(V.tagNameCheck,xe)||V.tagNameCheck instanceof Function&&V.tagNameCheck(xe))))return!1}else if(!Ye[ie]){if(!Yt(j,xs(xe,z,""))){if(!((ie==="src"||ie==="xlink:href"||ie==="href")&&Y!=="script"&&Eb(xe,"data:")===0&&je[Y])){if(!(ne&&!Yt(O,xs(xe,z,"")))){if(xe)return!1}}}}}}return!0},Io=function(Y){return Y!=="annotation-xml"&&Ga(Y,H)},Lo=function(Y){On(T.beforeSanitizeAttributes,Y,null);const{attributes:ie}=Y;if(!ie||bn(Y))return;const xe={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:B,forceKeepAttr:void 0};let ht=ie.length;for(;ht--;){const Lt=ie[ht],{name:q,namespaceURI:de,value:Ee}=Lt,Ze=De(q),Fe=Ee;let Nt=q==="value"?Fe:Mb(Fe);if(xe.attrName=Ze,xe.attrValue=Nt,xe.keepAttr=!0,xe.forceKeepAttr=void 0,On(T.uponSanitizeAttribute,Y,xe),Nt=xe.attrValue,se&&(Ze==="id"||Ze==="name")&&(Xe(q,Y),Nt=Oe+Nt),le&&Yt(/((--!?|])>)|<\/(style|title|textarea)/i,Nt)){Xe(q,Y);continue}if(Ze==="attributename"&&Ga(Nt,"href")){Xe(q,Y);continue}if(xe.forceKeepAttr)continue;if(!xe.keepAttr){Xe(q,Y);continue}if(!he&&Yt(/\/>/i,Nt)){Xe(q,Y);continue}J&&zo([P,L,$],Rm=>{Nt=xs(Nt,Rm," ")});const Jm=De(Y.nodeName);if(!Do(Jm,Ze,Nt)){Xe(q,Y);continue}if(E&&typeof g=="object"&&typeof g.getAttributeType=="function"&&!de)switch(g.getAttributeType(Jm,Ze)){case"TrustedHTML":{Nt=E.createHTML(Nt);break}case"TrustedScriptURL":{Nt=E.createScriptURL(Nt);break}}if(Nt!==Fe)try{de?Y.setAttributeNS(de,q,Nt):Y.setAttribute(q,Nt),bn(Y)?ft(Y):Vd(e.removed)}catch{Xe(q,Y)}}On(T.afterSanitizeAttributes,Y,null)},bs=function ke(Y){let ie=null;const xe=Wt(Y);for(On(T.beforeSanitizeShadowDOM,Y,null);ie=xe.nextNode();)On(T.uponSanitizeShadowNode,ie,null),Ao(ie),Lo(ie),ie.content instanceof o&&ke(ie.content);On(T.afterSanitizeShadowDOM,Y,null)};return e.sanitize=function(ke){let Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},ie=null,xe=null,ht=null,Lt=null;if(Ue=!ke,Ue&&(ke=""),typeof ke!="string"&&!hr(ke))if(typeof ke.toString=="function"){if(ke=ke.toString(),typeof ke!="string")throw ws("dirty is not a string, aborting")}else throw ws("toString is not a function");if(!e.isSupported)return ke;if(ae||rn(Y),e.removed=[],typeof ke=="string"&&(Ie=!1),Ie){if(ke.nodeName){const Ee=De(ke.nodeName);if(!W[Ee]||K[Ee])throw ws("root node is forbidden and cannot be sanitized in-place")}}else if(ke instanceof a)ie=Nn(""),xe=ie.ownerDocument.importNode(ke,!0),xe.nodeType===Cs.element&&xe.nodeName==="BODY"||xe.nodeName==="HTML"?ie=xe:ie.appendChild(xe);else{if(!fe&&!J&&!ue&&ke.indexOf("<")===-1)return E&&we?E.createHTML(ke):ke;if(ie=Nn(ke),!ie)return fe?null:we?C:""}ie&&ve&&ft(ie.firstChild);const q=Wt(Ie?ke:ie);for(;ht=q.nextNode();)Ao(ht),Lo(ht),ht.content instanceof o&&bs(ht.content);if(Ie)return ke;if(fe){if(ye)for(Lt=S.call(ie.ownerDocument);ie.firstChild;)Lt.appendChild(ie.firstChild);else Lt=ie;return(B.shadowroot||B.shadowrootmode)&&(Lt=M.call(r,Lt,!0)),Lt}let de=ue?ie.outerHTML:ie.innerHTML;return ue&&W["!doctype"]&&ie.ownerDocument&&ie.ownerDocument.doctype&&ie.ownerDocument.doctype.name&&Yt(Gd,ie.ownerDocument.doctype.name)&&(de=" +`+de),J&&zo([P,L,$],Ee=>{de=xs(de,Ee," ")}),E&&we?E.createHTML(de):de},e.setConfig=function(){let ke=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};rn(ke),ae=!0},e.clearConfig=function(){qe=null,ae=!1},e.isValidAttribute=function(ke,Y,ie){qe||rn({});const xe=De(ke),ht=De(Y);return Do(xe,ht,ie)},e.addHook=function(ke,Y){typeof Y=="function"&&vs(T[ke],Y)},e.removeHook=function(ke,Y){if(Y!==void 0){const ie=Sb(T[ke],Y);return ie===-1?void 0:_b(T[ke],ie,1)[0]}return Vd(T[ke])},e.removeHooks=function(ke){T[ke]=[]},e.removeAllHooks=function(){T=Xd()},e}var Bb=Kd();const Za=({content:t,text:e,isHtml:n=!0,className:r="",purifyConfig:s,id:o,editorAttrs:i})=>{const a=e??t??"";if(!a||a.trim()==="")return null;if(!n){const u=` whitespace-pre-wrap text-gray-900 dark:text-gray-100 font-sans ${r} - `.trim().replace(/\s+/g," ");return h.jsx(I,{className:u,id:o,...i,children:a})}const l={FORBID_TAGS:["script","noscript","iframe","frame","frameset","object","embed","applet","portal","form","input","textarea","select","button","style","link","meta","base","body","head","html","title","svg","math","audio","video","source","track","canvas","details","dialog","plaintext","xmp","listing","marquee","noframes","noembed","template","slot"],FORBID_ATTR:["onerror","onload","onclick","onmouseover","onfocus","onblur","ontoggle","oncanplay","onloadstart","formaction","xlink:href","action"],ALLOW_DATA_ATTR:!0,ADD_ATTR:["target"]},c=v.useMemo(()=>{const u=s?{...l,...s,FORBID_TAGS:[...l.FORBID_TAGS,...s.FORBID_TAGS||[]],FORBID_ATTR:[...l.FORBID_ATTR,...s.FORBID_ATTR||[]]}:l;return cy.sanitize(a,u).replace(/]*?)href=["']([^"']+)["']([^>]*?)>/gi,(g,m,p,y)=>(p.startsWith("http://")||p.startsWith("https://"))&&!g.includes("rel=")?``:g)},[a,s]),d=` + `.trim().replace(/\s+/g," ");return h.jsx(I,{className:u,id:o,...i,children:a})}const l={FORBID_TAGS:["script","noscript","iframe","frame","frameset","object","embed","applet","portal","form","input","textarea","select","button","style","link","meta","base","body","head","html","title","svg","math","audio","video","source","track","canvas","details","dialog","plaintext","xmp","listing","marquee","noframes","noembed","template","slot"],FORBID_ATTR:["onerror","onload","onclick","onmouseover","onfocus","onblur","ontoggle","oncanplay","onloadstart","formaction","xlink:href","action"],ALLOW_DATA_ATTR:!0,ADD_ATTR:["target"]},c=v.useMemo(()=>{const u=s?{...l,...s,FORBID_TAGS:[...l.FORBID_TAGS,...s.FORBID_TAGS||[]],FORBID_ATTR:[...l.FORBID_ATTR,...s.FORBID_ATTR||[]]}:l;return Bb.sanitize(a,u).replace(/]*?)href=["']([^"']+)["']([^>]*?)>/gi,(g,m,p,y)=>(p.startsWith("http://")||p.startsWith("https://"))&&!g.includes("rel=")?``:g)},[a,s]),d=` prose dark:prose-invert max-w-none prose-p:my-2 prose-headings:font-bold prose-headings:mt-6 prose-headings:mb-4 @@ -36,67 +35,67 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho [&_tr:hover]:bg-gray-50 dark:[&_tr:hover]:bg-gray-700/50 after:content-[''] after:block after:clear-both ${r} - `.trim().replace(/\s+/g," ");return h.jsx(I,{className:d,dangerouslySetInnerHTML:{__html:c},id:o,...i})},Qo=window.G7Core?.createLogger?.("Comp:DataGrid")??{log:(...e)=>console.log("[Comp:DataGrid]",...e),warn:(...e)=>console.warn("[Comp:DataGrid]",...e),error:(...e)=>console.error("[Comp:DataGrid]",...e)},zs=(e,t)=>window.G7Core?.t?.(e,t)??e,al={Div:I,Span:Q,Button:ie,Icon:se,Checkbox:_n,P:wt,A:Kr,Label:dn,Input:_t,H1:ja,H2:ar,H3:Xr,H4:Fd,Img:Sn,Select:qr,Textarea:ir,Table:Xa,Thead:Ka,Tbody:qa,Tfoot:Za,Tr:lr,Th:Zr,Td:Rt,MultilingualInput:Qa,Toggle:Ja,StatusBadge:qo,Badge:Wd,HtmlContent:il,ActionMenu:Ps},ll=e=>{if(e==null)return"";if(typeof e=="string"||typeof e=="number"||typeof e=="boolean")return String(e);if(Array.isArray(e))return e.map(t=>ll(t)).join(", ");if(typeof e=="object"){if(v.isValidElement(e))return e;if(Object.keys(e).length===0)return"";try{return JSON.stringify(e)}catch{return Qo.warn("객체를 문자열로 변환할 수 없습니다:",e),"[Object]"}}return String(e)},au=(e,t)=>{if(!e)return!0;const n=e.match(/^\{\{(.+)\}\}$/);if(!n)return!0;const r=n[1].trim();try{return!!new Function("row",`return ${r}`)(t)}catch(s){return Qo.warn("subRow 조건 평가 실패:",e,s),!0}},Vs=(e,t,n,r="",s)=>{const o=window.G7Core;if(o?.renderItemChildren){const i={row:t,value:n,$value:n};return o.renderItemChildren(e,i,al,r,s?{componentContext:s}:void 0)}return Qo.warn("G7Core.renderItemChildren을 사용할 수 없습니다."),null},cl=(e,t,n="",r)=>{const s=window.G7Core;if(s?.renderItemChildren){const o={row:t,$value:t};return s.renderItemChildren(e,o,al,n,r?{componentContext:r}:void 0)}return Qo.warn("G7Core.renderItemChildren을 사용할 수 없습니다."),null},dy=({id:e,columns:t,data:n,sortable:r=!0,pagination:s=!1,pageSize:o=10,onRowClick:i,className:a="",style:l,sortField:c,sortDirection:d,onSortChange:u,selectable:f=!1,selectedIds:g=[],onSelectionChange:m,idField:p="id",showColumnSelector:y=!1,visibleColumns:b,onColumnVisibilityChange:_,responsiveBreakpoint:x=768,cardRenderer:M,rowActions:C,onRowAction:k,alwaysShowPagination:N=!1,serverSidePagination:S=!1,serverCurrentPage:w,serverTotalPages:E,onPageChange:D,prevText:P,nextText:L,columnSelectorText:$,emptyMessage:A,actionsColumnHeader:O,requiredText:T,selectedCountText:z,showFirstLast:H=!0,expandable:j=!1,expandedRowIds:G,onExpandChange:U,expandChildren:W,expandedRowRender:Y,expandColumnWidth:V="48px",expandIconExpanded:X=ce.ChevronDown,expandIconCollapsed:K=ce.ChevronRight,expandConditionField:q,dynamicColumns:ne,dynamicColumnsInsertAfter:ee,expandContext:de,__componentContext:J,subRowChildren:le,subRowCondition:pe,subRowClassName:ae="px-6 py-2 bg-gray-50 dark:bg-gray-900",subRowRender:ye,footerCells:he,footerClassName:ge="bg-gray-50 dark:bg-gray-800/50 font-medium",footerCardChildren:ke,editorAttrs:Fe})=>{const oe=P||void 0,Me=L||void 0,Ae=$??zs("common.column_selector"),De=A??zs("common.no_data"),Ke=O??zs("common.actions"),pt=T??zs("common.required"),mt=z??zs("common.selected_count"),je=window.G7Core,tt=je?.useControllableState,We=je?.shallowArrayEqual,[re,ue]=tt?tt(c,null,Z=>{Z!==null&&u?.(Z,me)}):[c??null,()=>{}],[me,we]=tt?tt(d,"asc",Z=>{re!==null&&u?.(re,Z)}):[d??"asc",()=>{}],[ve,Ue]=v.useState(1),[_e,Le]=tt?tt(G,[],U,{isEqual:We}):[G??[],()=>{}],[Ce,Ge]=v.useState(!1),Qe=v.useRef(null),ze=je?.useResponsive,Ne=ze?.(),Ve=Ne?Ne.width{if(!ne||!Array.isArray(ne)||ne.length===0)return t;if(ee){const Z=t.findIndex(be=>be.field===ee);if(Z!==-1)return[...t.slice(0,Z+1),...ne,...t.slice(Z+1)]}return[...t,...ne]},[t,ne,ee]),[nt,lt]=v.useState(()=>b&&b.length>0?b:t.filter(Z=>!Z.hidden).map(Z=>Z.field));v.useEffect(()=>{b&&b.length>0&<(b)},[b]);const dt=v.useRef("");v.useEffect(()=>{const Z=t.map(be=>be.field).join(",");dt.current&&Z!==dt.current&&(!b||b.length===0)&<(t.filter(be=>!be.hidden).map(be=>be.field)),dt.current=Z},[t,b]);const kn=v.useRef([]),vt=v.useRef(null);v.useEffect(()=>{if(ne&&ne.length>0){const Z=ne.map(rt=>rt.field);if(vt.current===null&&(vt.current=b!==void 0&&b.length>0,vt.current)){kn.current=Z;return}const be=kn.current,Pe=Z.filter(rt=>!be.includes(rt));if(Pe.length>0){const rt=Pe.filter(Ie=>!nt.includes(Ie));if(rt.length>0){const Ie=[...nt,...rt];lt(Ie),_?.(Ie)}}kn.current=Z}},[ne]),v.useEffect(()=>{S||Ue(1)},[n,S]),v.useEffect(()=>{const Z=be=>{Qe.current&&!Qe.current.contains(be.target)&&Ge(!1)};return Ce&&document.addEventListener("mousedown",Z),()=>document.removeEventListener("mousedown",Z)},[Ce]);const Be=v.useMemo(()=>!y&&(!b||b.length===0)?qe.filter(Z=>!Z.hidden):qe.filter(Z=>nt.includes(Z.field)),[qe,nt,y,b]),xt=v.useMemo(()=>!re||!r||!n?n||[]:u?n:[...n].sort((Z,be)=>{const Pe=Z[re],rt=be[re];if(Pe===rt)return 0;const Ie=Pe>rt?1:-1;return me==="asc"?Ie:-Ie}),[n,re,me,r,u]),Je=v.useMemo(()=>{if(S||!s)return xt;const Z=(ve-1)*o,be=Z+o;return xt.slice(Z,be)},[xt,ve,o,s,S]),In=S?w||1:ve,an=S?E||1:Math.ceil((xt?.length||0)/o),ln=v.useCallback(Z=>{S&&D?D(Z):Ue(Z)},[S,D]),qn=Z=>{if(!r||t.find(rt=>rt.field===Z)?.sortable===!1)return;const Pe=re===Z&&me==="asc"?"desc":"asc";tt?(ue(Z),we(Pe)):u&&u(Z,Pe)},Wr=v.useCallback(()=>{if(!m||!Je)return;const Z=Je.map(Pe=>Pe[p]);if(Z.every(Pe=>g.includes(Pe)))m(g.filter(Pe=>!Z.includes(Pe)));else{const Pe=[...new Set([...g,...Z])];m(Pe)}},[Je,g,m,p]),cn=v.useCallback(Z=>{m&&(g.includes(Z)?m(g.filter(be=>be!==Z)):m([...g,Z]))},[g,m]),Zn=v.useCallback(Z=>{if(qe.find(rt=>rt.field===Z)?.required)return;const Pe=nt.includes(Z)?nt.filter(rt=>rt!==Z):[...nt,Z];lt(Pe),_?.(Pe)},[qe,nt,_]),Go=v.useCallback((Z,be)=>{k?.(Z,be)},[k]),Yo=v.useCallback(Z=>C?C.map(be=>{let Pe=be.disabled??!1;if(be.disabledField&&!Pe){const rt=be.disabledField.split(".");let Ie=Z;for(const xe of rt)Ie=Ie?.[xe];Ie||(Pe=!0)}return{...be,disabled:Pe,onClick:()=>Go(be.id,Z)}}):[],[C,Go]),kr=v.useCallback(Z=>{if(!q)return!0;const be=Z[q];return Array.isArray(be)?be.length>0:!!be},[q]),Gr=v.useCallback(Z=>{const be=_e.includes(Z)?_e.filter(Pe=>Pe!==Z):[..._e,Z];tt?Le(be):U&&U(be)},[_e,U,tt,Le]),or=v.useCallback(Z=>_e.includes(Z),[_e]),Yr=v.useCallback(Z=>Y?Y(Z):W&&W.length>0&&je?.renderExpandContent?je.renderExpandContent({children:W,row:Z,expandContext:de,componentContext:J,componentMap:al,keyPrefix:`expand-${Z[p]}`}):null,[Y,W,p,de,J,J?.state]),Ln=v.useMemo(()=>!Je||Je.length===0?!1:Je.every(Z=>g.includes(Z[p])),[Je,g,p]),Ua=v.useMemo(()=>{if(!Je||Je.length===0)return!1;const Z=Je.filter(be=>g.includes(be[p])).length;return Z>0&&Z{const Pe=Z[p],rt=or(Pe);return h.jsxs(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 mb-4",children:[h.jsxs(I,{className:"flex items-start justify-between",children:[j&&kr(Z)&&h.jsx(ie,{onClick:()=>Gr(Pe),className:"p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors mr-2","aria-label":rt?"Collapse card":"Expand card",children:h.jsx(se,{name:rt?X:K,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})}),f&&h.jsx(_n,{checked:g.includes(Z[p]),onChange:()=>cn(Z[p]),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded mr-3 mt-1"}),h.jsx(I,{className:"flex-1",children:h.jsx(I,{className:"grid grid-cols-2 gap-x-4 gap-y-2",children:be.map(Ie=>h.jsxs(I,{className:"min-w-0",children:[h.jsx(Q,{className:"text-xs font-medium text-gray-500 dark:text-gray-400 uppercase",children:Ie.header}),h.jsx(I,{className:"text-sm text-gray-900 dark:text-gray-200 mt-0.5 break-words",children:Ie.cellChildren&&Ie.cellChildren.length>0?Vs(Ie.cellChildren,Z,Z[Ie.field],`card-${Z[p]}-${Ie.field}`,J):Ie.render?Ie.render(Z[Ie.field],Z):ll(Z[Ie.field])})]},Ie.field))})}),C&&C.length>0&&h.jsx(I,{className:"ml-2",children:h.jsx(Ps,{items:Yo(Z),triggerLabel:"",triggerIconName:ce.EllipsisHorizontal})})]}),j&&rt&&kr(Z)&&h.jsx(I,{className:"mt-4 pt-4 border-t border-gray-200 dark:border-gray-700",children:Yr(Z)}),(le&&le.length>0||ye)&&au(pe,Z)&&h.jsx(I,{className:ae?`mt-3 ${ae}`:"mt-3 pt-3 border-t border-gray-100 dark:border-gray-700",children:ye?ye(Z):cl(le,Z,`card-subrow-${Pe}`,J)})]})};return Ve?h.jsxs(I,{id:e,className:`w-full ${a}`,style:l,...Fe,children:[y&&h.jsx(I,{className:"mb-4 flex justify-end",children:h.jsxs(I,{ref:Qe,className:"relative",children:[h.jsxs(ie,{onClick:()=>Ge(!Ce),className:"flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700",children:[h.jsx(se,{name:ce.Cog,className:"w-4 h-4 text-gray-500 dark:text-gray-400"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:Ae})]}),Ce&&h.jsx(I,{className:"absolute right-0 z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",children:h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:qe.map(Z=>h.jsxs(I,{className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${Z.required?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:()=>!Z.required&&Zn(Z.field),children:[h.jsx(_n,{checked:nt.includes(Z.field),disabled:Z.required,onChange:()=>{},className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"}),h.jsxs(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:[Z.header,Z.required&&h.jsxs(Q,{className:"text-xs text-gray-500 dark:text-gray-400 ml-1",children:["(",pt,")"]})]})]},Z.field))})})]})}),f&&g.length>0&&mt&&h.jsx(I,{className:"mb-4 text-sm text-gray-600 dark:text-gray-400",children:mt.replace("{count}",String(g.length))}),h.jsx(I,{id:e?`${e}__body`:void 0,children:Je&&Je.length>0?Je.map((Z,be)=>h.jsx(I,{children:M?M(Z,Be):Ba(Z,Be)},Z[p]||be)):h.jsx(I,{className:"text-center py-8 text-gray-500 dark:text-gray-400",children:De})}),he&&he.length>0&&Je&&Je.length>0&&h.jsx(I,{className:`rounded-lg p-4 mt-2 ${ge}`,children:ke&&ke.length>0?Vs(ke,{},null,"footer-card",J):h.jsx(I,{className:"space-y-1",children:he.filter(Z=>Z.text||Z.children&&Z.children.length>0).map((Z,be)=>{const Pe=Be.find(rt=>rt.field===Z.field);return h.jsxs(I,{className:"flex justify-between text-sm",children:[h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:Pe?.header||""}),h.jsx(Q,{className:Z.className||"text-gray-900 dark:text-gray-100",children:Z.children&&Z.children.length>0?Vs(Z.children,{},null,`footer-mobile-${be}`,J):Z.text||""})]},`footer-mobile-${be}`)})})}),s&&(N||an>1)&&h.jsx(I,{className:"flex justify-center mt-4",children:h.jsx(Ko,{currentPage:In,totalPages:an,onPageChange:ln,showFirstLast:H,maxVisiblePages:5,prevText:oe,nextText:Me})})]}):h.jsxs(I,{id:e,className:`w-full ${a}`,style:l,...Fe,children:[y&&h.jsx(I,{className:"mb-4 flex justify-end",children:h.jsxs(I,{ref:Qe,className:"relative",children:[h.jsxs(ie,{onClick:()=>Ge(!Ce),className:"flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700",children:[h.jsx(se,{name:ce.Cog,className:"w-4 h-4 text-gray-500 dark:text-gray-400"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:Ae})]}),Ce&&h.jsx(I,{className:"absolute right-0 z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",children:h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:qe.map(Z=>h.jsxs(I,{className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${Z.required?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:()=>!Z.required&&Zn(Z.field),children:[h.jsx(_n,{checked:nt.includes(Z.field),disabled:Z.required,onChange:()=>{},className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"}),h.jsxs(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:[Z.header,Z.required&&h.jsxs(Q,{className:"text-xs text-gray-500 dark:text-gray-400 ml-1",children:["(",pt,")"]})]})]},Z.field))})})]})}),f&&g.length>0&&mt&&h.jsx(I,{className:"mb-4 text-sm text-gray-600 dark:text-gray-400",children:mt.replace("{count}",String(g.length))}),h.jsx(I,{id:e?`${e}__body`:void 0,className:"overflow-x-auto border border-gray-200 dark:border-gray-700 rounded-lg",children:h.jsxs(Xa,{className:"w-full",children:[h.jsx(Ka,{className:"bg-gray-50 dark:bg-gray-700",children:h.jsxs(lr,{children:[j&&h.jsx(Zr,{className:"px-2 py-3",style:{width:V}}),f&&h.jsx(Zr,{className:"px-4 py-3 w-10",children:h.jsx(_n,{checked:Ln,ref:Z=>{Z&&(Z.indeterminate=Ua)},onChange:Wr,className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"})}),Be.map(Z=>h.jsxs(Zr,{className:`px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider ${r&&Z.sortable!==!1?"cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600":""}`,style:{width:Z.width,minWidth:Z.width},onClick:()=>qn(Z.field),children:[Z.header,re===Z.field&&h.jsx(Q,{className:"ml-1",children:me==="asc"?"↑":"↓"})]},Z.field)),C&&C.length>0&&h.jsx(Zr,{className:"px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24",children:Ke})]})}),h.jsx(qa,{className:`bg-white dark:bg-gray-800 ${ae?"":"divide-y divide-gray-200 dark:divide-gray-700"}`,children:Je&&Je.length>0?Je.map((Z,be)=>{const Pe=Z[p]||be,rt=or(Pe);return h.jsxs(v.Fragment,{children:[h.jsxs(lr,{className:`${i?"cursor-pointer":""} hover:bg-gray-50 dark:hover:bg-gray-700 ${g.includes(Z[p])?"bg-blue-50 dark:bg-blue-900/20":""} ${ae&&be>0?"border-t border-gray-200 dark:border-gray-700":""}`,onClick:()=>i?.(Z),children:[j&&h.jsx(Rt,{className:"px-2 py-4 text-center",onClick:Ie=>Ie.stopPropagation(),children:kr(Z)?h.jsx(ie,{onClick:()=>Gr(Pe),className:"p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors","aria-label":rt?"Collapse row":"Expand row",children:h.jsx(se,{name:rt?X:K,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})}):null}),f&&h.jsx(Rt,{className:"px-4 py-4",onClick:Ie=>Ie.stopPropagation(),children:h.jsx(_n,{checked:g.includes(Z[p]),onChange:()=>cn(Z[p]),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"})}),Be.map(Ie=>h.jsx(Rt,{className:"px-6 py-4 align-top whitespace-nowrap text-sm text-gray-900 dark:text-gray-200",style:{width:Ie.width,minWidth:Ie.width},children:Ie.cellChildren&&Ie.cellChildren.length>0?Vs(Ie.cellChildren,Z,Z[Ie.field],`${Z[p]}-${Ie.field}`,J):Ie.render?Ie.render(Z[Ie.field],Z):ll(Z[Ie.field])},Ie.field)),C&&C.length>0&&h.jsx(Rt,{className:"px-6 py-4 text-right",onClick:Ie=>Ie.stopPropagation(),children:h.jsx(Ps,{items:Yo(Z),triggerLabel:"",triggerIconName:ce.EllipsisHorizontal,position:"right"})})]}),j&&rt&&kr(Z)&&h.jsx(lr,{className:"bg-gray-50 dark:bg-gray-900",children:h.jsx(Rt,{colSpan:Be.length+1+(f?1:0)+(C&&C.length>0?1:0),className:"px-6 py-4",children:Yr(Z)})}),(le&&le.length>0||ye)&&au(pe,Z)&&h.jsx(lr,{className:ae?"!border-t-0":"",children:h.jsx(Rt,{colSpan:Be.length+(j?1:0)+(f?1:0)+(C&&C.length>0?1:0),className:ae?"p-2":ae||"px-6 py-2 bg-gray-50 dark:bg-gray-900",children:ae?h.jsx(I,{className:ae,children:ye?ye(Z):cl(le,Z,`subrow-${Pe}`,J)}):ye?ye(Z):cl(le,Z,`subrow-${Pe}`,J)})})]},Pe)}):h.jsx(lr,{children:h.jsx(Rt,{colSpan:Be.length+(j?1:0)+(f?1:0)+(C&&C.length>0?1:0),className:"px-6 py-8 text-center text-gray-500 dark:text-gray-400",children:De})})}),he&&he.length>0&&Je&&Je.length>0&&h.jsx(Za,{children:h.jsxs(lr,{className:ge,children:[j&&h.jsx(Rt,{className:"px-2 py-4"}),f&&h.jsx(Rt,{className:"px-4 py-4"}),(()=>{const Z=new Map(he.filter(be=>be.field).map(be=>[be.field,be]));return Be.map(be=>{const Pe=Z.get(be.field);return Pe?h.jsx(Rt,{className:`px-6 py-4 text-sm text-gray-900 dark:text-gray-200 ${Pe.className||""}`,colSpan:Pe.colSpan,style:{width:be.width,minWidth:be.width},children:Pe.children&&Pe.children.length>0?Vs(Pe.children,{},null,`footer-${be.field}`,J):Pe.text||""},`footer-${be.field}`):h.jsx(Rt,{className:"px-6 py-4",style:{width:be.width,minWidth:be.width}},`footer-${be.field}`)})})(),C&&C.length>0&&h.jsx(Rt,{className:"px-6 py-4"})]})})]})}),s&&(N||an>1)&&h.jsx(I,{className:"flex justify-center mt-4",children:h.jsx(Ko,{currentPage:In,totalPages:an,onPageChange:ln,showFirstLast:H,maxVisiblePages:5,prevText:oe,nextText:Me})})]})},dl=window.G7Core?.createLogger?.("Comp:CardGrid")??{log:(...e)=>console.log("[Comp:CardGrid]",...e),warn:(...e)=>console.warn("[Comp:CardGrid]",...e),error:(...e)=>console.error("[Comp:CardGrid]",...e)},uy=(e,t)=>window.G7Core?.t?.(e,t)??e,fy=()=>window.G7Core?.getComponentMap?.()||{},hy=(e,t)=>{if(!e)return!0;const n=e.match(/^\{\{(.+)\}\}$/);if(!n)return!0;const r=n[1].trim();if(dl.log("evaluateCondition:",{condition:e,expr:r,row:t,rowStatus:t?.status}),/[&|<>=!]/.test(r)||r.includes(".length"))try{const o=!!new Function("row",`return ${r}`)(t);return dl.log("evaluateCondition result:",o),o}catch(s){return dl.warn("evaluateCondition: 표현식 평가 실패:",r,s),!1}if(r.startsWith("!row.")){const s=r.slice(5);return!t[s]}if(r.startsWith("row.")){const s=r.slice(4);return!!t[s]}return!0},lu=(e,t,n,r,s)=>{const o=window.G7Core;if(e.name==="ActionMenu"){if(r&&r.length>0&&s)return{...e,props:{...e.props,items:r.map(i=>({...i,onClick:()=>s(i.id,t)}))}};if(e.actions&&Array.isArray(e.actions)){const i=e.props?.items||[],a=new Map(e.actions.map(c=>[c.id,c])),l=i.map(c=>{if(typeof c.onClick=="string"){const d=c.onClick,u=a.get(d);if(u&&o?.ActionDispatcher)return{...c,onClick:()=>{const f={...n};o.ActionDispatcher.dispatch(u,f)}}}return c});return{...e,props:{...e.props,items:l}}}}return e.children&&Array.isArray(e.children)?{...e,children:e.children.map(i=>lu(i,t,n,r,s))}:e},gy=(e,t,n,r="",s,o)=>{const i=window.G7Core;if(i?.renderItemChildren){const a=e.filter(d=>{const u=d.condition||d.if;return u?hy(u,t):!0}),l={row:t,$value:t,index:n},c=a.map(d=>lu(d,t,l,s,o));try{const d=fy();return i.renderItemChildren(c,l,d,r)}catch{return h.jsxs(I,{className:"text-red-600 dark:text-red-400 p-4",children:[h.jsx(wt,{children:"컴포넌트 로드 실패"}),h.jsx(wt,{className:"text-sm",children:"[CardGrid] 데이터를 표시할 수 없습니다."})]})}}return null},ul=(e,t)=>{if(e.name==="Div"&&e.children&&e.children.length>0){const n=e.children.map((r,s)=>ul(r,`${t}-child-${s}`)).filter(Boolean);return h.jsx(I,{className:e.props?.className||"",children:n},t)}return h.jsx(I,{className:"h-4 bg-gray-200 dark:bg-gray-700 rounded w-full animate-pulse"},t)},cu=(e,t="skeleton")=>!e||e.length===0?null:e.map((n,r)=>{const s=`${t}-${n.id||n.name}-${r}`;return n.condition||n.if?null:(n.iteration,ul(n,s))}).filter(Boolean),py=({data:e,cardColumns:t=[],gridColumns:n=3,gap:r=4,responsiveColumns:s,cardClassName:o="",idField:i="id",pagination:a=!0,pageSize:l=12,serverSidePagination:c=!1,serverCurrentPage:d,serverTotalPages:u,alwaysShowPagination:f=!1,onPageChange:g,paginationInfoText:m,rowActions:p,onRowAction:y,className:b="",style:_,emptyMessage:x,prevText:M,nextText:C,showSkeleton:k=!0,skeletonCount:N,skeletonCellChildren:S,id:w,editorAttrs:E})=>{const D=x??uy("common.no_data"),P=t[0]?.cellChildren||[],[L,$]=v.useState(1);window.G7Core?.useResponsive?.();const O=c?d??1:L,T=c?u??1:Math.ceil((e?.length??0)/l),z=Y=>{c?g?.(Y):$(Y)},H=v.useCallback((Y,V)=>{y?.(Y,V)},[y]),j=v.useMemo(()=>{if(!e)return[];if(!a||c)return e;const Y=(L-1)*l,V=Y+l;return e.slice(Y,V)},[e,a,c,L,l]),G=v.useMemo(()=>{const Y=[],V=s?.sm??1;Y.push(`grid-cols-${V}`),s?.sm&&Y.push(`sm:grid-cols-${s.sm}`);const X=s?.md??Math.min(n,2);Y.push(`md:grid-cols-${X}`);const K=s?.lg??n;return Y.push(`lg:grid-cols-${K}`),s?.xl&&Y.push(`xl:grid-cols-${s.xl}`),Y.join(" ")},[n,s]),U=v.useMemo(()=>k?S&&S.length>0?cu(S,"custom-skeleton"):P&&P.length>0?cu(P,"auto-skeleton"):null:null,[k,S,P]);if(!e){if(!k)return null;const Y=N??n;return h.jsx(I,{className:`grid ${G} gap-${r} ${b}`,style:_,id:w,...E,children:Array.from({length:Y}).map((V,X)=>h.jsx(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 space-y-4",children:U},`skeleton-${X}`))})}if(j.length===0)return h.jsx(I,{className:b,style:_,id:w,...E,children:h.jsx(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-12",children:h.jsx(wt,{className:"text-center text-gray-600 dark:text-gray-400",children:D})})});const W=a&&(f||T>1);return h.jsxs(I,{className:b,style:_,id:w,...E,children:[h.jsx(I,{className:`grid ${G} gap-${r}`,children:j.map((Y,V)=>h.jsx(I,{className:o,children:gy(P,Y,V,`card-${V}`,p,H)},Y[i]||V))}),W&&h.jsxs(I,{className:"mt-6 space-y-4 flex flex-col items-center",children:[h.jsx(Ko,{currentPage:O,totalPages:T,onPageChange:z,showFirstLast:!1,maxVisiblePages:5,prevText:M,nextText:C}),m&&h.jsx(I,{className:"text-sm text-gray-600 dark:text-gray-400 text-center",children:m})]})]})},my=(e,t)=>window.G7Core?.t?.(e,t)??e,Jo=({isOpen:e,onClose:t,title:n,titlePrefix:r,titleSuffix:s,closeOnBackdropClick:o=!0,closeOnOverlayClick:i,showCloseButton:a=!0,closeOnEscape:l=!0,children:c,width:d="500px",className:u="",style:f,__dataContext:g,__translationContext:m})=>{const p=i!==void 0?i:o,y=v.useRef(null),b=v.useRef(null);if(v.useEffect(()=>{const M=C=>{C.key==="Escape"&&e&&l&&t()};if(e&&l)return document.addEventListener("keydown",M),()=>document.removeEventListener("keydown",M)},[e,t,l]),v.useEffect(()=>{if(e&&y.current){b.current=document.activeElement;const M=y.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),C=M[0],k=M[M.length-1];C?.focus();const N=S=>{S.key==="Tab"&&(S.shiftKey?document.activeElement===C&&(S.preventDefault(),k?.focus()):document.activeElement===k&&(S.preventDefault(),C?.focus()))};return document.addEventListener("keydown",N),()=>{document.removeEventListener("keydown",N),b.current?.focus()}}},[e]),v.useEffect(()=>{if(e)return document.body.style.overflow="hidden",()=>{document.body.style.overflow=""}},[e]),!e)return null;const _=f?.zIndex??50,x=(M,C)=>{if(!M||M.length===0)return null;const k=window.G7Core;if(!k)return null;const N=k.getDynamicRenderer?.();return N?M.map((S,w)=>h.jsx(N,{componentDef:S,dataContext:g||{},translationContext:m,registry:k.getComponentRegistry(),bindingEngine:k.getDataBindingEngine(),translationEngine:k.getTranslationEngine(),actionDispatcher:k.getActionDispatcher(),isRootRenderer:!1},`${C}-${w}`)):null};return h.jsxs(I,{className:"modal-wrapper",style:{zIndex:_},onClick:p?t:void 0,children:[h.jsx(I,{className:"modal-backdrop","aria-hidden":"true"}),h.jsxs(I,{ref:y,className:`modal-container ${u}`,style:{width:d,...f?{...f,zIndex:void 0}:{}},onClick:M=>M.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":n?"modal-title":void 0,children:[(n||r||s||a)&&h.jsxs(I,{className:"modal-header-row",children:[h.jsxs(I,{className:"flex-center gap-sm",children:[x(r,"title-prefix"),n&&h.jsx(I,{id:"modal-title",className:"modal-title",children:n}),x(s,"title-suffix")]}),a&&h.jsx(ie,{onClick:t,className:"modal-close-btn","aria-label":my("common.close_modal"),children:h.jsx("svg",{className:"icon-lg",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),h.jsx(I,{className:"modal-body-content",children:c})]})]})},by=({label:e,items:t,onItemClick:n,position:r="bottom-left",variant:s="default",className:o="",style:i,id:a,editorAttrs:l})=>{const[c,d]=v.useState(!1),[u,f]=v.useState(-1),g=v.useRef(null),m=v.useRef(null),[p,y]=v.useState(r);v.useEffect(()=>{const x=M=>{g.current&&!g.current.contains(M.target)&&(d(!1),f(-1))};if(c)return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[c]),v.useEffect(()=>{const x=M=>{if(!c)return;const C=t.map((k,N)=>k.disabled?-1:N).filter(k=>k!==-1);switch(M.key){case"ArrowDown":M.preventDefault(),f(k=>{const S=(C.indexOf(k)+1)%C.length;return C[S]});break;case"ArrowUp":M.preventDefault(),f(k=>{const N=C.indexOf(k),S=N<=0?C.length-1:N-1;return C[S]});break;case"Enter":M.preventDefault(),u>=0&&!t[u].disabled&&b(t[u]);break;case"Escape":M.preventDefault(),d(!1),f(-1);break}};if(c)return document.addEventListener("keydown",x),()=>document.removeEventListener("keydown",x)},[c,u,t]),v.useEffect(()=>{if(c&&m.current&&g.current){const x=m.current.getBoundingClientRect(),M=g.current.getBoundingClientRect(),C=window.innerWidth,k=window.innerHeight,N=x.width||224,S=x.height||200;let w=r;r.includes("left")?M.left+N>C&&(w=w.replace("left","right")):M.right-N<0&&(w=w.replace("right","left")),r.includes("top")?M.top-S<0&&(w=w.replace("top","bottom")):M.bottom+S>k&&(w=w.replace("bottom","top")),y(w)}},[c,r]);const b=x=>{x.disabled||(x.onClick?.(),n?.(x.value,x),d(!1),f(-1))},_={"bottom-left":"dropdown-menu-bottom-left","bottom-right":"dropdown-menu-bottom-right","top-left":"dropdown-menu-top-left","top-right":"dropdown-menu-top-right"};return h.jsxs(I,{ref:g,className:`dropdown ${o}`,style:i,id:a,...l,children:[h.jsxs(ie,{onClick:()=>d(!c),className:s==="text"?"dropdown-trigger-text":"dropdown-trigger","aria-haspopup":"true","aria-expanded":c,children:[e,s!=="text"&&h.jsx("svg",{className:`inline-block ml-2 w-4 h-4 transition-transform ${c?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),c&&h.jsx(I,{ref:m,className:`dropdown-menu ${_[p]}`,role:"menu","aria-orientation":"vertical",children:h.jsx(I,{className:"py-1",children:t.map((x,M)=>h.jsx(I,{className:`${x.disabled?"dropdown-item-disabled":"dropdown-item"} ${u===M?"dropdown-item-focused":""}`,onClick:()=>b(x),onMouseEnter:()=>!x.disabled&&f(M),role:"menuitem","aria-disabled":x.disabled,tabIndex:x.disabled?-1:0,children:x.label},x.value))})})]})},yy=({imageUrl:e,imageAlt:t="",title:n,subtitle:r,description:s,price:o,originalPrice:i,discountRate:a,currency:l="₩",buttonText:c="자세히 보기",onButtonClick:d,className:u="",style:f,id:g,editorAttrs:m})=>{const p=a&&a>0,y=(o??0).toLocaleString(),b=i?.toLocaleString();return h.jsxs(I,{className:`rounded-lg bg-white dark:bg-gray-800 shadow-md hover:shadow-xl transition-shadow border border-gray-200 dark:border-gray-700 overflow-hidden ${u}`,style:f,id:g,...m,children:[e&&h.jsxs(I,{className:"relative w-full",children:[h.jsx(Sn,{src:e,alt:t||n,className:"w-full h-64 object-cover"}),p&&h.jsx(I,{className:"absolute top-2 right-2 bg-red-500 text-white px-2 py-1 rounded-md",children:h.jsxs(Q,{className:"text-sm font-bold",children:["-",a,"%"]})})]}),h.jsxs(I,{className:"p-4",children:[r&&h.jsx(Xr,{className:"text-xs font-medium text-blue-600 dark:text-blue-400 uppercase tracking-wider mb-2",children:r}),h.jsx(ar,{className:"text-xl font-bold text-gray-900 dark:text-white mb-2 line-clamp-2",children:n}),s&&h.jsx(wt,{className:"text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-3",children:s}),h.jsxs(I,{className:"flex items-center gap-2 mb-4",children:[h.jsxs(Q,{className:"text-2xl font-bold text-gray-900 dark:text-white",children:[l,y]}),i&&h.jsxs(Q,{className:"text-sm text-gray-400 dark:text-gray-500 line-through",children:[l,b]})]}),c&&h.jsx(ie,{onClick:d,className:"w-full bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors",children:c})]})]})},vy=({value:e,label:t,change:n,changeLabel:r="전월 대비",iconName:s,trend:o="neutral",className:i="",style:a,id:l,editorAttrs:c})=>{const d=o==="up",u=o==="down",f=n!==void 0?Math.abs(n):0,g=d?"stats-trend-up":u?"stats-trend-down":"stats-trend-neutral",m=d?ce.ArrowUp:u?ce.ArrowDown:void 0;return h.jsxs(I,{className:`stat-card ${i}`,style:a,id:l,...c,children:[h.jsxs(I,{className:"flex-between mb-md",children:[s&&h.jsx(I,{className:"stats-icon stats-icon-blue",children:h.jsx(se,{name:s,className:"icon-lg"})}),n!==void 0&&h.jsxs(I,{className:`stats-change ${g}`,children:[m&&h.jsx(se,{name:m,className:"icon-sm"}),h.jsxs(Q,{className:"text-sm font-semibold",children:[f,"%"]})]})]}),h.jsx(Xr,{className:"stats-value-lg",children:typeof e=="number"?e.toLocaleString():e}),h.jsx(wt,{className:"stats-label",children:t}),n!==void 0&&r&&h.jsx(I,{className:"stats-sublabel",children:h.jsx(Q,{children:r})})]})},du=()=>window.G7Core,uu=(e,t)=>du()?.t?.(e,t)??e,xy=({name:e="search",placeholder:t,value:n,onChange:r,onSubmit:s,showButton:o=!1,suggestions:i=[],onSuggestionClick:a,showSuggestions:l=!1,className:c="",style:d,id:u,editorAttrs:f})=>{const g=t??uu("common.search_placeholder"),[m,p]=v.useState(""),[y,b]=v.useState(!1),_=v.useRef(null),x=v.useRef(""),M=n!==void 0?n:m,C=l&&y&&i.length>0&&M.length>0,k="w-64 pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm",N=du()?.style?.mergeClasses?.(k,c)??`${k} ${c}`;v.useEffect(()=>{n!==void 0&&(x.current=n)},[n]);const S=D=>{const P=D.target.value;p(P),r?.(D),x.current!==""&&P===""&&!D.nativeEvent.inputType&&setTimeout(()=>{const L=_.current?.querySelector("form");L&&L.requestSubmit()},0),x.current=P},w=D=>{p(D.text);const P={target:{value:D.text,name:e}};r?.(P),a?.(D),b(!1)},E=D=>{D.preventDefault(),s?.(D)};return h.jsxs(I,{ref:_,className:"relative",style:d,id:u,...f,children:[h.jsx(Ya,{onSubmit:E,className:"relative",children:h.jsxs(I,{className:`relative flex items-center ${o?"gap-2":""}`,children:[h.jsxs(I,{className:"relative flex-1",children:[h.jsx(I,{className:"absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",children:h.jsx(se,{name:ce.Search,className:"w-5 h-5 text-gray-400 dark:text-gray-500"})}),h.jsx(_t,{type:"search",name:e,value:M,onChange:S,onFocus:()=>b(!0),onBlur:()=>setTimeout(()=>b(!1),200),placeholder:g,className:N})]}),o&&h.jsx(ie,{type:"submit",className:"px-4 py-2 bg-gray-800 dark:bg-gray-600 text-white rounded-lg hover:bg-gray-900 dark:hover:bg-gray-500 transition-colors text-sm font-medium cursor-pointer h-[42px]",children:uu("common.search")})]})}),C&&h.jsx(I,{className:"absolute z-10 w-full mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-64 overflow-y-auto",children:i.map(D=>h.jsx(I,{className:"px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 transition-colors",onClick:()=>w(D),children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(se,{name:ce.Search,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:D.text})]})},D.id))})]})},fl=(e,t)=>window.G7Core?.t?.(e,t)??e,wy=({title:e,filters:t,onChange:n,onReset:r,showResetButton:s=!0,className:o="",style:i,id:a,editorAttrs:l})=>{const c=e??fl("common.filter"),d=(f,g)=>{n?.(f,g)},u=(f,g,m)=>{const p=t.find(b=>b.id===f);if(!p)return;const y=Array.isArray(p.value)?p.value:[];if(typeof g=="string"){const b=y.filter(x=>typeof x=="string"),_=m?[...b,g]:b.filter(x=>x!==g);n?.(f,_)}else{const b=y.filter(x=>typeof x=="number"),_=m?[...b,g]:b.filter(x=>x!==g);n?.(f,_)}};return h.jsxs(I,{className:`rounded-lg bg-white dark:bg-gray-800 shadow-md border border-gray-200 dark:border-gray-700 p-4 ${o}`,style:i,id:a,...l,children:[h.jsxs(I,{className:"flex items-center justify-between mb-4 pb-3 border-b border-gray-200 dark:border-gray-700",children:[h.jsx(Q,{className:"text-lg font-semibold text-gray-900 dark:text-white",children:c}),s&&r&&h.jsx(ie,{onClick:r,className:"text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium",children:fl("common.reset")})]}),h.jsx(I,{className:"space-y-4",children:t.map(f=>h.jsxs(I,{className:"space-y-2",children:[h.jsx(dn,{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:f.label}),f.type==="select"&&h.jsxs(qr,{value:f.value,onChange:g=>d(f.id,g.target.value),className:"w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-blue-500 dark:focus:border-blue-400 bg-white dark:bg-gray-700 text-gray-900 dark:text-white",children:[h.jsx("option",{value:"",children:fl("common.select_placeholder")}),f.options.map(g=>h.jsx("option",{value:g.value,children:g.label},g.id))]}),f.type==="checkbox"&&h.jsx(I,{className:"space-y-2",children:f.options.map(g=>{const m=Array.isArray(f.value)?f.value.some(p=>p===g.value):!1;return h.jsxs(dn,{className:"flex items-center gap-2 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 p-2 rounded-md transition-colors",children:[h.jsx(_n,{checked:m,onChange:p=>u(f.id,g.value,p.target.checked),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:g.label})]},g.id)})})]},f.id))})]})},ky=({tabs:e,activeTabId:t,onTabChange:n,variant:r="default",className:s="",style:o,mobileBreakpoint:i=768,id:a,editorAttrs:l})=>{const d=window.G7Core?.useResponsive,u=d?.(),f=u?u.width{!k.disabled&&k.id!==t&&n?.(k.id)},p=(k,N)=>{const S=k.key==="ArrowRight"?1:k.key==="ArrowLeft"?-1:0;let w=null;if(S!==0)for(let E=1;E<=e.length;E+=1){const D=(N+S*E+e.length*E)%e.length;if(!e[D]?.disabled){w=D;break}}else if(k.key==="Home")w=e.findIndex(E=>!E.disabled);else if(k.key==="End"){for(let E=e.length-1;E>=0;E-=1)if(!e[E]?.disabled){w=E;break}}w===null||w<0||(k.preventDefault(),g.current[w]?.focus(),m(e[w]))},y=k=>{const N=k.target.value,S=Number(N),w=typeof N=="string"&&N!==""&&!Number.isNaN(S)?S:N,E=e.find(D=>String(D.id)===String(w));E&&m(E)};if(f)return h.jsx(I,{className:s,style:o,id:a,...l,children:h.jsx(qr,{value:t!==void 0?String(t):"",onChange:y,options:e.map(k=>({value:String(k.id),label:k.badge!==void 0?`${k.label} (${k.badge})`:k.label,disabled:k.disabled})),className:"w-full"})});const b=k=>{const N=k.id===t;if(k.disabled)return"tab-btn-base tab-btn-disabled";switch(r){case"pills":return N?"tab-btn-base tab-btn-pills-active":"tab-btn-base tab-btn-pills";case"underline":return N?"tab-btn-base tab-btn-underline-active":"tab-btn-base tab-btn-underline";default:return N?"tab-btn-base tab-btn-default-active":"tab-btn-base tab-btn-default"}},_=r==="underline"?"tab-nav-underline":"tab-nav-default",x=l?.["data-editor-path"],M=e.findIndex(k=>k.id===t),C=M>=0?M:e.findIndex(k=>!k.disabled);return h.jsx(Xo,{className:`${_} ${s}`,style:o,role:"tablist","aria-orientation":"horizontal",id:a,...l,children:e.map((k,N)=>h.jsxs(ie,{ref:S=>{g.current[N]=S},type:"button",role:"tab","aria-selected":k.id===t,tabIndex:N===C?0:-1,onClick:()=>m(k),onKeyDown:S=>p(S,N),disabled:k.disabled,className:b(k),...x?{"data-editor-item-path":`${x}.props.tabs.${N}`}:{},children:[k.iconName&&h.jsx(se,{name:k.iconName,className:"w-4 h-4"}),h.jsx(Q,{children:k.label}),k.badge!==void 0&&h.jsx(I,{className:"flex items-center justify-center min-w-[20px] h-5 px-1.5 bg-red-500 dark:bg-red-600 text-white dark:text-white text-xs font-bold rounded-full",children:h.jsx(Q,{children:k.badge})})]},k.id))})},hl=window.G7Core?.createLogger?.("Handler:ScrollToSection")??{log:(...e)=>console.log("[Handler:ScrollToSection]",...e),warn:(...e)=>console.warn("[Handler:ScrollToSection]",...e),error:(...e)=>console.error("[Handler:ScrollToSection]",...e)};async function fu(e,t){const n=e.params||{},{targetId:r,offset:s=120,delay:o=100,scrollContainerId:i}=n;if(!r){hl.warn("targetId is required");return}const a=await Cy(r,o);if(!a){hl.warn(`Element not found: ${r}`);return}const l=Sy(a,i),c=_y(a,l,s);Ey(l,c)}async function Cy(e,t){await new Promise(s=>setTimeout(s,t));const n=10,r=50;for(let s=0;ssetTimeout(i,50)),o;await new Promise(i=>setTimeout(i,r))}return null}function Sy(e,t){if(t){const o=document.getElementById(t);if(o&&hu(o))return o;hl.warn(`Container '${t}' ${o?"has no scroll":"not found"}, trying auto-detect`)}let n=e.parentElement,r=0;const s=10;for(;n&&n!==document.body&&re.clientHeight}function _y(e,t,n){if(t===window||!("getBoundingClientRect"in t))return e.getBoundingClientRect().top+window.pageYOffset-n;{const r=t.getBoundingClientRect(),s=e.getBoundingClientRect();return t.scrollTop+(s.top-r.top)-n}}function Ey(e,t){e===window||!("scrollTo"in e)?window.scrollTo({top:t,behavior:"smooth"}):e.scrollTo({top:t,behavior:"smooth"})}window.G7Core?.createLogger?.("Comp:TabNavigation");const My=({tabs:e,activeTabId:t,className:n="bg-white dark:bg-gray-800 flex gap-2 border-b border-gray-200 dark:border-gray-700 px-6",style:r,activeClassName:s="tab-btn-base tab-btn-default-active",inactiveClassName:o="tab-btn-base tab-btn-default",sectionIdPrefix:i="tab_content_",scrollOffset:a=120,scrollDelay:l=100,onTabChange:c,enableScrollSpy:d=!1,scrollSpyOffset:u=80,scrollContainerId:f,id:g,editorAttrs:m})=>{const[p,y]=v.useState(t??(e.length>0?e[0].id:"")),_=window.G7Core?.useResponsive,x=_?.(),M=x?x.width<768:typeof window<"u"&&window.innerWidth<768,C=v.useRef(!1),k=v.useRef(null),N=v.useRef([]),S=L=>{if(d&&(C.current=!0,k.current&&clearTimeout(k.current)),L.onClick){if(L.onClick(L.id),!d)return}else if(c){if(c(L.id),!d)return}else y(L.id);const $=L.scrollOffset??a,A=L.scrollDelay??l;fu({params:{targetId:`${i}${L.id}`,offset:$,delay:A,scrollContainerId:f}}),d&&(k.current=setTimeout(()=>{C.current=!1},A+500))},w=(L,$)=>{const A=L.key==="ArrowRight"?1:L.key==="ArrowLeft"?-1:0;let O=null;if(A!==0)for(let T=1;T<=e.length;T+=1){const z=($+A*T+e.length*T)%e.length;if(!e[z]?.disabled){O=z;break}}else if(L.key==="Home")O=e.findIndex(T=>!T.disabled);else if(L.key==="End"){for(let T=e.length-1;T>=0;T-=1)if(!e[T]?.disabled){O=T;break}}O===null||O<0||(L.preventDefault(),N.current[O]?.focus(),S(e[O]))};v.useEffect(()=>{if(!d)return;const L=f?document.getElementById(f):null,$={root:L??null,rootMargin:`-${u}px 0px -30% 0px`,threshold:[0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1]},A=new Map,O=()=>{const j=L,G=j?j.scrollTop:window.pageYOffset||document.documentElement.scrollTop,U=j?j.scrollHeight:document.documentElement.scrollHeight,W=j?j.clientHeight:document.documentElement.clientHeight;return G+W>=U-10},T=j=>{if(C.current)return;j.forEach(X=>{const K=X.target.id;X.isIntersecting?A.set(K,X.intersectionRatio):A.delete(K)});let G=0,U="";A.forEach((X,K)=>{X>G&&(G=X,U=K)});const W=e[e.length-1],Y=`${i}${W?.id}`,V=A.has(Y);if(O()&&V&&W){y(W.id);return}if(U){const X=U.replace(i,""),K=Number(X),q=isNaN(K)?X:K;e.some(ee=>ee.id===q)&&y(q)}},z=()=>{},H=new IntersectionObserver(T,$);return e.forEach(j=>{const G=document.getElementById(`${i}${j.id}`);G&&H.observe(G)}),window.addEventListener("scroll",z,{passive:!0}),()=>{H.disconnect(),window.removeEventListener("scroll",z),k.current&&clearTimeout(k.current)}},[d,e,i,u,f]);const E=L=>{const $=L.target.value,A=Number($),O=isNaN(A)?$:A,T=e.find(z=>z.id===O);T&&S(T)};if(M)return h.jsx(I,{className:"bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3",id:g,...m,children:h.jsx(qr,{value:String(p),onChange:E,options:e.map(L=>({value:String(L.id),label:L.label,disabled:L.disabled})),className:"w-full"})});const D=e.findIndex(L=>L.id===p),P=D>=0?D:e.findIndex(L=>!L.disabled);return h.jsx(I,{className:n,style:r,role:"tablist","aria-orientation":"horizontal",id:g,...m,children:e.map((L,$)=>h.jsxs(ie,{ref:A=>{N.current[$]=A},type:"button",role:"tab","aria-selected":p===L.id,tabIndex:$===P?0:-1,className:p===L.id?s:o,disabled:L.disabled,onClick:()=>S(L),onKeyDown:A=>w(A,$),children:[L.iconName&&h.jsx(se,{name:L.iconName,size:"sm"}),h.jsx(Q,{children:L.label})]},L.id))})},Ny=()=>window.G7Core,Ty=({iconName:e,label:t,onClick:n,variant:r="primary",size:s="md",disabled:o=!1,className:i="",style:a,id:l,editorAttrs:c})=>{const d={sm:t?"px-3 py-1.5 text-sm":"w-8 h-8",md:t?"px-4 py-2 text-base":"w-10 h-10",lg:t?"px-5 py-2.5 text-lg":"w-12 h-12"},u={sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6"},f={primary:"bg-blue-600 hover:bg-blue-700 text-white border-transparent",secondary:"bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600",danger:"bg-red-600 hover:bg-red-700 text-white border-transparent",ghost:"bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-transparent",outline:"bg-transparent hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600"},g="inline-flex items-center justify-center gap-2 font-medium rounded-lg border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-400",m=o?"opacity-50 cursor-not-allowed":"",p=`${g} ${d[s]} ${f[r]} ${m}`,y=Ny()?.style?.mergeClasses?.(p,i)??`${p} ${i}`;return h.jsxs(ie,{onClick:n,disabled:o,className:y,style:a,id:l,...c,children:[h.jsx(se,{name:e,className:u[s]}),t]})},Oy=(e,t)=>window.G7Core?.t?.(e,t)??e,gu=({isOpen:e,onClose:t,title:n,content:r,actions:s=[],width:o="500px",closeOnOverlay:i=!0,showCloseButton:a=!0,className:l="",style:c})=>{const d=()=>{i&&t()},u={primary:"dialog-btn-primary",secondary:"dialog-btn-secondary",danger:"dialog-btn-danger",ghost:"dialog-btn-ghost"};return h.jsxs(Jo,{isOpen:e,onClose:d,width:o,className:l,style:c,children:[h.jsxs(I,{className:"dialog-header",children:[h.jsx(ar,{className:"dialog-title",children:n}),a&&h.jsx(ie,{onClick:t,className:"dialog-close","aria-label":Oy("common.close_dialog"),children:h.jsx(se,{name:ce.Times,className:"icon-md"})})]}),h.jsx(I,{className:"dialog-body",children:r}),s.length>0&&h.jsx(I,{className:"dialog-footer",children:s.map((f,g)=>h.jsxs(ie,{onClick:f.onClick,disabled:f.disabled,className:u[f.variant||"primary"],children:[f.iconName&&h.jsx(se,{name:f.iconName,className:"icon-sm mr-2 inline-block"}),f.label]},g))})]})},gl=(e,t)=>window.G7Core?.t?.(e,t)??e,pu=({isOpen:e,onClose:t,title:n,message:r,confirmText:s,cancelText:o,onConfirm:i,onCancel:a,confirmButtonVariant:l="primary",cancelButtonVariant:c="secondary",width:d="500px",closeOnOverlay:u=!0,isLoading:f=!1,loadingText:g})=>{const m=s??gl("common.confirm"),p=o??gl("common.cancel"),y=g??gl("common.loading"),b=()=>{i()},_=()=>{f||(a?.(),t())},x=()=>{f||t()},M={primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600",danger:"bg-red-600 text-white hover:bg-red-700",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"};return h.jsxs(Jo,{isOpen:e,onClose:x,title:n,width:d,children:[h.jsx(I,{className:"py-2",children:typeof r=="string"?h.jsx(wt,{className:"text-gray-700 dark:text-gray-300",children:r}):r}),h.jsxs(I,{className:"flex justify-end gap-3 mt-6",children:[h.jsx(ie,{onClick:_,disabled:f,className:"px-4 py-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed",children:h.jsx(Q,{children:p})}),h.jsxs(ie,{onClick:b,disabled:f,className:`flex items-center gap-2 px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed ${M[l]}`,children:[f&&h.jsx(se,{name:ce.Spinner,className:"w-4 h-4 animate-spin"}),h.jsx(Q,{children:f?y:m})]})]})]})},Ay=(e,t)=>window.G7Core?.t?.(e,t)??e,Dy=({isOpen:e,onClose:t,title:n,message:r,confirmText:s,onConfirm:o,confirmButtonVariant:i="primary",width:a="500px",closeOnOverlay:l=!0})=>{const u=[{label:s??Ay("common.confirm"),onClick:()=>{o?.(),t()},variant:i}],f=typeof r=="string"?h.jsx(wt,{className:"text-gray-700 dark:text-gray-300",children:r}):r;return h.jsx(gu,{isOpen:e,onClose:t,title:n,content:f,actions:u,width:a,closeOnOverlay:l})},Iy=(e,t)=>window.G7Core?.t?.(e,t)??e,Ly={success:ce.CheckCircle,error:ce.XCircle,warning:ce.ExclamationTriangle,info:ce.InfoCircle},mu={success:{bg:"bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",text:"text-green-800 dark:text-green-300"},error:{bg:"bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",text:"text-red-800 dark:text-red-300"},warning:{bg:"bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",text:"text-yellow-800 dark:text-yellow-300"},info:{bg:"bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",text:"text-blue-800 dark:text-blue-300"}},Py={"top-left":"top-4 left-4","top-center":"top-4 left-1/2 -translate-x-1/2","top-right":"top-4 right-4","bottom-left":"bottom-4 left-4","bottom-center":"bottom-4 left-1/2 -translate-x-1/2","bottom-right":"bottom-4 right-4"},$y=({toasts:e,position:t="bottom-center",duration:n=3e3,onRemove:r})=>{const[s,o]=v.useState(new Map),i=v.useCallback(a=>{o(l=>{const c=new Map(l);return c.set(a,!1),c}),setTimeout(()=>{if(r)r(a);else{const l=window.G7Core;l?.state?.update&&l.state.update(c=>({toasts:(Array.isArray(c.toasts)?c.toasts:[]).filter(u=>u.id!==a)}))}},300)},[r]);return v.useEffect(()=>{!e||e.length===0||e.forEach(a=>{if(s.has(a.id))return;o(c=>{const d=new Map(c);return d.set(a.id,!0),d});const l=a.duration||n;l>0&&setTimeout(()=>{i(a.id)},l)})},[e,n,i,s]),!e||e.length===0?null:h.jsx(I,{className:`fixed z-[9999] flex flex-col gap-2 ${Py[t]}`,"aria-live":"polite","aria-atomic":"true",children:e.map(a=>h.jsx(Fy,{toast:a,position:t,isVisible:s.get(a.id)??!0,onClose:()=>i(a.id)},a.id))})},Fy=({toast:e,position:t,isVisible:n,onClose:r})=>{const s=mu[e.type]||mu.info,o=e.icon?e.icon:Ly[e.type],i=t.startsWith("bottom")?"translate-y-2":"-translate-y-2";return h.jsxs(I,{className:` + `.trim().replace(/\s+/g," ");return h.jsx(I,{className:d,dangerouslySetInnerHTML:{__html:c},id:o,...i})},Uo=window.G7Core?.createLogger?.("Comp:DataGrid")??{log:(...t)=>console.log("[Comp:DataGrid]",...t),warn:(...t)=>console.warn("[Comp:DataGrid]",...t),error:(...t)=>console.error("[Comp:DataGrid]",...t)},Ss=(t,e)=>window.G7Core?.t?.(t,e)??t,Qa={Div:I,Span:Q,Button:oe,Icon:re,Checkbox:vn,P:yt,A:Lr,Label:sn,Input:kt,H1:Da,H2:Jn,H3:Ir,H4:Nd,Img:yn,Select:Pr,Textarea:Qn,Table:$a,Thead:Fa,Tbody:za,Tfoot:Va,Tr:Rn,Th:$r,Td:Zt,MultilingualInput:Ha,Toggle:Ua,StatusBadge:Fo,Badge:Pd,HtmlContent:Za,ActionMenu:ys},Ja=t=>{if(t==null)return"";if(typeof t=="string"||typeof t=="number"||typeof t=="boolean")return String(t);if(Array.isArray(t))return t.map(e=>Ja(e)).join(", ");if(typeof t=="object"){if(v.isValidElement(t))return t;if(Object.keys(t).length===0)return"";try{return JSON.stringify(t)}catch{return Uo.warn("객체를 문자열로 변환할 수 없습니다:",t),"[Object]"}}return String(t)},qd=(t,e)=>{if(!t)return!0;const n=t.match(/^\{\{(.+)\}\}$/);if(!n)return!0;const r=n[1].trim();try{return!!new Function("row",`return ${r}`)(e)}catch(s){return Uo.warn("subRow 조건 평가 실패:",t,s),!0}},_s=(t,e,n,r="",s)=>{const o=window.G7Core;if(o?.renderItemChildren){const i={row:e,value:n,$value:n};return o.renderItemChildren(t,i,Qa,r,s?{componentContext:s}:void 0)}return Uo.warn("G7Core.renderItemChildren을 사용할 수 없습니다."),null},Ra=(t,e,n="",r)=>{const s=window.G7Core;if(s?.renderItemChildren){const o={row:e,$value:e};return s.renderItemChildren(t,o,Qa,n,r?{componentContext:r}:void 0)}return Uo.warn("G7Core.renderItemChildren을 사용할 수 없습니다."),null},jb=({id:t,columns:e,data:n,sortable:r=!0,pagination:s=!1,pageSize:o=10,onRowClick:i,className:a="",style:l,sortField:c,sortDirection:d,onSortChange:u,selectable:f=!1,selectedIds:g=[],onSelectionChange:m,idField:p="id",showColumnSelector:y=!1,visibleColumns:b,onColumnVisibilityChange:_,responsiveBreakpoint:x=768,cardRenderer:E,rowActions:C,onRowAction:k,alwaysShowPagination:N=!1,serverSidePagination:S=!1,serverCurrentPage:w,serverTotalPages:M,onPageChange:T,prevText:P,nextText:L,columnSelectorText:$,emptyMessage:A,actionsColumnHeader:D,requiredText:O,selectedCountText:z,showFirstLast:H=!0,expandable:j=!1,expandedRowIds:W,onExpandChange:U,expandChildren:B,expandedRowRender:G,expandColumnWidth:V="48px",expandIconExpanded:K=ce.ChevronDown,expandIconCollapsed:X=ce.ChevronRight,expandConditionField:Z,dynamicColumns:ee,dynamicColumnsInsertAfter:ne,expandContext:he,__componentContext:J,subRowChildren:le,subRowCondition:ue,subRowClassName:ae="px-6 py-2 bg-gray-50 dark:bg-gray-900",subRowRender:ve,footerCells:fe,footerClassName:ye="bg-gray-50 dark:bg-gray-800/50 font-medium",footerCardChildren:we,editorAttrs:He})=>{const se=P||void 0,Oe=L||void 0,Le=$??Ss("common.column_selector"),Ie=A??Ss("common.no_data"),Be=D??Ss("common.actions"),st=O??Ss("common.required"),ut=z??Ss("common.selected_count"),je=window.G7Core,nt=je?.useControllableState,Ye=je?.shallowArrayEqual,[te,ge]=nt?nt(c,null):[c??null,()=>{}],[me,pe]=nt?nt(d,"asc"):[d??"asc",()=>{}],[be,Ue]=v.useState(1),[Ce,ze]=nt?nt(W,[],U,{isEqual:Ye}):[W??[],()=>{}],[Se,Re]=v.useState(!1),ot=v.useRef(null),Pe=je?.useResponsive,Te=Pe?.(),Ve=Te?Te.width{if(!ee||!Array.isArray(ee)||ee.length===0)return e;if(ne){const q=e.findIndex(de=>de.field===ne);if(q!==-1)return[...e.slice(0,q+1),...ee,...e.slice(q+1)]}return[...e,...ee]},[e,ee,ne]),[qe,it]=v.useState(()=>b&&b.length>0?b:e.filter(q=>!q.hidden).map(q=>q.field));v.useEffect(()=>{b&&b.length>0&&it(b)},[b]);const pt=v.useRef("");v.useEffect(()=>{const q=e.map(de=>de.field).join(",");pt.current&&q!==pt.current&&(!b||b.length===0)&&it(e.filter(de=>!de.hidden).map(de=>de.field)),pt.current=q},[e,b]);const rn=v.useRef([]),bt=v.useRef(null);v.useEffect(()=>{if(ee&&ee.length>0){const q=ee.map(Ze=>Ze.field);if(bt.current===null&&(bt.current=b!==void 0&&b.length>0,bt.current)){rn.current=q;return}const de=rn.current,Ee=q.filter(Ze=>!de.includes(Ze));if(Ee.length>0){const Ze=Ee.filter(Fe=>!qe.includes(Fe));if(Ze.length>0){const Fe=[...qe,...Ze];it(Fe),_?.(Fe)}}rn.current=q}},[ee]),v.useEffect(()=>{S||Ue(1)},[n,S]),v.useEffect(()=>{const q=de=>{ot.current&&!ot.current.contains(de.target)&&Re(!1)};return Se&&document.addEventListener("mousedown",q),()=>document.removeEventListener("mousedown",q)},[Se]);const wt=v.useMemo(()=>!y&&(!b||b.length===0)?De.filter(q=>!q.hidden):De.filter(q=>qe.includes(q.field)),[De,qe,y,b]),jt=r&&!(S&&!u),ft=v.useMemo(()=>!te||!r||!n?n||[]:u||S?n:[...n].sort((q,de)=>{const Ee=q[te],Ze=de[te];if(Ee===Ze)return 0;const Fe=Ee>Ze?1:-1;return me==="asc"?Fe:-Fe}),[n,te,me,r,u,S]),Xe=v.useMemo(()=>{if(S||!s)return ft;const q=(be-1)*o,de=q+o;return ft.slice(q,de)},[ft,be,o,s,S]),Nn=S?w||1:be,Wt=S?M||1:Math.ceil((ft?.length||0)/o),bn=v.useCallback(q=>{S&&T?T(q):Ue(q)},[S,T]),hr=q=>{if(!jt||e.find(Ze=>Ze.field===q)?.sortable===!1)return;const Ee=te===q&&me==="asc"?"desc":"asc";ge(q),pe(Ee),u?.(q,Ee)},On=v.useCallback(()=>{if(!m||!Xe)return;const q=Xe.map(Ee=>Ee[p]);if(q.every(Ee=>g.includes(Ee)))m(g.filter(Ee=>!q.includes(Ee)));else{const Ee=[...new Set([...g,...q])];m(Ee)}},[Xe,g,m,p]),Ao=v.useCallback(q=>{m&&(g.includes(q)?m(g.filter(de=>de!==q)):m([...g,q]))},[g,m]),Do=v.useCallback(q=>{if(De.find(Ze=>Ze.field===q)?.required)return;const Ee=qe.includes(q)?qe.filter(Ze=>Ze!==q):[...qe,q];it(Ee),_?.(Ee)},[De,qe,_]),Io=v.useCallback((q,de)=>{k?.(q,de)},[k]),Lo=v.useCallback(q=>C?C.map(de=>{let Ee=de.disabled??!1;if(de.disabledField&&!Ee){const Ze=de.disabledField.split(".");let Fe=q;for(const Nt of Ze)Fe=Fe?.[Nt];Fe||(Ee=!0)}return{...de,disabled:Ee,onClick:()=>Io(de.id,q)}}):[],[C,Io]),bs=v.useCallback(q=>{if(!Z)return!0;const de=q[Z];return Array.isArray(de)?de.length>0:!!de},[Z]),ke=v.useCallback(q=>{const de=Ce.includes(q)?Ce.filter(Ee=>Ee!==q):[...Ce,q];nt?ze(de):U&&U(de)},[Ce,U,nt,ze]),Y=v.useCallback(q=>Ce.includes(q),[Ce]),ie=v.useCallback(q=>G?G(q):B&&B.length>0&&je?.renderExpandContent?je.renderExpandContent({children:B,row:q,expandContext:he,componentContext:J,componentMap:Qa,keyPrefix:`expand-${q[p]}`}):null,[G,B,p,he,J,J?.state]),xe=v.useMemo(()=>!Xe||Xe.length===0?!1:Xe.every(q=>g.includes(q[p])),[Xe,g,p]),ht=v.useMemo(()=>{if(!Xe||Xe.length===0)return!1;const q=Xe.filter(de=>g.includes(de[p])).length;return q>0&&q{const Ee=q[p],Ze=Y(Ee);return h.jsxs(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 mb-4",children:[h.jsxs(I,{className:"flex items-start justify-between",children:[j&&bs(q)&&h.jsx(oe,{onClick:()=>ke(Ee),className:"p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors mr-2","aria-label":Ze?"Collapse card":"Expand card",children:h.jsx(re,{name:Ze?K:X,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})}),f&&h.jsx(vn,{checked:g.includes(q[p]),onChange:()=>Ao(q[p]),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded mr-3 mt-1"}),h.jsx(I,{className:"flex-1",children:h.jsx(I,{className:"grid grid-cols-2 gap-x-4 gap-y-2",children:de.map(Fe=>h.jsxs(I,{className:"min-w-0",children:[h.jsx(Q,{className:"text-xs font-medium text-gray-500 dark:text-gray-400 uppercase",children:Fe.header}),h.jsx(I,{className:"text-sm text-gray-900 dark:text-gray-200 mt-0.5 break-words",children:Fe.cellChildren&&Fe.cellChildren.length>0?_s(Fe.cellChildren,q,q[Fe.field],`card-${q[p]}-${Fe.field}`,J):Fe.render?Fe.render(q[Fe.field],q):Ja(q[Fe.field])})]},Fe.field))})}),C&&C.length>0&&h.jsx(I,{className:"ml-2",children:h.jsx(ys,{items:Lo(q),triggerLabel:"",triggerIconName:ce.EllipsisHorizontal})})]}),j&&Ze&&bs(q)&&h.jsx(I,{className:"mt-4 pt-4 border-t border-gray-200 dark:border-gray-700",children:ie(q)}),(le&&le.length>0||ve)&&qd(ue,q)&&h.jsx(I,{className:ae?`mt-3 ${ae}`:"mt-3 pt-3 border-t border-gray-100 dark:border-gray-700",children:ve?ve(q):Ra(le,q,`card-subrow-${Ee}`,J)})]})};return Ve?h.jsxs(I,{id:t,className:`w-full ${a}`,style:l,...He,children:[y&&h.jsx(I,{className:"mb-4 flex justify-end",children:h.jsxs(I,{ref:ot,className:"relative",children:[h.jsxs(oe,{onClick:()=>Re(!Se),className:"flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700",children:[h.jsx(re,{name:ce.Cog,className:"w-4 h-4 text-gray-500 dark:text-gray-400"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:Le})]}),Se&&h.jsx(I,{className:"absolute right-0 z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",children:h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:De.map(q=>h.jsxs(I,{className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${q.required?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:()=>!q.required&&Do(q.field),children:[h.jsx(vn,{checked:qe.includes(q.field),disabled:q.required,onChange:()=>{},className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"}),h.jsxs(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:[q.header,q.required&&h.jsxs(Q,{className:"text-xs text-gray-500 dark:text-gray-400 ml-1",children:["(",st,")"]})]})]},q.field))})})]})}),f&&g.length>0&&ut&&h.jsx(I,{className:"mb-4 text-sm text-gray-600 dark:text-gray-400",children:ut.replace("{count}",String(g.length))}),h.jsx(I,{id:t?`${t}__body`:void 0,children:Xe&&Xe.length>0?Xe.map((q,de)=>h.jsx(I,{children:E?E(q,wt):Lt(q,wt)},q[p]||de)):h.jsx(I,{className:"text-center py-8 text-gray-500 dark:text-gray-400",children:Ie})}),fe&&fe.length>0&&Xe&&Xe.length>0&&h.jsx(I,{className:`rounded-lg p-4 mt-2 ${ye}`,children:we&&we.length>0?_s(we,{},null,"footer-card",J):h.jsx(I,{className:"space-y-1",children:fe.filter(q=>q.text||q.children&&q.children.length>0).map((q,de)=>{const Ee=wt.find(Ze=>Ze.field===q.field);return h.jsxs(I,{className:"flex justify-between text-sm",children:[h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:Ee?.header||""}),h.jsx(Q,{className:q.className||"text-gray-900 dark:text-gray-100",children:q.children&&q.children.length>0?_s(q.children,{},null,`footer-mobile-${de}`,J):q.text||""})]},`footer-mobile-${de}`)})})}),s&&(N||Wt>1)&&h.jsx(I,{className:"flex justify-center mt-4",children:h.jsx($o,{currentPage:Nn,totalPages:Wt,onPageChange:bn,showFirstLast:H,maxVisiblePages:5,prevText:se,nextText:Oe})})]}):h.jsxs(I,{id:t,className:`w-full ${a}`,style:l,...He,children:[y&&h.jsx(I,{className:"mb-4 flex justify-end",children:h.jsxs(I,{ref:ot,className:"relative",children:[h.jsxs(oe,{onClick:()=>Re(!Se),className:"flex items-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700",children:[h.jsx(re,{name:ce.Cog,className:"w-4 h-4 text-gray-500 dark:text-gray-400"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:Le})]}),Se&&h.jsx(I,{className:"absolute right-0 z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",children:h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:De.map(q=>h.jsxs(I,{className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${q.required?"opacity-50 cursor-not-allowed":"cursor-pointer"}`,onClick:()=>!q.required&&Do(q.field),children:[h.jsx(vn,{checked:qe.includes(q.field),disabled:q.required,onChange:()=>{},className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"}),h.jsxs(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:[q.header,q.required&&h.jsxs(Q,{className:"text-xs text-gray-500 dark:text-gray-400 ml-1",children:["(",st,")"]})]})]},q.field))})})]})}),f&&g.length>0&&ut&&h.jsx(I,{className:"mb-4 text-sm text-gray-600 dark:text-gray-400",children:ut.replace("{count}",String(g.length))}),h.jsx(I,{id:t?`${t}__body`:void 0,className:"overflow-x-auto border border-gray-200 dark:border-gray-700 rounded-lg",children:h.jsxs($a,{className:"w-full",children:[h.jsx(Fa,{className:"bg-gray-50 dark:bg-gray-700",children:h.jsxs(Rn,{children:[j&&h.jsx($r,{className:"px-2 py-3",style:{width:V}}),f&&h.jsx($r,{className:"px-4 py-3 w-10",children:h.jsx(vn,{checked:xe,ref:q=>{q&&(q.indeterminate=ht)},onChange:On,className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"})}),wt.map(q=>h.jsxs($r,{className:`px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider ${jt&&q.sortable!==!1?"cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600":""}`,style:{width:q.width,minWidth:q.width},onClick:jt?()=>hr(q.field):void 0,children:[q.header,te===q.field&&h.jsx(Q,{className:"ml-1",children:me==="asc"?"↑":"↓"})]},q.field)),C&&C.length>0&&h.jsx($r,{className:"px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider w-24",children:Be})]})}),h.jsx(za,{className:`bg-white dark:bg-gray-800 ${ae?"":"divide-y divide-gray-200 dark:divide-gray-700"}`,children:Xe&&Xe.length>0?Xe.map((q,de)=>{const Ee=q[p]||de,Ze=Y(Ee);return h.jsxs(v.Fragment,{children:[h.jsxs(Rn,{className:`${i?"cursor-pointer":""} hover:bg-gray-50 dark:hover:bg-gray-700 ${g.includes(q[p])?"bg-blue-50 dark:bg-blue-900/20":""} ${ae&&de>0?"border-t border-gray-200 dark:border-gray-700":""}`,onClick:()=>i?.(q),children:[j&&h.jsx(Zt,{className:"px-2 py-4 text-center",onClick:Fe=>Fe.stopPropagation(),children:bs(q)?h.jsx(oe,{onClick:()=>ke(Ee),className:"p-1.5 rounded-md hover:bg-gray-100 dark:hover:bg-gray-600 transition-colors","aria-label":Ze?"Collapse row":"Expand row",children:h.jsx(re,{name:Ze?K:X,className:"w-4 h-4 text-gray-500 dark:text-gray-400"})}):null}),f&&h.jsx(Zt,{className:"px-4 py-4",onClick:Fe=>Fe.stopPropagation(),children:h.jsx(vn,{checked:g.includes(q[p]),onChange:()=>Ao(q[p]),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded"})}),wt.map(Fe=>h.jsx(Zt,{className:"px-6 py-4 align-top whitespace-nowrap text-sm text-gray-900 dark:text-gray-200",style:{width:Fe.width,minWidth:Fe.width},children:Fe.cellChildren&&Fe.cellChildren.length>0?_s(Fe.cellChildren,q,q[Fe.field],`${q[p]}-${Fe.field}`,J):Fe.render?Fe.render(q[Fe.field],q):Ja(q[Fe.field])},Fe.field)),C&&C.length>0&&h.jsx(Zt,{className:"px-6 py-4 text-right",onClick:Fe=>Fe.stopPropagation(),children:h.jsx(ys,{items:Lo(q),triggerLabel:"",triggerIconName:ce.EllipsisHorizontal,position:"right"})})]}),j&&Ze&&bs(q)&&h.jsx(Rn,{className:"bg-gray-50 dark:bg-gray-900",children:h.jsx(Zt,{colSpan:wt.length+1+(f?1:0)+(C&&C.length>0?1:0),className:"px-6 py-4",children:ie(q)})}),(le&&le.length>0||ve)&&qd(ue,q)&&h.jsx(Rn,{className:ae?"!border-t-0":"",children:h.jsx(Zt,{colSpan:wt.length+(j?1:0)+(f?1:0)+(C&&C.length>0?1:0),className:ae?"p-2":ae||"px-6 py-2 bg-gray-50 dark:bg-gray-900",children:ae?h.jsx(I,{className:ae,children:ve?ve(q):Ra(le,q,`subrow-${Ee}`,J)}):ve?ve(q):Ra(le,q,`subrow-${Ee}`,J)})})]},Ee)}):h.jsx(Rn,{children:h.jsx(Zt,{colSpan:wt.length+(j?1:0)+(f?1:0)+(C&&C.length>0?1:0),className:"px-6 py-8 text-center text-gray-500 dark:text-gray-400",children:Ie})})}),fe&&fe.length>0&&Xe&&Xe.length>0&&h.jsx(Va,{children:h.jsxs(Rn,{className:ye,children:[j&&h.jsx(Zt,{className:"px-2 py-4"}),f&&h.jsx(Zt,{className:"px-4 py-4"}),(()=>{const q=new Map(fe.filter(de=>de.field).map(de=>[de.field,de]));return wt.map(de=>{const Ee=q.get(de.field);return Ee?h.jsx(Zt,{className:`px-6 py-4 text-sm text-gray-900 dark:text-gray-200 ${Ee.className||""}`,colSpan:Ee.colSpan,style:{width:de.width,minWidth:de.width},children:Ee.children&&Ee.children.length>0?_s(Ee.children,{},null,`footer-${de.field}`,J):Ee.text||""},`footer-${de.field}`):h.jsx(Zt,{className:"px-6 py-4",style:{width:de.width,minWidth:de.width}},`footer-${de.field}`)})})(),C&&C.length>0&&h.jsx(Zt,{className:"px-6 py-4"})]})})]})}),s&&(N||Wt>1)&&h.jsx(I,{className:"flex justify-center mt-4",children:h.jsx($o,{currentPage:Nn,totalPages:Wt,onPageChange:bn,showFirstLast:H,maxVisiblePages:5,prevText:se,nextText:Oe})})]})},el=window.G7Core?.createLogger?.("Comp:CardGrid")??{log:(...t)=>console.log("[Comp:CardGrid]",...t),warn:(...t)=>console.warn("[Comp:CardGrid]",...t),error:(...t)=>console.error("[Comp:CardGrid]",...t)},Wb=(t,e)=>window.G7Core?.t?.(t,e)??t,Gb=()=>window.G7Core?.getComponentMap?.()||{},Yb=(t,e)=>{if(!t)return!0;const n=t.match(/^\{\{(.+)\}\}$/);if(!n)return!0;const r=n[1].trim();if(el.log("evaluateCondition:",{condition:t,expr:r,row:e,rowStatus:e?.status}),/[&|<>=!]/.test(r)||r.includes(".length"))try{const o=!!new Function("row",`return ${r}`)(e);return el.log("evaluateCondition result:",o),o}catch(s){return el.warn("evaluateCondition: 표현식 평가 실패:",r,s),!1}if(r.startsWith("!row.")){const s=r.slice(5);return!e[s]}if(r.startsWith("row.")){const s=r.slice(4);return!!e[s]}return!0},Zd=(t,e,n,r,s)=>{const o=window.G7Core;if(t.name==="ActionMenu"){if(r&&r.length>0&&s)return{...t,props:{...t.props,items:r.map(i=>({...i,onClick:()=>s(i.id,e)}))}};if(t.actions&&Array.isArray(t.actions)){const i=t.props?.items||[],a=new Map(t.actions.map(c=>[c.id,c])),l=i.map(c=>{if(typeof c.onClick=="string"){const d=c.onClick,u=a.get(d);if(u&&o?.ActionDispatcher)return{...c,onClick:()=>{const f={...n};o.ActionDispatcher.dispatch(u,f)}}}return c});return{...t,props:{...t.props,items:l}}}}return t.children&&Array.isArray(t.children)?{...t,children:t.children.map(i=>Zd(i,e,n,r,s))}:t},Xb=(t,e,n,r="",s,o)=>{const i=window.G7Core;if(i?.renderItemChildren){const a=t.filter(d=>{const u=d.condition||d.if;return u?Yb(u,e):!0}),l={row:e,$value:e,index:n},c=a.map(d=>Zd(d,e,l,s,o));try{const d=Gb();return i.renderItemChildren(c,l,d,r)}catch{return h.jsxs(I,{className:"text-red-600 dark:text-red-400 p-4",children:[h.jsx(yt,{children:"컴포넌트 로드 실패"}),h.jsx(yt,{className:"text-sm",children:"[CardGrid] 데이터를 표시할 수 없습니다."})]})}}return null},tl=(t,e)=>{if(t.name==="Div"&&t.children&&t.children.length>0){const n=t.children.map((r,s)=>tl(r,`${e}-child-${s}`)).filter(Boolean);return h.jsx(I,{className:t.props?.className||"",children:n},e)}return h.jsx(I,{className:"h-4 bg-gray-200 dark:bg-gray-700 rounded w-full animate-pulse"},e)},Qd=(t,e="skeleton")=>!t||t.length===0?null:t.map((n,r)=>{const s=`${e}-${n.id||n.name}-${r}`;return n.condition||n.if?null:(n.iteration,tl(n,s))}).filter(Boolean),Kb=({data:t,cardColumns:e=[],gridColumns:n=3,gap:r=4,responsiveColumns:s,cardClassName:o="",idField:i="id",pagination:a=!0,pageSize:l=12,serverSidePagination:c=!1,serverCurrentPage:d,serverTotalPages:u,alwaysShowPagination:f=!1,onPageChange:g,paginationInfoText:m,rowActions:p,onRowAction:y,className:b="",style:_,emptyMessage:x,prevText:E,nextText:C,showSkeleton:k=!0,skeletonCount:N,skeletonCellChildren:S,id:w,editorAttrs:M})=>{const T=x??Wb("common.no_data"),P=e[0]?.cellChildren||[],[L,$]=v.useState(1);window.G7Core?.useResponsive?.();const D=c?d??1:L,O=c?u??1:Math.ceil((t?.length??0)/l),z=G=>{c?g?.(G):$(G)},H=v.useCallback((G,V)=>{y?.(G,V)},[y]),j=v.useMemo(()=>{if(!t)return[];if(!a||c)return t;const G=(L-1)*l,V=G+l;return t.slice(G,V)},[t,a,c,L,l]),W=v.useMemo(()=>{const G=[],V=s?.sm??1;G.push(`grid-cols-${V}`),s?.sm&&G.push(`sm:grid-cols-${s.sm}`);const K=s?.md??Math.min(n,2);G.push(`md:grid-cols-${K}`);const X=s?.lg??n;return G.push(`lg:grid-cols-${X}`),s?.xl&&G.push(`xl:grid-cols-${s.xl}`),G.join(" ")},[n,s]),U=v.useMemo(()=>k?S&&S.length>0?Qd(S,"custom-skeleton"):P&&P.length>0?Qd(P,"auto-skeleton"):null:null,[k,S,P]);if(!t){if(!k)return null;const G=N??n;return h.jsx(I,{className:`grid ${W} gap-${r} ${b}`,style:_,id:w,...M,children:Array.from({length:G}).map((V,K)=>h.jsx(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-6 space-y-4",children:U},`skeleton-${K}`))})}if(j.length===0)return h.jsx(I,{className:b,style:_,id:w,...M,children:h.jsx(I,{className:"bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-12",children:h.jsx(yt,{className:"text-center text-gray-600 dark:text-gray-400",children:T})})});const B=a&&(f||O>1);return h.jsxs(I,{className:b,style:_,id:w,...M,children:[h.jsx(I,{className:`grid ${W} gap-${r}`,children:j.map((G,V)=>h.jsx(I,{className:o,children:Xb(P,G,V,`card-${V}`,p,H)},G[i]||V))}),B&&h.jsxs(I,{className:"mt-6 space-y-4 flex flex-col items-center",children:[h.jsx($o,{currentPage:D,totalPages:O,onPageChange:z,showFirstLast:!1,maxVisiblePages:5,prevText:E,nextText:C}),m&&h.jsx(I,{className:"text-sm text-gray-600 dark:text-gray-400 text-center",children:m})]})]})},qb=(t,e)=>window.G7Core?.t?.(t,e)??t,Bo=({isOpen:t,onClose:e,title:n,titlePrefix:r,titleSuffix:s,closeOnBackdropClick:o=!0,closeOnOverlayClick:i,showCloseButton:a=!0,closeOnEscape:l=!0,children:c,width:d="500px",className:u="",style:f,__dataContext:g,__translationContext:m})=>{const p=i!==void 0?i:o,y=v.useRef(null),b=v.useRef(null);if(v.useEffect(()=>{const E=C=>{C.key==="Escape"&&t&&l&&e()};if(t&&l)return document.addEventListener("keydown",E),()=>document.removeEventListener("keydown",E)},[t,e,l]),v.useEffect(()=>{if(t&&y.current){b.current=document.activeElement;const E=y.current.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),C=E[0],k=E[E.length-1];C?.focus();const N=S=>{S.key==="Tab"&&(S.shiftKey?document.activeElement===C&&(S.preventDefault(),k?.focus()):document.activeElement===k&&(S.preventDefault(),C?.focus()))};return document.addEventListener("keydown",N),()=>{document.removeEventListener("keydown",N),b.current?.focus()}}},[t]),v.useEffect(()=>{if(t)return document.body.style.overflow="hidden",()=>{document.body.style.overflow=""}},[t]),!t)return null;const _=f?.zIndex??50,x=(E,C)=>{if(!E||E.length===0)return null;const k=window.G7Core;if(!k)return null;const N=k.getDynamicRenderer?.();return N?E.map((S,w)=>h.jsx(N,{componentDef:S,dataContext:g||{},translationContext:m,registry:k.getComponentRegistry(),bindingEngine:k.getDataBindingEngine(),translationEngine:k.getTranslationEngine(),actionDispatcher:k.getActionDispatcher(),isRootRenderer:!1},`${C}-${w}`)):null};return h.jsxs(I,{className:"modal-wrapper",style:{zIndex:_},onClick:p?e:void 0,children:[h.jsx(I,{className:"modal-backdrop","aria-hidden":"true"}),h.jsxs(I,{ref:y,className:`modal-container ${u}`,style:{width:d,...f?{...f,zIndex:void 0}:{}},onClick:E=>E.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":n?"modal-title":void 0,children:[(n||r||s||a)&&h.jsxs(I,{className:"modal-header-row",children:[h.jsxs(I,{className:"flex-center gap-sm",children:[x(r,"title-prefix"),n&&h.jsx(I,{id:"modal-title",className:"modal-title",children:n}),x(s,"title-suffix")]}),a&&h.jsx(oe,{onClick:e,className:"modal-close-btn","aria-label":qb("common.close_modal"),children:h.jsx("svg",{className:"icon-lg",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),h.jsx(I,{className:"modal-body-content",children:c})]})]})},Zb=({label:t,items:e,onItemClick:n,position:r="bottom-left",variant:s="default",className:o="",style:i,id:a,editorAttrs:l})=>{const[c,d]=v.useState(!1),[u,f]=v.useState(-1),g=v.useRef(null),m=v.useRef(null),[p,y]=v.useState(r);v.useEffect(()=>{const x=E=>{g.current&&!g.current.contains(E.target)&&(d(!1),f(-1))};if(c)return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[c]),v.useEffect(()=>{const x=E=>{if(!c)return;const C=e.map((k,N)=>k.disabled?-1:N).filter(k=>k!==-1);switch(E.key){case"ArrowDown":E.preventDefault(),f(k=>{const S=(C.indexOf(k)+1)%C.length;return C[S]});break;case"ArrowUp":E.preventDefault(),f(k=>{const N=C.indexOf(k),S=N<=0?C.length-1:N-1;return C[S]});break;case"Enter":E.preventDefault(),u>=0&&!e[u].disabled&&b(e[u]);break;case"Escape":E.preventDefault(),d(!1),f(-1);break}};if(c)return document.addEventListener("keydown",x),()=>document.removeEventListener("keydown",x)},[c,u,e]),v.useEffect(()=>{if(c&&m.current&&g.current){const x=m.current.getBoundingClientRect(),E=g.current.getBoundingClientRect(),C=window.innerWidth,k=window.innerHeight,N=x.width||224,S=x.height||200;let w=r;r.includes("left")?E.left+N>C&&(w=w.replace("left","right")):E.right-N<0&&(w=w.replace("right","left")),r.includes("top")?E.top-S<0&&(w=w.replace("top","bottom")):E.bottom+S>k&&(w=w.replace("bottom","top")),y(w)}},[c,r]);const b=x=>{x.disabled||(x.onClick?.(),n?.(x.value,x),d(!1),f(-1))},_={"bottom-left":"dropdown-menu-bottom-left","bottom-right":"dropdown-menu-bottom-right","top-left":"dropdown-menu-top-left","top-right":"dropdown-menu-top-right"};return h.jsxs(I,{ref:g,className:`dropdown ${o}`,style:i,id:a,...l,children:[h.jsxs(oe,{onClick:()=>d(!c),className:s==="text"?"dropdown-trigger-text":"dropdown-trigger","aria-haspopup":"true","aria-expanded":c,children:[t,s!=="text"&&h.jsx("svg",{className:`inline-block ml-2 w-4 h-4 transition-transform ${c?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),c&&h.jsx(I,{ref:m,className:`dropdown-menu ${_[p]}`,role:"menu","aria-orientation":"vertical",children:h.jsx(I,{className:"py-1",children:e.map((x,E)=>h.jsx(I,{className:`${x.disabled?"dropdown-item-disabled":"dropdown-item"} ${u===E?"dropdown-item-focused":""}`,onClick:()=>b(x),onMouseEnter:()=>!x.disabled&&f(E),role:"menuitem","aria-disabled":x.disabled,tabIndex:x.disabled?-1:0,children:x.label},x.value))})})]})},Qb=({imageUrl:t,imageAlt:e="",title:n,subtitle:r,description:s,price:o,originalPrice:i,discountRate:a,currency:l="₩",buttonText:c="자세히 보기",onButtonClick:d,className:u="",style:f,id:g,editorAttrs:m})=>{const p=a&&a>0,y=(o??0).toLocaleString(),b=i?.toLocaleString();return h.jsxs(I,{className:`rounded-lg bg-white dark:bg-gray-800 shadow-md hover:shadow-xl transition-shadow border border-gray-200 dark:border-gray-700 overflow-hidden ${u}`,style:f,id:g,...m,children:[t&&h.jsxs(I,{className:"relative w-full",children:[h.jsx(yn,{src:t,alt:e||n,className:"w-full h-64 object-cover"}),p&&h.jsx(I,{className:"absolute top-2 right-2 bg-red-500 text-white px-2 py-1 rounded-md",children:h.jsxs(Q,{className:"text-sm font-bold",children:["-",a,"%"]})})]}),h.jsxs(I,{className:"p-4",children:[r&&h.jsx(Ir,{className:"text-xs font-medium text-blue-600 dark:text-blue-400 uppercase tracking-wider mb-2",children:r}),h.jsx(Jn,{className:"text-xl font-bold text-gray-900 dark:text-white mb-2 line-clamp-2",children:n}),s&&h.jsx(yt,{className:"text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-3",children:s}),h.jsxs(I,{className:"flex items-center gap-2 mb-4",children:[h.jsxs(Q,{className:"text-2xl font-bold text-gray-900 dark:text-white",children:[l,y]}),i&&h.jsxs(Q,{className:"text-sm text-gray-400 dark:text-gray-500 line-through",children:[l,b]})]}),c&&h.jsx(oe,{onClick:d,className:"w-full bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors",children:c})]})]})},Jb=({value:t,label:e,change:n,changeLabel:r="전월 대비",iconName:s,trend:o="neutral",className:i="",style:a,id:l,editorAttrs:c})=>{const d=o==="up",u=o==="down",f=n!==void 0?Math.abs(n):0,g=d?"stats-trend-up":u?"stats-trend-down":"stats-trend-neutral",m=d?ce.ArrowUp:u?ce.ArrowDown:void 0;return h.jsxs(I,{className:`stat-card ${i}`,style:a,id:l,...c,children:[h.jsxs(I,{className:"flex-between mb-md",children:[s&&h.jsx(I,{className:"stats-icon stats-icon-blue",children:h.jsx(re,{name:s,className:"icon-lg"})}),n!==void 0&&h.jsxs(I,{className:`stats-change ${g}`,children:[m&&h.jsx(re,{name:m,className:"icon-sm"}),h.jsxs(Q,{className:"text-sm font-semibold",children:[f,"%"]})]})]}),h.jsx(Ir,{className:"stats-value-lg",children:typeof t=="number"?t.toLocaleString():t}),h.jsx(yt,{className:"stats-label",children:e}),n!==void 0&&r&&h.jsx(I,{className:"stats-sublabel",children:h.jsx(Q,{children:r})})]})},Jd=()=>window.G7Core,Rd=(t,e)=>Jd()?.t?.(t,e)??t,Rb=({name:t="search",placeholder:e,value:n,onChange:r,onSubmit:s,showButton:o=!1,suggestions:i=[],onSuggestionClick:a,showSuggestions:l=!1,className:c="",style:d,id:u,editorAttrs:f})=>{const g=e??Rd("common.search_placeholder"),[m,p]=v.useState(""),[y,b]=v.useState(!1),_=v.useRef(null),x=v.useRef(""),E=n!==void 0?n:m,C=l&&y&&i.length>0&&E.length>0,k="w-64 pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white text-sm",N=Jd()?.style?.mergeClasses?.(k,c)??`${k} ${c}`;v.useEffect(()=>{n!==void 0&&(x.current=n)},[n]);const S=T=>{const P=T.target.value;p(P),r?.(T),x.current!==""&&P===""&&!T.nativeEvent.inputType&&setTimeout(()=>{const L=_.current?.querySelector("form");L&&L.requestSubmit()},0),x.current=P},w=T=>{p(T.text);const P={target:{value:T.text,name:t}};r?.(P),a?.(T),b(!1)},M=T=>{T.preventDefault(),s?.(T)};return h.jsxs(I,{ref:_,className:"relative",style:d,id:u,...f,children:[h.jsx(Pa,{onSubmit:M,className:"relative",children:h.jsxs(I,{className:`relative flex items-center ${o?"gap-2":""}`,children:[h.jsxs(I,{className:"relative flex-1",children:[h.jsx(I,{className:"absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none",children:h.jsx(re,{name:ce.Search,className:"w-5 h-5 text-gray-400 dark:text-gray-500"})}),h.jsx(kt,{type:"search",name:t,value:E,onChange:S,onFocus:()=>b(!0),onBlur:()=>setTimeout(()=>b(!1),200),placeholder:g,className:N})]}),o&&h.jsx(oe,{type:"submit",className:"px-4 py-2 bg-gray-800 dark:bg-gray-600 text-white rounded-lg hover:bg-gray-900 dark:hover:bg-gray-500 transition-colors text-sm font-medium cursor-pointer h-[42px]",children:Rd("common.search")})]})}),C&&h.jsx(I,{className:"absolute z-10 w-full mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-64 overflow-y-auto",children:i.map(T=>h.jsx(I,{className:"px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer border-b border-gray-100 dark:border-gray-700 last:border-b-0 transition-colors",onClick:()=>w(T),children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(re,{name:ce.Search,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:T.text})]})},T.id))})]})},nl=(t,e)=>window.G7Core?.t?.(t,e)??t,ey=({title:t,filters:e,onChange:n,onReset:r,showResetButton:s=!0,className:o="",style:i,id:a,editorAttrs:l})=>{const c=t??nl("common.filter"),d=(f,g)=>{n?.(f,g)},u=(f,g,m)=>{const p=e.find(b=>b.id===f);if(!p)return;const y=Array.isArray(p.value)?p.value:[];if(typeof g=="string"){const b=y.filter(x=>typeof x=="string"),_=m?[...b,g]:b.filter(x=>x!==g);n?.(f,_)}else{const b=y.filter(x=>typeof x=="number"),_=m?[...b,g]:b.filter(x=>x!==g);n?.(f,_)}};return h.jsxs(I,{className:`rounded-lg bg-white dark:bg-gray-800 shadow-md border border-gray-200 dark:border-gray-700 p-4 ${o}`,style:i,id:a,...l,children:[h.jsxs(I,{className:"flex items-center justify-between mb-4 pb-3 border-b border-gray-200 dark:border-gray-700",children:[h.jsx(Q,{className:"text-lg font-semibold text-gray-900 dark:text-white",children:c}),s&&r&&h.jsx(oe,{onClick:r,className:"text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium",children:nl("common.reset")})]}),h.jsx(I,{className:"space-y-4",children:e.map(f=>h.jsxs(I,{className:"space-y-2",children:[h.jsx(sn,{className:"block text-sm font-medium text-gray-700 dark:text-gray-300",children:f.label}),f.type==="select"&&h.jsxs(Pr,{value:f.value,onChange:g=>d(f.id,g.target.value),className:"w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-blue-500 dark:focus:border-blue-400 bg-white dark:bg-gray-700 text-gray-900 dark:text-white",children:[h.jsx("option",{value:"",children:nl("common.select_placeholder")}),f.options.map(g=>h.jsx("option",{value:g.value,children:g.label},g.id))]}),f.type==="checkbox"&&h.jsx(I,{className:"space-y-2",children:f.options.map(g=>{const m=Array.isArray(f.value)?f.value.some(p=>p===g.value):!1;return h.jsxs(sn,{className:"flex items-center gap-2 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 p-2 rounded-md transition-colors",children:[h.jsx(vn,{checked:m,onChange:p=>u(f.id,g.value,p.target.checked),className:"w-4 h-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:g.label})]},g.id)})})]},f.id))})]})},ty=({tabs:t,activeTabId:e,onTabChange:n,variant:r="default",className:s="",style:o,mobileBreakpoint:i=768,id:a,editorAttrs:l})=>{const d=window.G7Core?.useResponsive,u=d?.(),f=u?u.width{!k.disabled&&k.id!==e&&n?.(k.id)},p=(k,N)=>{const S=k.key==="ArrowRight"?1:k.key==="ArrowLeft"?-1:0;let w=null;if(S!==0)for(let M=1;M<=t.length;M+=1){const T=(N+S*M+t.length*M)%t.length;if(!t[T]?.disabled){w=T;break}}else if(k.key==="Home")w=t.findIndex(M=>!M.disabled);else if(k.key==="End"){for(let M=t.length-1;M>=0;M-=1)if(!t[M]?.disabled){w=M;break}}w===null||w<0||(k.preventDefault(),g.current[w]?.focus(),m(t[w]))},y=k=>{const N=k.target.value,S=Number(N),w=typeof N=="string"&&N!==""&&!Number.isNaN(S)?S:N,M=t.find(T=>String(T.id)===String(w));M&&m(M)};if(f)return h.jsx(I,{className:s,style:o,id:a,...l,children:h.jsx(Pr,{value:e!==void 0?String(e):"",onChange:y,options:t.map(k=>({value:String(k.id),label:k.badge!==void 0?`${k.label} (${k.badge})`:k.label,disabled:k.disabled})),className:"w-full"})});const b=k=>{const N=k.id===e;if(k.disabled)return"tab-btn-base tab-btn-disabled";switch(r){case"pills":return N?"tab-btn-base tab-btn-pills-active":"tab-btn-base tab-btn-pills";case"underline":return N?"tab-btn-base tab-btn-underline-active":"tab-btn-base tab-btn-underline";default:return N?"tab-btn-base tab-btn-default-active":"tab-btn-base tab-btn-default"}},_=r==="underline"?"tab-nav-underline":"tab-nav-default",x=l?.["data-editor-path"],E=t.findIndex(k=>k.id===e),C=E>=0?E:t.findIndex(k=>!k.disabled);return h.jsx(Po,{className:`${_} ${s}`,style:o,role:"tablist","aria-orientation":"horizontal",id:a,...l,children:t.map((k,N)=>h.jsxs(oe,{ref:S=>{g.current[N]=S},type:"button",role:"tab","aria-selected":k.id===e,tabIndex:N===C?0:-1,onClick:()=>m(k),onKeyDown:S=>p(S,N),disabled:k.disabled,className:b(k),...x?{"data-editor-item-path":`${x}.props.tabs.${N}`}:{},children:[k.iconName&&h.jsx(re,{name:k.iconName,className:"w-4 h-4"}),h.jsx(Q,{children:k.label}),k.badge!==void 0&&h.jsx(I,{className:"flex items-center justify-center min-w-[20px] h-5 px-1.5 bg-red-500 dark:bg-red-600 text-white dark:text-white text-xs font-bold rounded-full",children:h.jsx(Q,{children:k.badge})})]},k.id))})},rl=window.G7Core?.createLogger?.("Handler:ScrollToSection")??{log:(...t)=>console.log("[Handler:ScrollToSection]",...t),warn:(...t)=>console.warn("[Handler:ScrollToSection]",...t),error:(...t)=>console.error("[Handler:ScrollToSection]",...t)};async function eu(t,e){const n=t.params||{},{targetId:r,offset:s=120,delay:o=100,scrollContainerId:i}=n;if(!r){rl.warn("targetId is required");return}const a=await ny(r,o);if(!a){rl.warn(`Element not found: ${r}`);return}const l=ry(a,i),c=sy(a,l,s);oy(l,c)}async function ny(t,e){await new Promise(s=>setTimeout(s,e));const n=10,r=50;for(let s=0;ssetTimeout(i,50)),o;await new Promise(i=>setTimeout(i,r))}return null}function ry(t,e){if(e){const o=document.getElementById(e);if(o&&tu(o))return o;rl.warn(`Container '${e}' ${o?"has no scroll":"not found"}, trying auto-detect`)}let n=t.parentElement,r=0;const s=10;for(;n&&n!==document.body&&rt.clientHeight}function sy(t,e,n){if(e===window||!("getBoundingClientRect"in e))return t.getBoundingClientRect().top+window.pageYOffset-n;{const r=e.getBoundingClientRect(),s=t.getBoundingClientRect();return e.scrollTop+(s.top-r.top)-n}}function oy(t,e){t===window||!("scrollTo"in t)?window.scrollTo({top:e,behavior:"smooth"}):t.scrollTo({top:e,behavior:"smooth"})}window.G7Core?.createLogger?.("Comp:TabNavigation");const iy=({tabs:t,activeTabId:e,className:n="bg-white dark:bg-gray-800 flex gap-2 border-b border-gray-200 dark:border-gray-700 px-6",style:r,activeClassName:s="tab-btn-base tab-btn-default-active",inactiveClassName:o="tab-btn-base tab-btn-default",sectionIdPrefix:i="tab_content_",scrollOffset:a=120,scrollDelay:l=100,onTabChange:c,enableScrollSpy:d=!1,scrollSpyOffset:u=80,scrollContainerId:f,id:g,editorAttrs:m})=>{const[p,y]=v.useState(e??(t.length>0?t[0].id:"")),_=window.G7Core?.useResponsive,x=_?.(),E=x?x.width<768:typeof window<"u"&&window.innerWidth<768,C=v.useRef(!1),k=v.useRef(null),N=v.useRef([]),S=L=>{if(d&&(C.current=!0,k.current&&clearTimeout(k.current)),L.onClick){if(L.onClick(L.id),!d)return}else if(c){if(c(L.id),!d)return}else y(L.id);const $=L.scrollOffset??a,A=L.scrollDelay??l;eu({params:{targetId:`${i}${L.id}`,offset:$,delay:A,scrollContainerId:f}}),d&&(k.current=setTimeout(()=>{C.current=!1},A+500))},w=(L,$)=>{const A=L.key==="ArrowRight"?1:L.key==="ArrowLeft"?-1:0;let D=null;if(A!==0)for(let O=1;O<=t.length;O+=1){const z=($+A*O+t.length*O)%t.length;if(!t[z]?.disabled){D=z;break}}else if(L.key==="Home")D=t.findIndex(O=>!O.disabled);else if(L.key==="End"){for(let O=t.length-1;O>=0;O-=1)if(!t[O]?.disabled){D=O;break}}D===null||D<0||(L.preventDefault(),N.current[D]?.focus(),S(t[D]))};v.useEffect(()=>{if(!d)return;const L=f?document.getElementById(f):null,$={root:L??null,rootMargin:`-${u}px 0px -30% 0px`,threshold:[0,.1,.2,.3,.4,.5,.6,.7,.8,.9,1]},A=new Map,D=()=>{const j=L,W=j?j.scrollTop:window.pageYOffset||document.documentElement.scrollTop,U=j?j.scrollHeight:document.documentElement.scrollHeight,B=j?j.clientHeight:document.documentElement.clientHeight;return W+B>=U-10},O=j=>{if(C.current)return;j.forEach(K=>{const X=K.target.id;K.isIntersecting?A.set(X,K.intersectionRatio):A.delete(X)});let W=0,U="";A.forEach((K,X)=>{K>W&&(W=K,U=X)});const B=t[t.length-1],G=`${i}${B?.id}`,V=A.has(G);if(D()&&V&&B){y(B.id);return}if(U){const K=U.replace(i,""),X=Number(K),Z=isNaN(X)?K:X;t.some(ne=>ne.id===Z)&&y(Z)}},z=()=>{},H=new IntersectionObserver(O,$);return t.forEach(j=>{const W=document.getElementById(`${i}${j.id}`);W&&H.observe(W)}),window.addEventListener("scroll",z,{passive:!0}),()=>{H.disconnect(),window.removeEventListener("scroll",z),k.current&&clearTimeout(k.current)}},[d,t,i,u,f]);const M=L=>{const $=L.target.value,A=Number($),D=isNaN(A)?$:A,O=t.find(z=>z.id===D);O&&S(O)};if(E)return h.jsx(I,{className:"bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3",id:g,...m,children:h.jsx(Pr,{value:String(p),onChange:M,options:t.map(L=>({value:String(L.id),label:L.label,disabled:L.disabled})),className:"w-full"})});const T=t.findIndex(L=>L.id===p),P=T>=0?T:t.findIndex(L=>!L.disabled);return h.jsx(I,{className:n,style:r,role:"tablist","aria-orientation":"horizontal",id:g,...m,children:t.map((L,$)=>h.jsxs(oe,{ref:A=>{N.current[$]=A},type:"button",role:"tab","aria-selected":p===L.id,tabIndex:$===P?0:-1,className:p===L.id?s:o,disabled:L.disabled,onClick:()=>S(L),onKeyDown:A=>w(A,$),children:[L.iconName&&h.jsx(re,{name:L.iconName,size:"sm"}),h.jsx(Q,{children:L.label})]},L.id))})},ay=()=>window.G7Core,ly=({iconName:t,label:e,onClick:n,variant:r="primary",size:s="md",disabled:o=!1,className:i="",style:a,id:l,editorAttrs:c})=>{const d={sm:e?"px-3 py-1.5 text-sm":"w-8 h-8",md:e?"px-4 py-2 text-base":"w-10 h-10",lg:e?"px-5 py-2.5 text-lg":"w-12 h-12"},u={sm:"w-4 h-4",md:"w-5 h-5",lg:"w-6 h-6"},f={primary:"bg-blue-600 hover:bg-blue-700 text-white border-transparent",secondary:"bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600",danger:"bg-red-600 hover:bg-red-700 text-white border-transparent",ghost:"bg-transparent hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-transparent",outline:"bg-transparent hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600"},g="inline-flex items-center justify-center gap-2 font-medium rounded-lg border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-400",m=o?"opacity-50 cursor-not-allowed":"",p=`${g} ${d[s]} ${f[r]} ${m}`,y=ay()?.style?.mergeClasses?.(p,i)??`${p} ${i}`;return h.jsxs(oe,{onClick:n,disabled:o,className:y,style:a,id:l,...c,children:[h.jsx(re,{name:t,className:u[s]}),e]})},cy=(t,e)=>window.G7Core?.t?.(t,e)??t,nu=({isOpen:t,onClose:e,title:n,content:r,actions:s=[],width:o="500px",closeOnOverlay:i=!0,showCloseButton:a=!0,className:l="",style:c})=>{const d=()=>{i&&e()},u={primary:"dialog-btn-primary",secondary:"dialog-btn-secondary",danger:"dialog-btn-danger",ghost:"dialog-btn-ghost"};return h.jsxs(Bo,{isOpen:t,onClose:d,width:o,className:l,style:c,children:[h.jsxs(I,{className:"dialog-header",children:[h.jsx(Jn,{className:"dialog-title",children:n}),a&&h.jsx(oe,{onClick:e,className:"dialog-close","aria-label":cy("common.close_dialog"),children:h.jsx(re,{name:ce.Times,className:"icon-md"})})]}),h.jsx(I,{className:"dialog-body",children:r}),s.length>0&&h.jsx(I,{className:"dialog-footer",children:s.map((f,g)=>h.jsxs(oe,{onClick:f.onClick,disabled:f.disabled,className:u[f.variant||"primary"],children:[f.iconName&&h.jsx(re,{name:f.iconName,className:"icon-sm mr-2 inline-block"}),f.label]},g))})]})},sl=(t,e)=>window.G7Core?.t?.(t,e)??t,ru=({isOpen:t,onClose:e,title:n,message:r,confirmText:s,cancelText:o,onConfirm:i,onCancel:a,confirmButtonVariant:l="primary",cancelButtonVariant:c="secondary",width:d="500px",closeOnOverlay:u=!0,isLoading:f=!1,loadingText:g})=>{const m=s??sl("common.confirm"),p=o??sl("common.cancel"),y=g??sl("common.loading"),b=()=>{i()},_=()=>{f||(a?.(),e())},x=()=>{f||e()},E={primary:"bg-blue-600 text-white hover:bg-blue-700",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600",danger:"bg-red-600 text-white hover:bg-red-700",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"};return h.jsxs(Bo,{isOpen:t,onClose:x,title:n,width:d,children:[h.jsx(I,{className:"py-2",children:typeof r=="string"?h.jsx(yt,{className:"text-gray-700 dark:text-gray-300",children:r}):r}),h.jsxs(I,{className:"flex justify-end gap-3 mt-6",children:[h.jsx(oe,{onClick:_,disabled:f,className:"px-4 py-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed",children:h.jsx(Q,{children:p})}),h.jsxs(oe,{onClick:b,disabled:f,className:`flex items-center gap-2 px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed ${E[l]}`,children:[f&&h.jsx(re,{name:ce.Spinner,className:"w-4 h-4 animate-spin"}),h.jsx(Q,{children:f?y:m})]})]})]})},dy=(t,e)=>window.G7Core?.t?.(t,e)??t,uy=({isOpen:t,onClose:e,title:n,message:r,confirmText:s,onConfirm:o,confirmButtonVariant:i="primary",width:a="500px",closeOnOverlay:l=!0})=>{const u=[{label:s??dy("common.confirm"),onClick:()=>{o?.(),e()},variant:i}],f=typeof r=="string"?h.jsx(yt,{className:"text-gray-700 dark:text-gray-300",children:r}):r;return h.jsx(nu,{isOpen:t,onClose:e,title:n,content:f,actions:u,width:a,closeOnOverlay:l})},fy=(t,e)=>window.G7Core?.t?.(t,e)??t,hy={success:ce.CheckCircle,error:ce.XCircle,warning:ce.ExclamationTriangle,info:ce.InfoCircle},su={success:{bg:"bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800",text:"text-green-800 dark:text-green-300"},error:{bg:"bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800",text:"text-red-800 dark:text-red-300"},warning:{bg:"bg-yellow-50 dark:bg-yellow-900/20 border-yellow-200 dark:border-yellow-800",text:"text-yellow-800 dark:text-yellow-300"},info:{bg:"bg-blue-50 dark:bg-blue-900/20 border-blue-200 dark:border-blue-800",text:"text-blue-800 dark:text-blue-300"}},gy={"top-left":"top-4 left-4","top-center":"top-4 left-1/2 -translate-x-1/2","top-right":"top-4 right-4","bottom-left":"bottom-4 left-4","bottom-center":"bottom-4 left-1/2 -translate-x-1/2","bottom-right":"bottom-4 right-4"},py=({toasts:t,position:e="bottom-center",duration:n=3e3,onRemove:r})=>{const[s,o]=v.useState(new Map),i=v.useCallback(a=>{o(l=>{const c=new Map(l);return c.set(a,!1),c}),setTimeout(()=>{if(r)r(a);else{const l=window.G7Core;l?.state?.update&&l.state.update(c=>({toasts:(Array.isArray(c.toasts)?c.toasts:[]).filter(u=>u.id!==a)}))}},300)},[r]);return v.useEffect(()=>{!t||t.length===0||t.forEach(a=>{if(s.has(a.id))return;o(c=>{const d=new Map(c);return d.set(a.id,!0),d});const l=a.duration||n;l>0&&setTimeout(()=>{i(a.id)},l)})},[t,n,i,s]),!t||t.length===0?null:h.jsx(I,{className:`fixed z-[9999] flex flex-col gap-2 ${gy[e]}`,"aria-live":"polite","aria-atomic":"true",children:t.map(a=>h.jsx(my,{toast:a,position:e,isVisible:s.get(a.id)??!0,onClose:()=>i(a.id)},a.id))})},my=({toast:t,position:e,isVisible:n,onClose:r})=>{const s=su[t.type]||su.info,o=t.icon?t.icon:hy[t.type],i=e.startsWith("bottom")?"translate-y-2":"-translate-y-2";return h.jsxs(I,{className:` flex items-center gap-3 p-4 rounded-lg border shadow-lg min-w-[300px] max-w-md transition-all duration-300 ${s.bg} ${n?"opacity-100 translate-y-0":`opacity-0 ${i}`} - `,role:"alert",children:[h.jsx(se,{name:o,className:`w-5 h-5 flex-shrink-0 ${s.text}`}),h.jsx(Q,{className:`flex-1 text-sm font-medium ${s.text}`,children:e.message}),h.jsx(ie,{onClick:r,className:`flex-shrink-0 p-1 rounded hover:bg-black/10 dark:hover:bg-white/10 ${s.text} focus:outline-none`,"aria-label":Iy("common.close_notification"),children:h.jsx(se,{name:ce.Times,className:"w-4 h-4"})})]})},bu=({items:e,separator:t=h.jsx(se,{name:ce.ChevronRight,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),showHome:n=!1,homeHref:r="/",maxItems:s,className:o="",id:i,editorAttrs:a})=>{let c=n?[{label:"Home",href:r},...e]:e,d=c,u=!1;if(s&&c.length>s){u=!0;const f=c.slice(0,1),g=c.slice(-(s-2));d=[...f,...g]}return h.jsx(Xo,{className:`flex items-center space-x-2 text-sm ${o}`,"aria-label":"Breadcrumb",id:i,...a,children:h.jsx(I,{className:"flex items-center space-x-2",children:d.map((f,g)=>{const m=g===d.length-1,p=g===0;return h.jsxs(v.Fragment,{children:[u&&p&&h.jsxs(h.Fragment,{children:[h.jsx(yu,{item:f,isLast:!1}),h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:t}),h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:"..."}),d.length>1&&h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:t})]}),(!u||!p)&&h.jsxs(h.Fragment,{children:[h.jsx(yu,{item:f,isLast:m}),!m&&h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:t})]})]},g)})})})},yu=({item:e,isLast:t})=>t||!e.href&&!e.onClick?h.jsx(Q,{className:"font-medium text-gray-900 dark:text-white","aria-current":t?"page":void 0,children:e.label}):e.onClick?h.jsx(Kr,{href:"#",onClick:n=>{n.preventDefault(),e.onClick?.()},className:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors",children:e.label}):h.jsx(Kr,{href:e.href,className:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors",children:e.label}),zy=({title:e,description:t,iconName:n,illustrationSrc:r,illustrationAlt:s="Empty state illustration",actions:o=[],className:i="",id:a,editorAttrs:l})=>{const c={primary:"bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600 focus:ring-gray-500",danger:"bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-gray-500"};return h.jsxs(I,{className:`flex flex-col items-center justify-center p-8 text-center ${i}`,role:"status","aria-live":"polite",id:a,...l,children:[r?h.jsx(Sn,{src:r,alt:s,className:"w-48 h-48 mb-6 object-contain"}):n?h.jsx(I,{className:"mb-6 p-4 rounded-full bg-gray-100 dark:bg-gray-700",children:h.jsx(se,{name:n,className:"w-12 h-12 text-gray-400 dark:text-gray-500"})}):null,h.jsx(ar,{className:"text-xl font-semibold text-gray-900 dark:text-white mb-2",children:e}),t&&h.jsx(wt,{className:"text-gray-600 dark:text-gray-400 mb-6 max-w-md",children:t}),o.length>0&&h.jsx(I,{className:"flex flex-wrap gap-3 justify-center",children:o.map((d,u)=>h.jsxs(ie,{onClick:d.onClick,className:` + `,role:"alert",children:[h.jsx(re,{name:o,className:`w-5 h-5 flex-shrink-0 ${s.text}`}),h.jsx(Q,{className:`flex-1 text-sm font-medium ${s.text}`,children:t.message}),h.jsx(oe,{onClick:r,className:`flex-shrink-0 p-1 rounded hover:bg-black/10 dark:hover:bg-white/10 ${s.text} focus:outline-none`,"aria-label":fy("common.close_notification"),children:h.jsx(re,{name:ce.Times,className:"w-4 h-4"})})]})},ou=({items:t,separator:e=h.jsx(re,{name:ce.ChevronRight,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),showHome:n=!1,homeHref:r="/",maxItems:s,className:o="",id:i,editorAttrs:a})=>{let c=n?[{label:"Home",href:r},...t]:t,d=c,u=!1;if(s&&c.length>s){u=!0;const f=c.slice(0,1),g=c.slice(-(s-2));d=[...f,...g]}return h.jsx(Po,{className:`flex items-center space-x-2 text-sm ${o}`,"aria-label":"Breadcrumb",id:i,...a,children:h.jsx(I,{className:"flex items-center space-x-2",children:d.map((f,g)=>{const m=g===d.length-1,p=g===0;return h.jsxs(v.Fragment,{children:[u&&p&&h.jsxs(h.Fragment,{children:[h.jsx(iu,{item:f,isLast:!1}),h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:e}),h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:"..."}),d.length>1&&h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:e})]}),(!u||!p)&&h.jsxs(h.Fragment,{children:[h.jsx(iu,{item:f,isLast:m}),!m&&h.jsx(Q,{className:"text-gray-400 dark:text-gray-500","aria-hidden":"true",children:e})]})]},g)})})})},iu=({item:t,isLast:e})=>e||!t.href&&!t.onClick?h.jsx(Q,{className:"font-medium text-gray-900 dark:text-white","aria-current":e?"page":void 0,children:t.label}):t.onClick?h.jsx(Lr,{href:"#",onClick:n=>{n.preventDefault(),t.onClick?.()},className:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors",children:t.label}):h.jsx(Lr,{href:t.href,className:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors",children:t.label}),by=({title:t,description:e,iconName:n,illustrationSrc:r,illustrationAlt:s="Empty state illustration",actions:o=[],className:i="",id:a,editorAttrs:l})=>{const c={primary:"bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",secondary:"bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600 focus:ring-gray-500",danger:"bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",ghost:"bg-transparent text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-gray-500"};return h.jsxs(I,{className:`flex flex-col items-center justify-center p-8 text-center ${i}`,role:"status","aria-live":"polite",id:a,...l,children:[r?h.jsx(yn,{src:r,alt:s,className:"w-48 h-48 mb-6 object-contain"}):n?h.jsx(I,{className:"mb-6 p-4 rounded-full bg-gray-100 dark:bg-gray-700",children:h.jsx(re,{name:n,className:"w-12 h-12 text-gray-400 dark:text-gray-500"})}):null,h.jsx(Jn,{className:"text-xl font-semibold text-gray-900 dark:text-white mb-2",children:t}),e&&h.jsx(yt,{className:"text-gray-600 dark:text-gray-400 mb-6 max-w-md",children:e}),o.length>0&&h.jsx(I,{className:"flex flex-wrap gap-3 justify-center",children:o.map((d,u)=>h.jsxs(oe,{onClick:d.onClick,className:` px-4 py-2 rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 ${c[d.variant||"primary"]} - `,children:[d.iconName&&h.jsx(se,{name:d.iconName,className:"w-4 h-4 mr-2 inline-block"}),d.label]},u))})]})},Vy=(e,t)=>window.G7Core?.t?.(e,t)??e,Hy=({id:e,size:t="md",color:n="text-blue-600",fullscreen:r=!1,text:s,className:o="",editorAttrs:i})=>{const a={sm:"w-4 h-4 border-2",md:"w-8 h-8 border-2",lg:"w-12 h-12 border-3",xl:"w-16 h-16 border-4"},l={sm:"text-xs",md:"text-sm",lg:"text-base",xl:"text-lg"},c=h.jsxs(I,{className:`flex flex-col items-center justify-center gap-3 ${o}`,children:[h.jsx(I,{className:` - ${a[t]} + `,children:[d.iconName&&h.jsx(re,{name:d.iconName,className:"w-4 h-4 mr-2 inline-block"}),d.label]},u))})]})},yy=(t,e)=>window.G7Core?.t?.(t,e)??t,vy=({id:t,size:e="md",color:n="text-blue-600",fullscreen:r=!1,text:s,className:o="",editorAttrs:i})=>{const a={sm:"w-4 h-4 border-2",md:"w-8 h-8 border-2",lg:"w-12 h-12 border-3",xl:"w-16 h-16 border-4"},l={sm:"text-xs",md:"text-sm",lg:"text-base",xl:"text-lg"},c=h.jsxs(I,{className:`flex flex-col items-center justify-center gap-3 ${o}`,children:[h.jsx(I,{className:` + ${a[e]} border-gray-200 dark:border-gray-700 border-t-current rounded-full animate-spin ${n} - `,role:"status","aria-label":Vy("common.loading")}),s&&h.jsx(Q,{className:`${l[t]} ${n} font-medium`,children:s})]});return r?h.jsx(I,{id:e,className:"fixed inset-0 z-50 flex items-center justify-center bg-white dark:bg-gray-900 bg-opacity-90 dark:bg-opacity-90","aria-live":"polite","aria-busy":"true",...i,children:c}):h.jsx(I,{id:e,className:"flex items-center justify-center p-4","aria-live":"polite","aria-busy":"true",...i,children:c})},ns=()=>window.G7Core,pl=(e,t)=>window.G7Core?.t?.(e,t)??e,Uy=({logo:e,logoAlt:t="Logo",menu:n=[],collapsed:r=!1,onToggleCollapse:s,className:o="",currentLocale:i,id:a,editorAttrs:l})=>{const[c,d]=v.useState(new Set),[u,f]=v.useState(i||ns()?.locale?.current?.()||"ko"),[g,m]=v.useState(window.location.pathname),p=v.useMemo(()=>{const w=[],E=D=>{for(const P of D)P.url&&w.push(P.url),P.children&&E(P.children)};return E(n),w},[n]),y=v.useCallback(w=>{let E=null;for(const D of p)(w===D||w.startsWith(D+"/"))&&(!E||D.length>E.length)&&(E=D);return E},[p]);v.useEffect(()=>{const w=ns();return w?.state?.subscribe?w.state.subscribe(D=>{const P=D?._global?.locale||w?.locale?.current?.()||"ko";f(P);const L=D?._global?.currentPath||window.location.pathname;m(L)}):void 0},[]),v.useEffect(()=>{const w=()=>{m(window.location.pathname)};return window.addEventListener("popstate",w),()=>window.removeEventListener("popstate",w)},[]),v.useEffect(()=>{i&&f(i)},[i]);const b=(w,E)=>!w||typeof E!="string"?!1:E===w?!0:E.startsWith(w+"/")?y(E)===w:!1,_=(w,E)=>{const D=new Set,P=L=>{for(const $ of L)if($.children&&$.children.length>0){if(P($.children)||b($.url,E))return D.add($.id),!0}else if(b($.url,E))return!0;return!1};return P(w),D};v.useEffect(()=>{if(n.length>0&&g){const w=_(n,g);w.size>0&&d(E=>new Set([...E,...w]))}},[g,n]);const x=w=>b(w,g),M=w=>w.children?w.children.some(E=>x(E.url)||M(E)):!1,C=w=>{d(E=>{const D=new Set(E);return D.has(w)?D.delete(w):D.add(w),D})},k=w=>typeof w=="string"?w:w[u]||w.en||Object.values(w)[0]||"",N=(w,E)=>{ns()?.dispatch?(w.preventDefault(),ns().navigation?.onComplete(()=>{ns().dispatch({handler:"setState",params:{target:"global",sidebarOpen:!1}})}),ns().dispatch({handler:"navigate",params:{path:E}})):window.location.href=E},S=(w,E=0,D=!1)=>{const P=w.children&&w.children.length>0,L=c.has(w.id),$=k(w.name),A=E>0,O=x(w.url),T=M(w),z=A?"admin-sidebar-subitem":"admin-sidebar-item",H="admin-sidebar-item-active",j="admin-sidebar-item-active-parent",G="admin-sidebar-item-default";return h.jsxs(I,{className:A?"relative group":"",children:[A&&h.jsxs(h.Fragment,{children:[!D&&h.jsx(I,{className:"admin-sidebar-tree-line-vertical"}),h.jsx(I,{className:"admin-sidebar-tree-line-curve"})]}),P&&w.url?h.jsxs(I,{className:` + `,role:"status","aria-label":yy("common.loading")}),s&&h.jsx(Q,{className:`${l[e]} ${n} font-medium`,children:s})]});return r?h.jsx(I,{id:t,className:"fixed inset-0 z-50 flex items-center justify-center bg-white dark:bg-gray-900 bg-opacity-90 dark:bg-opacity-90","aria-live":"polite","aria-busy":"true",...i,children:c}):h.jsx(I,{id:t,className:"flex items-center justify-center p-4","aria-live":"polite","aria-busy":"true",...i,children:c})},zr=()=>window.G7Core,ol=(t,e)=>window.G7Core?.t?.(t,e)??t,xy=({logo:t,logoAlt:e="Logo",menu:n=[],collapsed:r=!1,onToggleCollapse:s,className:o="",currentLocale:i,id:a,editorAttrs:l})=>{const[c,d]=v.useState(new Set),[u,f]=v.useState(i||zr()?.locale?.current?.()||"ko"),[g,m]=v.useState(window.location.pathname),p=v.useMemo(()=>{const w=[],M=T=>{for(const P of T)P.url&&w.push(P.url),P.children&&M(P.children)};return M(n),w},[n]),y=v.useCallback(w=>{let M=null;for(const T of p)(w===T||w.startsWith(T+"/"))&&(!M||T.length>M.length)&&(M=T);return M},[p]);v.useEffect(()=>{const w=zr();return w?.state?.subscribe?w.state.subscribe(T=>{const P=T?._global?.locale||w?.locale?.current?.()||"ko";f(P);const L=T?._global?.currentPath||window.location.pathname;m(L)}):void 0},[]),v.useEffect(()=>{const w=()=>{m(window.location.pathname)};return window.addEventListener("popstate",w),()=>window.removeEventListener("popstate",w)},[]),v.useEffect(()=>{i&&f(i)},[i]);const b=(w,M)=>!w||typeof M!="string"?!1:M===w?!0:M.startsWith(w+"/")?y(M)===w:!1,_=(w,M)=>{const T=new Set,P=L=>{for(const $ of L)if($.children&&$.children.length>0){if(P($.children)||b($.url,M))return T.add($.id),!0}else if(b($.url,M))return!0;return!1};return P(w),T};v.useEffect(()=>{if(n.length>0&&g){const w=_(n,g);w.size>0&&d(M=>new Set([...M,...w]))}},[g,n]);const x=w=>b(w,g),E=w=>w.children?w.children.some(M=>x(M.url)||E(M)):!1,C=w=>{d(M=>{const T=new Set(M);return T.has(w)?T.delete(w):T.add(w),T})},k=w=>typeof w=="string"?w:w[u]||w.en||Object.values(w)[0]||"",N=(w,M)=>{zr()?.dispatch?(w.preventDefault(),zr().navigation?.onComplete(()=>{zr().dispatch({handler:"setState",params:{target:"global",sidebarOpen:!1}})}),zr().dispatch({handler:"navigate",params:{path:M}})):window.location.href=M},S=(w,M=0,T=!1)=>{const P=w.children&&w.children.length>0,L=c.has(w.id),$=k(w.name),A=M>0,D=x(w.url),O=E(w),z=A?"admin-sidebar-subitem":"admin-sidebar-item",H="admin-sidebar-item-active",j="admin-sidebar-item-active-parent",W="admin-sidebar-item-default";return h.jsxs(I,{className:A?"relative group":"",children:[A&&h.jsxs(h.Fragment,{children:[!T&&h.jsx(I,{className:"admin-sidebar-tree-line-vertical"}),h.jsx(I,{className:"admin-sidebar-tree-line-curve"})]}),P&&w.url?h.jsxs(I,{className:` ${z} - ${O?H:T?j:G} + ${D?H:O?j:W} ${r?"justify-center":""} - `,children:[h.jsxs(ie,{onClick:W=>N(W,w.url),className:"flex items-center justify-start flex-1 min-w-0",children:[w.icon&&h.jsx(yt,{className:`${w.icon} ${r?"":"mr-3"} w-5 h-5 flex items-center justify-center`}),!r&&h.jsx(Q,{className:"truncate",children:$})]}),!r&&h.jsxs(I,{className:"admin-sidebar-chevron-trail",children:[w.module_id&&h.jsx(yt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"}),h.jsx(ie,{onClick:()=>C(w.id),className:"p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600","aria-label":pl(L?"common.collapse":"common.expand"),children:h.jsx(yt,{className:`fas fa-chevron-down w-4 h-4 transition-transform ${L?"rotate-180":""}`})})]})]}):P?h.jsxs(ie,{onClick:()=>C(w.id),className:` + `,children:[h.jsxs(oe,{onClick:B=>N(B,w.url),className:"flex items-center justify-start flex-1 min-w-0",children:[w.icon&&h.jsx(mt,{className:`${w.icon} ${r?"":"mr-3"} w-5 h-5 flex items-center justify-center`}),!r&&h.jsx(Q,{className:"truncate",children:$})]}),!r&&h.jsxs(I,{className:"admin-sidebar-chevron-trail",children:[w.module_id&&h.jsx(mt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"}),h.jsx(oe,{onClick:()=>C(w.id),className:"p-1 rounded hover:bg-gray-200 dark:hover:bg-gray-600","aria-label":ol(L?"common.collapse":"common.expand"),children:h.jsx(mt,{className:`fas fa-chevron-down w-4 h-4 transition-transform ${L?"rotate-180":""}`})})]})]}):P?h.jsxs(oe,{onClick:()=>C(w.id),className:` ${z} - ${T?j:G} + ${O?j:W} ${r?"justify-center":""} - `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(yt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&h.jsxs(I,{className:"admin-sidebar-chevron-trail",children:[w.module_id&&h.jsx(yt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"}),h.jsx(yt,{className:`fas fa-chevron-right w-4 h-4 text-slate-400 transition-transform duration-200 ${L?"rotate-90":""}`})]})]}):w.url?h.jsxs(ie,{onClick:W=>N(W,w.url),className:` + `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(mt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&h.jsxs(I,{className:"admin-sidebar-chevron-trail",children:[w.module_id&&h.jsx(mt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"}),h.jsx(mt,{className:`fas fa-chevron-right w-4 h-4 text-slate-400 transition-transform duration-200 ${L?"rotate-90":""}`})]})]}):w.url?h.jsxs(oe,{onClick:B=>N(B,w.url),className:` ${z} - ${O?H:G} + ${D?H:W} ${r?"justify-center":""} text-left - `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(yt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&w.module_id&&h.jsx(yt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"})]}):h.jsxs(ie,{className:` + `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(mt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&w.module_id&&h.jsx(mt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"})]}):h.jsxs(oe,{className:` ${z} admin-sidebar-item-disabled ${r?"justify-center":""} text-left - `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(yt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&w.module_id&&h.jsx(yt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"})]}),P&&!r&&L&&h.jsx(I,{className:"relative ml-2",children:w.children.map((W,Y)=>S(W,E+1,Y===w.children.length-1))})]},w.id)};return h.jsxs(I,{className:o,id:a,...l,children:[(e||s)&&h.jsxs(I,{className:"admin-sidebar-header",children:[e&&!r&&h.jsx(Sn,{src:e,alt:t,className:"h-8"}),s&&h.jsx(ie,{onClick:s,className:"admin-sidebar-collapse-btn","aria-label":pl(r?"common.expand_sidebar":"common.collapse_sidebar"),children:h.jsx(yt,{className:`${r?"fas fa-chevron-right":"fas fa-chevron-left"} w-5 h-5 text-slate-600 dark:text-slate-400`})})]}),h.jsx(I,{className:"admin-sidebar-menu-list",children:n.map((w,E)=>S(w,0,E===n.length-1))})]})},Hs=(e,t)=>window.G7Core?.t?.(e,t)??e,By=({user:e={name:"",email:""},notifications:t=[],onNotificationClick:n,onProfileClick:r,onLogoutClick:s,className:o=""})=>{const[i,a]=v.useState(!1),[l,c]=v.useState(!1),d=v.useRef(null),u=v.useRef(null),f=t.filter(g=>!g.read).length;return v.useEffect(()=>{const g=m=>{d.current&&!d.current.contains(m.target)&&a(!1),u.current&&!u.current.contains(m.target)&&c(!1)};return document.addEventListener("mousedown",g),()=>document.removeEventListener("mousedown",g)},[]),h.jsxs(I,{className:` + `,children:[h.jsxs(I,{className:"admin-sidebar-item-content",children:[w.icon&&h.jsx(I,{className:"admin-sidebar-icon-box",children:h.jsx(mt,{className:w.icon})}),!r&&h.jsx(Q,{children:$})]}),!r&&w.module_id&&h.jsx(mt,{className:"fas fa-cube admin-sidebar-module-icon",title:"모듈 메뉴"})]}),P&&!r&&L&&h.jsx(I,{className:"relative ml-2",children:w.children.map((B,G)=>S(B,M+1,G===w.children.length-1))})]},w.id)};return h.jsxs(I,{className:o,id:a,...l,children:[(t||s)&&h.jsxs(I,{className:"admin-sidebar-header",children:[t&&!r&&h.jsx(yn,{src:t,alt:e,className:"h-8"}),s&&h.jsx(oe,{onClick:s,className:"admin-sidebar-collapse-btn","aria-label":ol(r?"common.expand_sidebar":"common.collapse_sidebar"),children:h.jsx(mt,{className:`${r?"fas fa-chevron-right":"fas fa-chevron-left"} w-5 h-5 text-slate-600 dark:text-slate-400`})})]}),h.jsx(I,{className:"admin-sidebar-menu-list",children:n.map((w,M)=>S(w,0,M===n.length-1))})]})},Es=(t,e)=>window.G7Core?.t?.(t,e)??t,wy=({user:t={name:"",email:""},notifications:e=[],onNotificationClick:n,onProfileClick:r,onLogoutClick:s,className:o=""})=>{const[i,a]=v.useState(!1),[l,c]=v.useState(!1),d=v.useRef(null),u=v.useRef(null),f=e.filter(g=>!g.read).length;return v.useEffect(()=>{const g=m=>{d.current&&!d.current.contains(m.target)&&a(!1),u.current&&!u.current.contains(m.target)&&c(!1)};return document.addEventListener("mousedown",g),()=>document.removeEventListener("mousedown",g)},[]),h.jsxs(I,{className:` bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-6 py-4 flex items-center justify-end gap-4 ${o} - `,children:[h.jsxs(I,{ref:d,className:"relative",children:[h.jsxs(ie,{onClick:()=>a(!i),className:"relative p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors","aria-label":Hs("common.notifications"),children:[h.jsx(se,{name:ce.Bell,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),f>0&&h.jsx(Q,{className:"absolute top-0 right-0 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 rounded-full",children:f>9?"9+":f})]}),i&&h.jsxs(I,{className:"absolute right-0 mt-2 w-80 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:[h.jsx(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:h.jsx(Q,{className:"font-semibold text-gray-900 dark:text-white",children:Hs("common.notifications")})}),h.jsx(I,{className:"max-h-96 overflow-y-auto",children:t.length===0?h.jsx(I,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:h.jsx(Q,{children:Hs("common.no_notifications")})}):t.map(g=>h.jsx(ie,{onClick:()=>{g.onClick?.(),n?.(g.id),a(!1)},className:` + `,children:[h.jsxs(I,{ref:d,className:"relative",children:[h.jsxs(oe,{onClick:()=>a(!i),className:"relative p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-full transition-colors","aria-label":Es("common.notifications"),children:[h.jsx(re,{name:ce.Bell,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),f>0&&h.jsx(Q,{className:"absolute top-0 right-0 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 rounded-full",children:f>9?"9+":f})]}),i&&h.jsxs(I,{className:"absolute right-0 mt-2 w-80 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:[h.jsx(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:h.jsx(Q,{className:"font-semibold text-gray-900 dark:text-white",children:Es("common.notifications")})}),h.jsx(I,{className:"max-h-96 overflow-y-auto",children:e.length===0?h.jsx(I,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:h.jsx(Q,{children:Es("common.no_notifications")})}):e.map(g=>h.jsx(oe,{onClick:()=>{g.onClick?.(),n?.(g.id),a(!1)},className:` w-full px-4 py-3 text-left hover:bg-gray-50 dark:hover:bg-gray-700 border-b border-gray-100 dark:border-gray-700 transition-colors ${g.read?"":"bg-blue-50 dark:bg-blue-900/20"} - `,children:h.jsxs(I,{className:"flex items-start gap-3",children:[g.iconName&&h.jsx(se,{name:g.iconName,className:"w-5 h-5 text-gray-600 dark:text-gray-400 mt-0.5"}),h.jsxs(I,{className:"flex-1",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:g.title}),h.jsx(I,{className:"text-gray-600 dark:text-gray-400 text-sm mt-1",children:g.message}),h.jsx(I,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1",children:g.time})]}),!g.read&&h.jsx(Q,{className:"w-2 h-2 bg-blue-500 rounded-full mt-1.5"})]})},g.id))})]})]}),h.jsxs(I,{ref:u,className:"relative",children:[h.jsxs(ie,{onClick:()=>c(!l),className:"flex items-center gap-3 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[e.avatar?h.jsx(Sn,{src:e.avatar,alt:e.name,className:"w-8 h-8 rounded-full object-cover"}):h.jsx(I,{className:"w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center",children:h.jsx(se,{name:ce.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:"hidden md:block text-left",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:e.name}),e.role&&h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-xs",children:e.role})]}),h.jsx(se,{name:ce.ChevronDown,className:"w-4 h-4 text-gray-600 dark:text-gray-400"})]}),l&&h.jsxs(I,{className:"absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white",children:e.name}),h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-sm",children:e.email})]}),h.jsxs(I,{className:"py-2",children:[h.jsxs(ie,{onClick:()=>{r?.(),c(!1)},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors text-gray-900 dark:text-white",children:[h.jsx(se,{name:ce.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{children:Hs("common.profile_settings")})]}),h.jsxs(ie,{onClick:()=>{s?.(),c(!1)},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 text-red-600 dark:text-red-400 transition-colors",children:[h.jsx(se,{name:ce.ArrowRight,className:"w-5 h-5"}),h.jsx(Q,{children:Hs("common.logout")})]})]})]})]})]})},jy=({notifications:e=[],unreadCount:t=0,hasMore:n=!1,loading:r=!1,titleText:s="Notifications",emptyText:o="No notifications",markAllReadText:i="Mark all as read",deleteAllText:a="Delete all",unreadOnlyText:l="Unread only",unreadOnly:c=!1,onNotificationClick:d,onClose:u,onLoadMore:f,onMarkAllRead:g,onDeleteAll:m,onDelete:p,onUnreadOnlyToggle:y,dropdownAlign:b="right",className:_="",id:x,editorAttrs:M})=>{const[C,k]=v.useState(!1),N=v.useRef(null),S=v.useRef(null),w=v.useRef(null),E=v.useRef(null),D=v.useRef(new Set),P=v.useRef(f),L=v.useRef(n),$=v.useRef(r),A=v.useRef(e.length),O=v.useRef(-1);v.useEffect(()=>{P.current=f,L.current=n,$.current=r,A.current=e.length}),v.useEffect(()=>{if(!E.current||!w.current||!C)return;O.current=-1;const j=new IntersectionObserver(([G])=>{G.isIntersecting&&($.current||!L.current||O.current!==A.current&&(O.current=A.current,P.current?.()))},{root:w.current,threshold:.1});return j.observe(E.current),()=>j.disconnect()},[C]),v.useEffect(()=>{if(!w.current||!C)return;const j=new IntersectionObserver(U=>{U.forEach(W=>{const Y=W.target.getAttribute("data-notification-id"),V=W.target.getAttribute("data-unread")==="true";Y&&V&&W.isIntersecting&&D.current.add(Y)})},{root:w.current,threshold:.5});return w.current.querySelectorAll("[data-notification-id]").forEach(U=>j.observe(U)),()=>j.disconnect()},[C,e]);const T=t>0?t:e.filter(j=>!j.read).length,z=v.useCallback(()=>{if(C){if(T>0){const j=Array.from(D.current).filter(G=>{const U=e.find(W=>String(W.id)===String(G));return U&&!U.read});j.length>0&&u?.(j)}D.current.clear(),k(!1)}},[C,u,T,e]);v.useEffect(()=>{if(!C)return;const j=G=>{N.current&&!N.current.contains(G.target)&&z()};return document.addEventListener("mousedown",j),()=>document.removeEventListener("mousedown",j)},[C,z]);const H=v.useCallback(()=>{C?z():k(!0)},[C,z]);return v.useLayoutEffect(()=>{const j=S.current;if(!C||!j)return;j.style.transform="";const G=j.getBoundingClientRect(),U=typeof window<"u"?window.innerWidth:0;if(G.width===0&&G.height===0)return;const W=8;let Y=0;G.right>U-W?Y=-(G.right-(U-W)):G.left0&&h.jsx(Q,{className:"absolute top-0 right-0 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 dark:bg-red-600 rounded-full",children:T>99?"99+":T})]}),C&&h.jsxs(I,{ref:S,className:`absolute ${b==="left"?"left-0":"right-0"} mt-2 w-80 max-w-[calc(100vw-1rem)] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between gap-2",children:[h.jsxs(I,{className:"flex items-center gap-3 min-w-0",children:[h.jsx(Q,{className:"font-semibold text-gray-900 dark:text-white",children:s}),y&&h.jsxs(I,{className:"flex items-center gap-1.5 cursor-pointer select-none",onClick:()=>y(!c),children:[h.jsx(_t,{type:"checkbox",checked:c,onChange:j=>{j.stopPropagation(),y(j.target.checked)},onClick:j=>j.stopPropagation(),className:"w-3.5 h-3.5 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 cursor-pointer"}),h.jsx(Q,{className:"text-xs text-gray-600 dark:text-gray-400",children:l})]})]}),h.jsxs(I,{className:"flex items-center gap-3 flex-shrink-0",children:[g&&T>0&&h.jsx(ie,{onClick:()=>{g(),D.current.clear()},className:"text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline cursor-pointer",children:i}),m&&e.length>0&&h.jsx(ie,{onClick:()=>{m()},className:"text-xs text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 hover:underline cursor-pointer",children:a})]})]}),h.jsx(I,{ref:w,className:"max-h-96 overflow-y-auto",children:e.length===0&&!r?h.jsx(I,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:h.jsx(Q,{children:o})}):h.jsxs(h.Fragment,{children:[e.map(j=>{const G=!j.read;return h.jsxs(I,{"data-notification-id":j.id,"data-unread":G?"true":"false",onClick:()=>{j.onClick?.(),d?.(j),k(!1)},className:`group flex items-start gap-3 px-4 py-3 text-left border-b border-gray-100 dark:border-gray-700 transition-colors cursor-pointer ${G?"bg-blue-50/70 dark:bg-blue-900/20 hover:bg-blue-50 dark:hover:bg-blue-900/30":"bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/50 opacity-70"}`,children:[j.iconName&&h.jsx(se,{name:j.iconName,className:`w-5 h-5 mt-0.5 flex-shrink-0 ${G?"text-blue-600 dark:text-blue-400":"text-gray-400 dark:text-gray-500"}`}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsx(I,{className:`text-sm truncate ${G?"font-semibold text-gray-900 dark:text-white":"font-normal text-gray-600 dark:text-gray-400"}`,children:j.title}),h.jsx(I,{className:`text-sm mt-1 line-clamp-2 ${G?"text-gray-700 dark:text-gray-300":"text-gray-500 dark:text-gray-500"}`,children:j.message}),h.jsx(I,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1 font-mono",children:j.time})]}),h.jsxs(I,{className:"flex flex-col items-center gap-2 flex-shrink-0",children:[G&&h.jsx(Q,{className:"w-2 h-2 bg-blue-500 dark:bg-blue-400 rounded-full mt-1.5"}),p&&h.jsx(ie,{onClick:U=>{U.stopPropagation(),p(j)},className:"p-1 text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 rounded hover:bg-red-50 dark:hover:bg-red-900/20 cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity","aria-label":"Delete notification",children:h.jsx(se,{name:ce.Trash,className:"w-4 h-4"})})]})]},j.id)}),n&&h.jsx(I,{ref:E,className:"px-4 py-3 text-center",children:r?h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."}):h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500"})}),r&&!n&&e.length>0&&h.jsx(I,{className:"px-4 py-2 text-center",children:h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."})})]})})]})]})},vu=window.G7Core?.createLogger?.("Comp:UserProfile")??{log:(...e)=>console.log("[Comp:UserProfile]",...e),warn:(...e)=>console.warn("[Comp:UserProfile]",...e),error:(...e)=>console.error("[Comp:UserProfile]",...e)},Wy=({user:e={name:"",email:""},profileText:t="Profile Settings",logoutText:n="Logout",onProfileClick:r,onLogoutClick:s,className:o="",logoutEndpoint:i="/api/admin/auth/logout",redirectPath:a="/admin/login",showChevron:l=!0,dropdownDirection:c="up",dropdownOnly:d=!1,id:u,editorAttrs:f})=>{const[g,m]=v.useState(!1),[p,y]=v.useState(!1),b=v.useRef(null);v.useEffect(()=>{const C=k=>{b.current&&!b.current.contains(k.target)&&m(!1)};return document.addEventListener("mousedown",C),()=>document.removeEventListener("mousedown",C)},[]);const _=()=>{const k=`; ${document.cookie}`.split("; XSRF-TOKEN=");return k.length===2?decodeURIComponent(k.pop()?.split(";").shift()||""):null},x=()=>localStorage.getItem("auth_token"),M=async()=>{if(!p)try{if(y(!0),m(!1),s){s();return}const C=_(),k=x(),N=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...C&&{"X-XSRF-TOKEN":C},...k&&{Authorization:`Bearer ${k}`}},credentials:"include"});N.ok?(localStorage.removeItem("auth_token"),window.location.href=a):(vu.error("로그아웃 실패:",N.statusText),localStorage.removeItem("auth_token"),window.location.href=a)}catch(C){vu.error("로그아웃 오류:",C),localStorage.removeItem("auth_token"),window.location.href=a}finally{y(!1)}};return h.jsxs(I,{ref:b,className:d?`absolute inset-0 ${o}`:`relative ${o}`,id:u,...f,children:[h.jsx(ie,{onClick:()=>m(!g),className:d?"w-full h-full opacity-0 cursor-pointer":"flex items-center gap-2 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors w-full",children:!d&&h.jsxs(h.Fragment,{children:[e.avatar?h.jsx(Sn,{src:e.avatar,alt:e.name,className:"w-8 h-8 rounded-full object-cover"}):h.jsx(I,{className:"w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center",children:h.jsx(se,{name:ce.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:"flex-1 text-left",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:e.name}),e.role&&h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-xs",children:e.role})]}),l&&h.jsx(se,{name:ce.ChevronDown,className:"w-4 h-4 text-gray-600 dark:text-gray-400"})]})}),g&&h.jsxs(I,{className:`absolute ${c==="down"?"top-full mt-2":"bottom-full mb-2"} ${d?"right-0 w-72":"left-0 w-full"} bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors",onClick:()=>{m(!1),window.G7Core?.dispatch({handler:"navigate",params:{path:`/admin/users/${e.uuid}`}})},children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white",children:e.name}),h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-sm",children:e.email})]}),h.jsxs(I,{className:"py-2",children:[h.jsxs(ie,{onClick:()=>{m(!1),r?r():window.G7Core?.dispatch({handler:"navigate",params:{path:`/admin/users/${e.uuid}/edit`}})},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center justify-start gap-3 transition-colors",children:[h.jsx(se,{name:ce.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white",children:t})]}),h.jsxs(ie,{onClick:M,disabled:p,className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center justify-start gap-3 text-red-600 dark:text-red-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:[h.jsx(se,{name:ce.ArrowRight,className:"w-5 h-5"}),h.jsx(Q,{children:p?"Logging out...":n})]})]})]})]})},Ro=window.G7Core?.createLogger?.("Comp:LanguageSelector")??{log:(...e)=>console.log("[Comp:LanguageSelector]",...e),warn:(...e)=>console.warn("[Comp:LanguageSelector]",...e),error:(...e)=>console.error("[Comp:LanguageSelector]",...e)},xu=e=>{const t=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[e];return t||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[e]||e},Gy=({availableLocales:e=["ko","en"],languageText:t="Language",apiEndpoint:n="/api/admin/users/me/language",onLanguageChange:r,className:s="",inline:o=!1,showCode:i=!1,id:a,editorAttrs:l})=>{const[c,d]=v.useState(!1),[u,f]=v.useState(!1),[g,m]=v.useState(()=>localStorage.getItem("g7_locale")||"ko"),p=v.useRef(null);v.useEffect(()=>{if(o)return;const x=M=>{p.current&&!p.current.contains(M.target)&&d(!1)};return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[o]);const y=()=>{const M=`; ${document.cookie}`.split("; XSRF-TOKEN=");return M.length===2?decodeURIComponent(M.pop()?.split(";").shift()||""):null},b=()=>localStorage.getItem("auth_token"),_=async x=>{if(!(u||x===g))try{f(!0);const M=y(),C=b();try{const k=await fetch(n,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json",...M&&{"X-XSRF-TOKEN":M},...C&&{Authorization:`Bearer ${C}`}},credentials:"include",body:JSON.stringify({language:x})});k.ok||Ro.warn("언어 설정 저장 실패 (UI는 변경됨):",k.statusText)}catch(k){Ro.warn("언어 설정 API 호출 실패 (UI는 변경됨):",k)}m(x),d(!1),r?.(x),window.__templateApp?await window.__templateApp.changeLocale(x):(Ro.warn("TemplateApp not found, reloading page"),window.location.reload())}catch(M){Ro.error("언어 변경 오류:",M)}finally{f(!1)}};return o?h.jsxs(I,{className:`${s}`,id:a,...l,children:[h.jsxs(ie,{onClick:()=>d(!c),disabled:u,className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors",children:[h.jsx(se,{name:ce.Globe,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white flex-1",children:t}),h.jsx(Q,{className:"px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded",children:g.toUpperCase()}),h.jsx(se,{name:ce.ChevronDown,className:`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${c?"rotate-180":""}`})]}),c&&h.jsx(I,{className:"px-4 py-2 flex flex-wrap gap-2",children:e.map(x=>h.jsx(ie,{onClick:()=>_(x),disabled:u,className:` + `,children:h.jsxs(I,{className:"flex items-start gap-3",children:[g.iconName&&h.jsx(re,{name:g.iconName,className:"w-5 h-5 text-gray-600 dark:text-gray-400 mt-0.5"}),h.jsxs(I,{className:"flex-1",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:g.title}),h.jsx(I,{className:"text-gray-600 dark:text-gray-400 text-sm mt-1",children:g.message}),h.jsx(I,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1",children:g.time})]}),!g.read&&h.jsx(Q,{className:"w-2 h-2 bg-blue-500 rounded-full mt-1.5"})]})},g.id))})]})]}),h.jsxs(I,{ref:u,className:"relative",children:[h.jsxs(oe,{onClick:()=>c(!l),className:"flex items-center gap-3 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[t.avatar?h.jsx(yn,{src:t.avatar,alt:t.name,className:"w-8 h-8 rounded-full object-cover"}):h.jsx(I,{className:"w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center",children:h.jsx(re,{name:ce.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:"hidden md:block text-left",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:t.name}),t.role&&h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-xs",children:t.role})]}),h.jsx(re,{name:ce.ChevronDown,className:"w-4 h-4 text-gray-600 dark:text-gray-400"})]}),l&&h.jsxs(I,{className:"absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white",children:t.name}),h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-sm",children:t.email})]}),h.jsxs(I,{className:"py-2",children:[h.jsxs(oe,{onClick:()=>{r?.(),c(!1)},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors text-gray-900 dark:text-white",children:[h.jsx(re,{name:ce.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{children:Es("common.profile_settings")})]}),h.jsxs(oe,{onClick:()=>{s?.(),c(!1)},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 text-red-600 dark:text-red-400 transition-colors",children:[h.jsx(re,{name:ce.ArrowRight,className:"w-5 h-5"}),h.jsx(Q,{children:Es("common.logout")})]})]})]})]})]})},ky=({notifications:t=[],unreadCount:e=0,hasMore:n=!1,loading:r=!1,titleText:s="Notifications",emptyText:o="No notifications",markAllReadText:i="Mark all as read",deleteAllText:a="Delete all",unreadOnlyText:l="Unread only",unreadOnly:c=!1,onNotificationClick:d,onClose:u,onLoadMore:f,onMarkAllRead:g,onDeleteAll:m,onDelete:p,onUnreadOnlyToggle:y,dropdownAlign:b="right",className:_="",id:x,editorAttrs:E})=>{const[C,k]=v.useState(!1),N=v.useRef(null),S=v.useRef(null),w=v.useRef(null),M=v.useRef(null),T=v.useRef(new Set),P=v.useRef(f),L=v.useRef(n),$=v.useRef(r),A=v.useRef(t.length),D=v.useRef(-1);v.useEffect(()=>{P.current=f,L.current=n,$.current=r,A.current=t.length}),v.useEffect(()=>{if(!M.current||!w.current||!C)return;D.current=-1;const j=new IntersectionObserver(([W])=>{W.isIntersecting&&($.current||!L.current||D.current!==A.current&&(D.current=A.current,P.current?.()))},{root:w.current,threshold:.1});return j.observe(M.current),()=>j.disconnect()},[C]),v.useEffect(()=>{if(!w.current||!C)return;const j=new IntersectionObserver(U=>{U.forEach(B=>{const G=B.target.getAttribute("data-notification-id"),V=B.target.getAttribute("data-unread")==="true";G&&V&&B.isIntersecting&&T.current.add(G)})},{root:w.current,threshold:.5});return w.current.querySelectorAll("[data-notification-id]").forEach(U=>j.observe(U)),()=>j.disconnect()},[C,t]);const O=e>0?e:t.filter(j=>!j.read).length,z=v.useCallback(()=>{if(C){if(O>0){const j=Array.from(T.current).filter(W=>{const U=t.find(B=>String(B.id)===String(W));return U&&!U.read});j.length>0&&u?.(j)}T.current.clear(),k(!1)}},[C,u,O,t]);v.useEffect(()=>{if(!C)return;const j=W=>{N.current&&!N.current.contains(W.target)&&z()};return document.addEventListener("mousedown",j),()=>document.removeEventListener("mousedown",j)},[C,z]);const H=v.useCallback(()=>{C?z():k(!0)},[C,z]);return v.useLayoutEffect(()=>{const j=S.current;if(!C||!j)return;j.style.transform="";const W=j.getBoundingClientRect(),U=typeof window<"u"?window.innerWidth:0;if(W.width===0&&W.height===0)return;const B=8;let G=0;W.right>U-B?G=-(W.right-(U-B)):W.left0&&h.jsx(Q,{className:"absolute top-0 right-0 flex items-center justify-center w-5 h-5 text-xs font-bold text-white bg-red-500 dark:bg-red-600 rounded-full",children:O>99?"99+":O})]}),C&&h.jsxs(I,{ref:S,className:`absolute ${b==="left"?"left-0":"right-0"} mt-2 w-80 max-w-[calc(100vw-1rem)] bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between gap-2",children:[h.jsxs(I,{className:"flex items-center gap-3 min-w-0",children:[h.jsx(Q,{className:"font-semibold text-gray-900 dark:text-white",children:s}),y&&h.jsxs(I,{className:"flex items-center gap-1.5 cursor-pointer select-none",onClick:()=>y(!c),children:[h.jsx(kt,{type:"checkbox",checked:c,onChange:j=>{j.stopPropagation(),y(j.target.checked)},onClick:j=>j.stopPropagation(),className:"w-3.5 h-3.5 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400 cursor-pointer"}),h.jsx(Q,{className:"text-xs text-gray-600 dark:text-gray-400",children:l})]})]}),h.jsxs(I,{className:"flex items-center gap-3 flex-shrink-0",children:[g&&O>0&&h.jsx(oe,{onClick:()=>{g(),T.current.clear()},className:"text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 hover:underline cursor-pointer",children:i}),m&&t.length>0&&h.jsx(oe,{onClick:()=>{m()},className:"text-xs text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 hover:underline cursor-pointer",children:a})]})]}),h.jsx(I,{ref:w,className:"max-h-96 overflow-y-auto",children:t.length===0&&!r?h.jsx(I,{className:"px-4 py-8 text-center text-gray-500 dark:text-gray-400",children:h.jsx(Q,{children:o})}):h.jsxs(h.Fragment,{children:[t.map(j=>{const W=!j.read;return h.jsxs(I,{"data-notification-id":j.id,"data-unread":W?"true":"false",onClick:()=>{j.onClick?.(),d?.(j),k(!1)},className:`group flex items-start gap-3 px-4 py-3 text-left border-b border-gray-100 dark:border-gray-700 transition-colors cursor-pointer ${W?"bg-blue-50/70 dark:bg-blue-900/20 hover:bg-blue-50 dark:hover:bg-blue-900/30":"bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700/50 opacity-70"}`,children:[j.iconName&&h.jsx(re,{name:j.iconName,className:`w-5 h-5 mt-0.5 flex-shrink-0 ${W?"text-blue-600 dark:text-blue-400":"text-gray-400 dark:text-gray-500"}`}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsx(I,{className:`text-sm truncate ${W?"font-semibold text-gray-900 dark:text-white":"font-normal text-gray-600 dark:text-gray-400"}`,children:j.title}),h.jsx(I,{className:`text-sm mt-1 line-clamp-2 ${W?"text-gray-700 dark:text-gray-300":"text-gray-500 dark:text-gray-500"}`,children:j.message}),h.jsx(I,{className:"text-gray-400 dark:text-gray-500 text-xs mt-1 font-mono",children:j.time})]}),h.jsxs(I,{className:"flex flex-col items-center gap-2 flex-shrink-0",children:[W&&h.jsx(Q,{className:"w-2 h-2 bg-blue-500 dark:bg-blue-400 rounded-full mt-1.5"}),p&&h.jsx(oe,{onClick:U=>{U.stopPropagation(),p(j)},className:"p-1 text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400 rounded hover:bg-red-50 dark:hover:bg-red-900/20 cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity","aria-label":"Delete notification",children:h.jsx(re,{name:ce.Trash,className:"w-4 h-4"})})]})]},j.id)}),n&&h.jsx(I,{ref:M,className:"px-4 py-3 text-center",children:r?h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."}):h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500"})}),r&&!n&&t.length>0&&h.jsx(I,{className:"px-4 py-2 text-center",children:h.jsx(Q,{className:"text-sm text-gray-400 dark:text-gray-500",children:"..."})})]})})]})]})},au=window.G7Core?.createLogger?.("Comp:UserProfile")??{log:(...t)=>console.log("[Comp:UserProfile]",...t),warn:(...t)=>console.warn("[Comp:UserProfile]",...t),error:(...t)=>console.error("[Comp:UserProfile]",...t)},Cy=({user:t={name:"",email:""},profileText:e="Profile Settings",logoutText:n="Logout",onProfileClick:r,onLogoutClick:s,className:o="",logoutEndpoint:i="/api/admin/auth/logout",redirectPath:a="/admin/login",showChevron:l=!0,dropdownDirection:c="up",dropdownOnly:d=!1,id:u,editorAttrs:f})=>{const[g,m]=v.useState(!1),[p,y]=v.useState(!1),b=v.useRef(null);v.useEffect(()=>{const C=k=>{b.current&&!b.current.contains(k.target)&&m(!1)};return document.addEventListener("mousedown",C),()=>document.removeEventListener("mousedown",C)},[]);const _=()=>{const k=`; ${document.cookie}`.split("; XSRF-TOKEN=");return k.length===2?decodeURIComponent(k.pop()?.split(";").shift()||""):null},x=()=>localStorage.getItem("auth_token"),E=async()=>{if(!p)try{if(y(!0),m(!1),s){s();return}const C=_(),k=x(),N=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json",...C&&{"X-XSRF-TOKEN":C},...k&&{Authorization:`Bearer ${k}`}},credentials:"include"});N.ok?(localStorage.removeItem("auth_token"),window.location.href=a):(au.error("로그아웃 실패:",N.statusText),localStorage.removeItem("auth_token"),window.location.href=a)}catch(C){au.error("로그아웃 오류:",C),localStorage.removeItem("auth_token"),window.location.href=a}finally{y(!1)}};return h.jsxs(I,{ref:b,className:d?`absolute inset-0 ${o}`:`relative ${o}`,id:u,...f,children:[h.jsx(oe,{onClick:()=>m(!g),className:d?"w-full h-full opacity-0 cursor-pointer":"flex items-center gap-2 px-3 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors w-full",children:!d&&h.jsxs(h.Fragment,{children:[t.avatar?h.jsx(yn,{src:t.avatar,alt:t.name,className:"w-8 h-8 rounded-full object-cover"}):h.jsx(I,{className:"w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center",children:h.jsx(re,{name:ce.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:"flex-1 text-left",children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white text-sm",children:t.name}),t.role&&h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-xs",children:t.role})]}),l&&h.jsx(re,{name:ce.ChevronDown,className:"w-4 h-4 text-gray-600 dark:text-gray-400"})]})}),g&&h.jsxs(I,{className:`absolute ${c==="down"?"top-full mt-2":"bottom-full mb-2"} ${d?"right-0 w-72":"left-0 w-full"} bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50`,children:[h.jsxs(I,{className:"px-4 py-3 border-b border-gray-200 dark:border-gray-700 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors",onClick:()=>{m(!1),window.G7Core?.dispatch({handler:"navigate",params:{path:`/admin/users/${t.uuid}`}})},children:[h.jsx(I,{className:"font-semibold text-gray-900 dark:text-white",children:t.name}),h.jsx(I,{className:"text-gray-500 dark:text-gray-400 text-sm",children:t.email})]}),h.jsxs(I,{className:"py-2",children:[h.jsxs(oe,{onClick:()=>{m(!1),r?r():window.G7Core?.dispatch({handler:"navigate",params:{path:`/admin/users/${t.uuid}/edit`}})},className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center justify-start gap-3 transition-colors",children:[h.jsx(re,{name:ce.User,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white",children:e})]}),h.jsxs(oe,{onClick:E,disabled:p,className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center justify-start gap-3 text-red-600 dark:text-red-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",children:[h.jsx(re,{name:ce.ArrowRight,className:"w-5 h-5"}),h.jsx(Q,{children:p?"Logging out...":n})]})]})]})]})},jo=window.G7Core?.createLogger?.("Comp:LanguageSelector")??{log:(...t)=>console.log("[Comp:LanguageSelector]",...t),warn:(...t)=>console.warn("[Comp:LanguageSelector]",...t),error:(...t)=>console.error("[Comp:LanguageSelector]",...t)},lu=t=>{const e=window.G7Core?.state?.get?.("_global.appConfig.localeNames")?.[t];return e||{ko:"한국어",en:"English",ja:"日本語",zh:"中文",es:"Español",fr:"Français",de:"Deutsch"}[t]||t},Sy=({availableLocales:t=["ko","en"],languageText:e="Language",apiEndpoint:n="/api/admin/users/me/language",onLanguageChange:r,className:s="",inline:o=!1,showCode:i=!1,id:a,editorAttrs:l})=>{const[c,d]=v.useState(!1),[u,f]=v.useState(!1),[g,m]=v.useState(()=>localStorage.getItem("g7_locale")||"ko"),p=v.useRef(null);v.useEffect(()=>{if(o)return;const x=E=>{p.current&&!p.current.contains(E.target)&&d(!1)};return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[o]);const y=()=>{const E=`; ${document.cookie}`.split("; XSRF-TOKEN=");return E.length===2?decodeURIComponent(E.pop()?.split(";").shift()||""):null},b=()=>localStorage.getItem("auth_token"),_=async x=>{if(!(u||x===g))try{f(!0);const E=y(),C=b();try{const k=await fetch(n,{method:"PATCH",headers:{"Content-Type":"application/json",Accept:"application/json",...E&&{"X-XSRF-TOKEN":E},...C&&{Authorization:`Bearer ${C}`}},credentials:"include",body:JSON.stringify({language:x})});k.ok||jo.warn("언어 설정 저장 실패 (UI는 변경됨):",k.statusText)}catch(k){jo.warn("언어 설정 API 호출 실패 (UI는 변경됨):",k)}m(x),d(!1),r?.(x),window.__templateApp?await window.__templateApp.changeLocale(x):(jo.warn("TemplateApp not found, reloading page"),window.location.reload())}catch(E){jo.error("언어 변경 오류:",E)}finally{f(!1)}};return o?h.jsxs(I,{className:`${s}`,id:a,...l,children:[h.jsxs(oe,{onClick:()=>d(!c),disabled:u,className:"w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors",children:[h.jsx(re,{name:ce.Globe,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white flex-1",children:e}),h.jsx(Q,{className:"px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded",children:g.toUpperCase()}),h.jsx(re,{name:ce.ChevronDown,className:`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${c?"rotate-180":""}`})]}),c&&h.jsx(I,{className:"px-4 py-2 flex flex-wrap gap-2",children:t.map(x=>h.jsx(oe,{onClick:()=>_(x),disabled:u,className:` px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${g===x?"bg-blue-600 text-white":"bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} ${u?"opacity-50 cursor-not-allowed":""} - `,children:xu(x)},x))})]}):h.jsxs(I,{ref:p,className:`relative ${s}`,id:a,...l,children:[h.jsxs(ie,{onClick:()=>d(!c),className:i?"flex items-center gap-1.5 px-2.5 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors cursor-pointer":"p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors","aria-haspopup":"listbox","aria-expanded":c,"aria-label":t,children:[h.jsx(se,{name:ce.Globe,className:i?"w-4 h-4":"w-5 h-5 text-gray-600 dark:text-gray-400"}),i&&h.jsx(Q,{className:"font-medium uppercase",children:g}),i&&h.jsx(se,{name:ce.ChevronDown,className:"w-3 h-3"})]}),c&&h.jsx(I,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:h.jsx(I,{className:"py-2",children:e.map(x=>h.jsxs(ie,{onClick:()=>_(x),disabled:u,className:` + `,children:lu(x)},x))})]}):h.jsxs(I,{ref:p,className:`relative ${s}`,id:a,...l,children:[h.jsxs(oe,{onClick:()=>d(!c),className:i?"flex items-center gap-1.5 px-2.5 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors cursor-pointer":"p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors","aria-haspopup":"listbox","aria-expanded":c,"aria-label":e,children:[h.jsx(re,{name:ce.Globe,className:i?"w-4 h-4":"w-5 h-5 text-gray-600 dark:text-gray-400"}),i&&h.jsx(Q,{className:"font-medium uppercase",children:g}),i&&h.jsx(re,{name:ce.ChevronDown,className:"w-3 h-3"})]}),c&&h.jsx(I,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:h.jsx(I,{className:"py-2",children:t.map(x=>h.jsxs(oe,{onClick:()=>_(x),disabled:u,className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${g===x?"bg-gray-50 dark:bg-gray-700":""} ${u?"opacity-50 cursor-not-allowed":""} - `,children:[h.jsx(Q,{className:"text-gray-900 dark:text-white flex-1",children:xu(x)}),g===x&&h.jsx(se,{name:ce.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400"})]},x))})})]})},Yy=({copyright:e="© 2026 G7",version:t,quickLinks:n=[],className:r="",changelogModalId:s,copyrightModalId:o,id:i,editorAttrs:a})=>{const l=v.useCallback(()=>{s&&window.G7Core?.dispatch?.({handler:"sequence",actions:[{handler:"apiCall",auth_required:!0,target:"/api/admin/changelog",params:{method:"GET"},onSuccess:[{handler:"setState",params:{target:"global",coreChangelogContent:"{{response.data.content}}"}},{handler:"openModal",target:s}],onError:[{handler:"openModal",target:s}]}]})},[s]),c=v.useCallback(()=>{o&&window.G7Core?.dispatch?.({handler:"sequence",actions:[{handler:"apiCall",auth_required:!0,target:"/api/admin/license",params:{method:"GET"},onSuccess:[{handler:"setState",params:{target:"global",coreLicenseContent:"{{response.data.content}}"}},{handler:"openModal",target:o}],onError:[{handler:"openModal",target:o}]}]})},[o]);return h.jsx(I,{className:`admin-footer ${r}`,id:i,...a,children:h.jsxs(I,{className:"admin-footer-row",children:[h.jsxs(I,{className:"admin-footer-group",children:[h.jsx(Q,{className:o?"admin-footer-link-hover":"",onClick:o?c:void 0,children:e}),t&&h.jsxs(h.Fragment,{children:[h.jsx(Q,{className:"hidden md:inline",children:"•"}),h.jsxs(Q,{className:`flex-center gap-2 ${s?"admin-footer-link-hover":""}`,onClick:s?l:void 0,children:[h.jsx(se,{name:ce.Tag,className:"w-4 h-4"}),h.jsxs(Q,{children:["v",t]})]})]})]}),n.length>0&&h.jsx(I,{className:"flex-center gap-4",children:n.map(d=>h.jsxs(Kr,{href:d.url,className:"admin-footer-link",children:[d.iconName&&h.jsx(se,{name:d.iconName,className:"w-4 h-4"}),h.jsx(Q,{children:d.label})]},d.id))})]})})},Xy=({title:e,description:t,breadcrumbItems:n,tabs:r,onTabChange:s,actions:o,className:i="",children:a,id:l,editorAttrs:c})=>{const d=(u="secondary")=>({primary:"page-action-primary",secondary:"page-action-secondary",danger:"page-action-danger",ghost:"page-action-ghost"})[u];return h.jsxs(I,{className:i,id:l,...c,children:[n&&n.length>0&&h.jsx(I,{className:"mb-md",children:h.jsx(bu,{items:n})}),h.jsxs(I,{className:"flex-between mb-lg",children:[h.jsxs(I,{children:[h.jsx(ja,{className:"page-title",children:e}),t&&h.jsx(wt,{className:"page-description",children:t})]}),a?h.jsx(I,{className:"flex-center page-actions",children:a}):o&&o.length>0?h.jsx(I,{className:"page-actions",children:o.map(u=>h.jsxs(ie,{onClick:u.onClick,disabled:u.disabled,className:d(u.variant),children:[u.iconName&&h.jsx(se,{name:u.iconName,className:"icon-md"}),u.variant!=="ghost"&&u.label&&h.jsx(I,{children:u.label})]},u.id))}):null]}),r&&r.length>0&&h.jsx(I,{className:"tab-container",children:r.map(u=>h.jsxs(ie,{onClick:()=>s?.(u.value),className:u.active?"tab-btn-active":"tab-btn",children:[h.jsx(I,{children:u.label}),u.badge!==void 0&&h.jsx(I,{className:u.active?"tab-badge-active":"tab-badge",children:u.badge})]},u.id))})]})},Ky=()=>window.G7Core,qy=({title:e,description:t,className:n="",style:r,children:s,id:o,editorAttrs:i})=>{const a="flex items-center justify-between",l=Ky()?.style?.mergeClasses?.(a,n)??`${a} ${n}`;return h.jsxs(I,{className:l,style:r,id:o,...i,children:[h.jsxs(I,{children:[e&&h.jsx(ar,{className:"text-base font-semibold text-gray-900 dark:text-white",children:e}),t&&h.jsx(wt,{className:"mt-1 text-sm text-gray-500 dark:text-gray-400",children:t})]}),s&&h.jsx(I,{className:"flex items-center gap-2",children:s})]})},cr=(e,t)=>window.G7Core?.t?.(e,t)??e,Zy=({image:e,imageAlt:t,vendor:n,name:r,version:s,status:o,updateAvailable:i=!1,latestVersion:a,dependencies:l=[],showLayoutEditButton:c=!1,actions:d=[],onLayoutEditClick:u,className:f="",id:g,editorAttrs:m})=>{const p=b=>({active:"success",inactive:"default",pending:"pending",error:"error"})[b],y=b=>({active:cr("common.status_active"),inactive:cr("common.status_inactive"),pending:cr("common.status_pending"),error:cr("common.status_error")})[b];return h.jsxs(I,{className:` + `,children:[h.jsx(Q,{className:"text-gray-900 dark:text-white flex-1",children:lu(x)}),g===x&&h.jsx(re,{name:ce.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400"})]},x))})})]})},_y=({copyright:t="© 2026 G7",version:e,quickLinks:n=[],className:r="",changelogModalId:s,copyrightModalId:o,id:i,editorAttrs:a})=>{const l=v.useCallback(()=>{s&&window.G7Core?.dispatch?.({handler:"sequence",actions:[{handler:"apiCall",auth_required:!0,target:"/api/admin/changelog",params:{method:"GET"},onSuccess:[{handler:"setState",params:{target:"global",coreChangelogContent:"{{response.data.content}}"}},{handler:"openModal",target:s}],onError:[{handler:"openModal",target:s}]}]})},[s]),c=v.useCallback(()=>{o&&window.G7Core?.dispatch?.({handler:"sequence",actions:[{handler:"apiCall",auth_required:!0,target:"/api/admin/license",params:{method:"GET"},onSuccess:[{handler:"setState",params:{target:"global",coreLicenseContent:"{{response.data.content}}"}},{handler:"openModal",target:o}],onError:[{handler:"openModal",target:o}]}]})},[o]);return h.jsx(I,{className:`admin-footer ${r}`,id:i,...a,children:h.jsxs(I,{className:"admin-footer-row",children:[h.jsxs(I,{className:"admin-footer-group",children:[h.jsx(Q,{className:o?"admin-footer-link-hover":"",onClick:o?c:void 0,children:t}),e&&h.jsxs(h.Fragment,{children:[h.jsx(Q,{className:"hidden md:inline",children:"•"}),h.jsxs(Q,{className:`flex-center gap-2 ${s?"admin-footer-link-hover":""}`,onClick:s?l:void 0,children:[h.jsx(re,{name:ce.Tag,className:"w-4 h-4"}),h.jsxs(Q,{children:["v",e]})]})]})]}),n.length>0&&h.jsx(I,{className:"flex-center gap-4",children:n.map(d=>h.jsxs(Lr,{href:d.url,className:"admin-footer-link",children:[d.iconName&&h.jsx(re,{name:d.iconName,className:"w-4 h-4"}),h.jsx(Q,{children:d.label})]},d.id))})]})})},Ey=({title:t,description:e,breadcrumbItems:n,tabs:r,onTabChange:s,actions:o,className:i="",children:a,id:l,editorAttrs:c})=>{const d=(u="secondary")=>({primary:"page-action-primary",secondary:"page-action-secondary",danger:"page-action-danger",ghost:"page-action-ghost"})[u];return h.jsxs(I,{className:i,id:l,...c,children:[n&&n.length>0&&h.jsx(I,{className:"mb-md",children:h.jsx(ou,{items:n})}),h.jsxs(I,{className:"flex-between mb-lg",children:[h.jsxs(I,{children:[h.jsx(Da,{className:"page-title",children:t}),e&&h.jsx(yt,{className:"page-description",children:e})]}),a?h.jsx(I,{className:"flex-center page-actions",children:a}):o&&o.length>0?h.jsx(I,{className:"page-actions",children:o.map(u=>h.jsxs(oe,{onClick:u.onClick,disabled:u.disabled,className:d(u.variant),children:[u.iconName&&h.jsx(re,{name:u.iconName,className:"icon-md"}),u.variant!=="ghost"&&u.label&&h.jsx(I,{children:u.label})]},u.id))}):null]}),r&&r.length>0&&h.jsx(I,{className:"tab-container",children:r.map(u=>h.jsxs(oe,{onClick:()=>s?.(u.value),className:u.active?"tab-btn-active":"tab-btn",children:[h.jsx(I,{children:u.label}),u.badge!==void 0&&h.jsx(I,{className:u.active?"tab-badge-active":"tab-badge",children:u.badge})]},u.id))})]})},My=()=>window.G7Core,Ny=({title:t,description:e,className:n="",style:r,children:s,id:o,editorAttrs:i})=>{const a="flex items-center justify-between",l=My()?.style?.mergeClasses?.(a,n)??`${a} ${n}`;return h.jsxs(I,{className:l,style:r,id:o,...i,children:[h.jsxs(I,{children:[t&&h.jsx(Jn,{className:"text-base font-semibold text-gray-900 dark:text-white",children:t}),e&&h.jsx(yt,{className:"mt-1 text-sm text-gray-500 dark:text-gray-400",children:e})]}),s&&h.jsx(I,{className:"flex items-center gap-2",children:s})]})},er=(t,e)=>window.G7Core?.t?.(t,e)??t,Oy=({image:t,imageAlt:e,vendor:n,name:r,version:s,status:o,updateAvailable:i=!1,latestVersion:a,dependencies:l=[],showLayoutEditButton:c=!1,actions:d=[],onLayoutEditClick:u,className:f="",id:g,editorAttrs:m})=>{const p=b=>({active:"success",inactive:"default",pending:"pending",error:"error"})[b],y=b=>({active:er("common.status_active"),inactive:er("common.status_inactive"),pending:er("common.status_pending"),error:er("common.status_error")})[b];return h.jsxs(I,{className:` bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden hover:shadow-lg transition-shadow ${f} - `,id:g,...m,children:[e&&h.jsxs(I,{className:"relative",children:[h.jsx(Sn,{src:e,alt:t||cr("common.template_preview",{vendor:n,name:r}),className:"w-full h-48 object-cover"}),h.jsx(I,{className:"absolute top-3 right-3",children:h.jsx(qo,{status:p(o),label:y(o),showIcon:!0})}),i&&h.jsx(I,{className:"absolute top-3 left-3",children:h.jsxs(Q,{className:"flex items-center gap-1 px-2 py-1 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 border border-yellow-200 dark:border-yellow-800 rounded-md text-xs font-semibold",children:[h.jsx(se,{name:ce.ExclamationTriangle,className:"w-3 h-3"}),h.jsx(Q,{children:cr("common.update_available")})]})})]}),h.jsxs(I,{className:"p-4",children:[h.jsxs(I,{className:"flex items-start justify-between mb-3",children:[h.jsxs(I,{className:"flex-1",children:[h.jsxs(Xr,{className:"text-lg font-bold text-gray-900 dark:text-white",children:[n,"/",r]}),h.jsxs(I,{className:"flex items-center gap-2 mt-1 text-sm text-gray-600 dark:text-gray-400",children:[h.jsxs(Q,{children:["v",s]}),i&&a&&h.jsxs(h.Fragment,{children:[h.jsx(Q,{children:"→"}),h.jsxs(Q,{className:"text-yellow-600 dark:text-yellow-400 font-semibold",children:["v",a]})]})]}),!e&&h.jsx(I,{className:"mt-2",children:h.jsx(qo,{status:p(o),label:y(o),showIcon:!0})})]}),d.length>0&&h.jsx(Ps,{items:d,triggerIconName:ce.EllipsisVertical,position:"left"})]}),l.length>0&&h.jsxs(I,{className:"mb-3",children:[h.jsxs(wt,{className:"text-xs text-gray-500 dark:text-gray-400 mb-1",children:[cr("common.dependencies"),":"]}),h.jsx(I,{className:"flex flex-wrap gap-1",children:l.map((b,_)=>h.jsx(Q,{className:"px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs rounded",children:b},_))})]}),c&&u&&h.jsxs(ie,{onClick:u,className:"w-full px-4 py-2 bg-blue-600 dark:bg-blue-500 text-white rounded-md hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors flex items-center justify-center gap-2 font-medium",children:[h.jsx(se,{name:ce.Edit,className:"w-4 h-4"}),h.jsx(Q,{children:cr("common.edit_layout")})]})]})]})},wu=({type:e,message:t,dismissible:n=!1,onDismiss:r,className:s="",id:o,editorAttrs:i})=>{const a={info:{containerClass:"alert-info",iconClass:"alert-icon-info",textClass:"alert-text-info",icon:ce.InfoCircle},success:{containerClass:"alert-success",iconClass:"alert-icon-success",textClass:"alert-text-success",icon:ce.CheckCircle},warning:{containerClass:"alert-warning",iconClass:"alert-icon-warning",textClass:"alert-text-warning",icon:ce.ExclamationTriangle},error:{containerClass:"alert-error",iconClass:"alert-icon-error",textClass:"alert-text-error",icon:ce.TimesCircle}},l=a[e]??a.info;return h.jsxs(I,{className:`${l.containerClass} ${s}`,role:"alert","aria-live":"polite",id:o,...i,children:[h.jsx(se,{name:l.icon,className:l.iconClass,ariaLabel:`${e} icon`}),h.jsx(wt,{className:l.textClass,children:t}),n&&h.jsx(ie,{onClick:r,className:`alert-dismiss ${l.textClass}`,"aria-label":"Close alert",children:h.jsx(se,{name:ce.Times,className:l.iconClass,ariaLabel:"Close"})})]})},Qy=({layoutName:e,onBack:t,onPreview:n,onSave:r,isSaving:s=!1,className:o=""})=>{const i=` + `,id:g,...m,children:[t&&h.jsxs(I,{className:"relative",children:[h.jsx(yn,{src:t,alt:e||er("common.template_preview",{vendor:n,name:r}),className:"w-full h-48 object-cover"}),h.jsx(I,{className:"absolute top-3 right-3",children:h.jsx(Fo,{status:p(o),label:y(o),showIcon:!0})}),i&&h.jsx(I,{className:"absolute top-3 left-3",children:h.jsxs(Q,{className:"flex items-center gap-1 px-2 py-1 bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-400 border border-yellow-200 dark:border-yellow-800 rounded-md text-xs font-semibold",children:[h.jsx(re,{name:ce.ExclamationTriangle,className:"w-3 h-3"}),h.jsx(Q,{children:er("common.update_available")})]})})]}),h.jsxs(I,{className:"p-4",children:[h.jsxs(I,{className:"flex items-start justify-between mb-3",children:[h.jsxs(I,{className:"flex-1",children:[h.jsxs(Ir,{className:"text-lg font-bold text-gray-900 dark:text-white",children:[n,"/",r]}),h.jsxs(I,{className:"flex items-center gap-2 mt-1 text-sm text-gray-600 dark:text-gray-400",children:[h.jsxs(Q,{children:["v",s]}),i&&a&&h.jsxs(h.Fragment,{children:[h.jsx(Q,{children:"→"}),h.jsxs(Q,{className:"text-yellow-600 dark:text-yellow-400 font-semibold",children:["v",a]})]})]}),!t&&h.jsx(I,{className:"mt-2",children:h.jsx(Fo,{status:p(o),label:y(o),showIcon:!0})})]}),d.length>0&&h.jsx(ys,{items:d,triggerIconName:ce.EllipsisVertical,position:"left"})]}),l.length>0&&h.jsxs(I,{className:"mb-3",children:[h.jsxs(yt,{className:"text-xs text-gray-500 dark:text-gray-400 mb-1",children:[er("common.dependencies"),":"]}),h.jsx(I,{className:"flex flex-wrap gap-1",children:l.map((b,_)=>h.jsx(Q,{className:"px-2 py-0.5 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 text-xs rounded",children:b},_))})]}),c&&u&&h.jsxs(oe,{onClick:u,className:"w-full px-4 py-2 bg-blue-600 dark:bg-blue-500 text-white rounded-md hover:bg-blue-700 dark:hover:bg-blue-600 transition-colors flex items-center justify-center gap-2 font-medium",children:[h.jsx(re,{name:ce.Edit,className:"w-4 h-4"}),h.jsx(Q,{children:er("common.edit_layout")})]})]})]})},cu=({type:t,message:e,dismissible:n=!1,onDismiss:r,className:s="",id:o,editorAttrs:i})=>{const a={info:{containerClass:"alert-info",iconClass:"alert-icon-info",textClass:"alert-text-info",icon:ce.InfoCircle},success:{containerClass:"alert-success",iconClass:"alert-icon-success",textClass:"alert-text-success",icon:ce.CheckCircle},warning:{containerClass:"alert-warning",iconClass:"alert-icon-warning",textClass:"alert-text-warning",icon:ce.ExclamationTriangle},error:{containerClass:"alert-error",iconClass:"alert-icon-error",textClass:"alert-text-error",icon:ce.TimesCircle}},l=a[t]??a.info;return h.jsxs(I,{className:`${l.containerClass} ${s}`,role:"alert","aria-live":"polite",id:o,...i,children:[h.jsx(re,{name:l.icon,className:l.iconClass,ariaLabel:`${t} icon`}),h.jsx(yt,{className:l.textClass,children:e}),n&&h.jsx(oe,{onClick:r,className:`alert-dismiss ${l.textClass}`,"aria-label":"Close alert",children:h.jsx(re,{name:ce.Times,className:l.iconClass,ariaLabel:"Close"})})]})},Ty=({layoutName:t,onBack:e,onPreview:n,onSave:r,isSaving:s=!1,className:o=""})=>{const i=` flex items-center justify-between gap-4 p-4 bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 ${o} - `.trim().replace(/\s+/g," ");return h.jsxs(I,{className:i,children:[h.jsxs(I,{className:"flex items-center gap-3",children:[h.jsx(ie,{onClick:t,className:"p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors bg-transparent border-0 cursor-pointer","aria-label":"뒤로가기",children:h.jsx(se,{name:ce.ArrowLeft,className:"text-gray-700 dark:text-gray-300",ariaLabel:"뒤로가기"})}),h.jsx(ar,{className:"text-xl font-semibold text-gray-900 dark:text-gray-100",children:e})]}),h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(ie,{onClick:n,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors border-0 cursor-pointer","aria-label":"미리보기",children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(se,{name:ce.Eye,className:"text-gray-700 dark:text-gray-300",ariaLabel:"미리보기 아이콘"}),h.jsx("span",{children:"미리보기"})]})}),h.jsx(ie,{onClick:r,disabled:s,className:` + `.trim().replace(/\s+/g," ");return h.jsxs(I,{className:i,children:[h.jsxs(I,{className:"flex items-center gap-3",children:[h.jsx(oe,{onClick:e,className:"p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors bg-transparent border-0 cursor-pointer","aria-label":"뒤로가기",children:h.jsx(re,{name:ce.ArrowLeft,className:"text-gray-700 dark:text-gray-300",ariaLabel:"뒤로가기"})}),h.jsx(Jn,{className:"text-xl font-semibold text-gray-900 dark:text-gray-100",children:t})]}),h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(oe,{onClick:n,className:"px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-colors border-0 cursor-pointer","aria-label":"미리보기",children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(re,{name:ce.Eye,className:"text-gray-700 dark:text-gray-300",ariaLabel:"미리보기 아이콘"}),h.jsx("span",{children:"미리보기"})]})}),h.jsx(oe,{onClick:r,disabled:s,className:` px-4 py-2 rounded-lg transition-colors border-0 cursor-pointer ${s?"bg-blue-400 dark:bg-blue-600 cursor-not-allowed":"bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-600"} text-white - `.trim().replace(/\s+/g," "),"aria-label":s?"저장 중...":"저장",children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(se,{name:s?ce.Spinner:ce.Save,className:`text-white ${s?"animate-spin":""}`,ariaLabel:s?"저장 중 아이콘":"저장 아이콘"}),h.jsx("span",{children:s?"저장 중...":"저장"})]})})]})]})},Jy=({files:e,selectedId:t,onSelect:n,className:r=""})=>{const s=a=>{try{const l=new Date(a);if(isNaN(l.getTime()))return a;const c=l.getFullYear(),d=String(l.getMonth()+1).padStart(2,"0"),u=String(l.getDate()).padStart(2,"0"),f=String(l.getHours()).padStart(2,"0"),g=String(l.getMinutes()).padStart(2,"0");return`${c}-${d}-${u} ${f}:${g}`}catch{return a}},o=a=>{n(a)},i=` + `.trim().replace(/\s+/g," "),"aria-label":s?"저장 중...":"저장",children:h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(re,{name:s?ce.Spinner:ce.Save,className:`text-white ${s?"animate-spin":""}`,ariaLabel:s?"저장 중 아이콘":"저장 아이콘"}),h.jsx("span",{children:s?"저장 중...":"저장"})]})})]})]})},Ay=({files:t,selectedId:e,onSelect:n,className:r=""})=>{const s=a=>{try{const l=new Date(a);if(isNaN(l.getTime()))return a;const c=l.getFullYear(),d=String(l.getMonth()+1).padStart(2,"0"),u=String(l.getDate()).padStart(2,"0"),f=String(l.getHours()).padStart(2,"0"),g=String(l.getMinutes()).padStart(2,"0");return`${c}-${d}-${u} ${f}:${g}`}catch{return a}},o=a=>{n(a)},i=` w-full ${r} - `.trim().replace(/\s+/g," ");return h.jsx(I,{className:i,children:e.length===0?h.jsx(I,{className:"p-4 text-center text-gray-500 dark:text-gray-400",children:"파일이 없습니다."}):h.jsx(Wa,{className:"divide-y divide-gray-200 dark:divide-gray-700",role:"listbox","aria-label":"레이아웃 파일 목록",children:e.map(a=>{const l=a.id===t,c=` + `.trim().replace(/\s+/g," ");return h.jsx(I,{className:i,children:t.length===0?h.jsx(I,{className:"p-4 text-center text-gray-500 dark:text-gray-400",children:"파일이 없습니다."}):h.jsx(Ia,{className:"divide-y divide-gray-200 dark:divide-gray-700",role:"listbox","aria-label":"레이아웃 파일 목록",children:t.map(a=>{const l=a.id===e,c=` flex items-center justify-between p-4 cursor-pointer transition-colors duration-150 ${l?"bg-blue-100 dark:bg-blue-900 border-l-4 border-blue-500":"hover:bg-gray-100 dark:hover:bg-gray-800"} @@ -106,8 +105,8 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho `.trim().replace(/\s+/g," "),u=` text-sm ${l?"text-blue-700 dark:text-blue-300":"text-gray-500 dark:text-gray-400"} - `.trim().replace(/\s+/g," ");return h.jsx(Ga,{className:c,onClick:()=>o(a.id),role:"option","aria-selected":l,tabIndex:0,onKeyDown:f=>{(f.key==="Enter"||f.key===" ")&&(f.preventDefault(),o(a.id))},children:h.jsxs(I,{className:"flex flex-col gap-1",children:[h.jsx(Q,{className:d,children:a.name}),h.jsx(Q,{className:u,children:s(a.updated_at)})]})},a.id)})})})};function ku(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n=e.length?e.apply(this,s):function(){for(var i=arguments.length,a=new Array(i),l=0;l1&&arguments[1]!==void 0?arguments[1]:{};ti.initial(e),ti.handler(t);var n={current:e},r=Us(kv)(n,t),s=Us(wv)(n),o=Us(ti.changes)(e),i=Us(xv)(n);function a(){var c=arguments.length>0&&arguments[0]!==void 0?arguments[0]:function(d){return d};return ti.selector(c),c(n.current)}function l(c){dv(r,s,o,i)(c)}return[a,l]}function xv(e,t){return Bs(t)?t(e.current):t}function wv(e,t){return e.current=Eu(Eu({},e.current),t),t}function kv(e,t,n){return Bs(t)?t(e.current):Object.keys(n).forEach(function(r){var s;return(s=t[r])===null||s===void 0?void 0:s.call(t,e.current[r])}),n}var Cv={create:vv},Sv={paths:{vs:"https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs"}};function _v(e){return function t(){for(var n=this,r=arguments.length,s=new Array(r),o=0;o=e.length?e.apply(this,s):function(){for(var i=arguments.length,a=new Array(i),l=0;lo(a.id),role:"option","aria-selected":l,tabIndex:0,onKeyDown:f=>{(f.key==="Enter"||f.key===" ")&&(f.preventDefault(),o(a.id))},children:h.jsxs(I,{className:"flex flex-col gap-1",children:[h.jsx(Q,{className:d,children:a.name}),h.jsx(Q,{className:u,children:s(a.updated_at)})]})},a.id)})})})};function du(t,e){(e==null||e>t.length)&&(e=t.length);for(var n=0,r=Array(e);n=t.length?t.apply(this,s):function(){for(var i=arguments.length,a=new Array(i),l=0;l1&&arguments[1]!==void 0?arguments[1]:{};Go.initial(t),Go.handler(e);var n={current:t},r=Ms(tv)(n,e),s=Ms(ev)(n),o=Ms(Go.changes)(t),i=Ms(Ry)(n);function a(){var c=arguments.length>0&&arguments[0]!==void 0?arguments[0]:function(d){return d};return Go.selector(c),c(n.current)}function l(c){jy(r,s,o,i)(c)}return[a,l]}function Ry(t,e){return Ns(e)?e(t.current):e}function ev(t,e){return t.current=gu(gu({},t.current),e),e}function tv(t,e,n){return Ns(e)?e(t.current):Object.keys(n).forEach(function(r){var s;return(s=e[r])===null||s===void 0?void 0:s.call(e,t.current[r])}),n}var nv={create:Jy},rv={paths:{vs:"https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs"}};function sv(t){return function e(){for(var n=this,r=arguments.length,s=new Array(r),o=0;o=t.length?t.apply(this,s):function(){for(var i=arguments.length,a=new Array(i),l=0;l{r.current=!1}:e,t)}var un=Qv;function Ws(){}function rs(e,t,n,r){return Jv(e,r)||Rv(e,t,n,r)}function Jv(e,t){return e.editor.getModel(Pu(e,t))}function Rv(e,t,n,r){return e.editor.createModel(t,n,r?Pu(e,r):void 0)}function Pu(e,t){return e.Uri.parse(t)}function ex({original:e,modified:t,language:n,originalLanguage:r,modifiedLanguage:s,originalModelPath:o,modifiedModelPath:i,keepCurrentOriginalModel:a=!1,keepCurrentModifiedModel:l=!1,theme:c="light",loading:d="Loading...",options:u={},height:f="100%",width:g="100%",className:m,wrapperProps:p={},beforeMount:y=Ws,onMount:b=Ws}){let[_,x]=v.useState(!1),[M,C]=v.useState(!0),k=v.useRef(null),N=v.useRef(null),S=v.useRef(null),w=v.useRef(b),E=v.useRef(y),D=v.useRef(!1);Lu(()=>{let A=Du.init();return A.then(O=>(N.current=O)&&C(!1)).catch(O=>O?.type!=="cancelation"&&console.error("Monaco initialization: error:",O)),()=>k.current?$():A.cancel()}),un(()=>{if(k.current&&N.current){let A=k.current.getOriginalEditor(),O=rs(N.current,e||"",r||n||"text",o||"");O!==A.getModel()&&A.setModel(O)}},[o],_),un(()=>{if(k.current&&N.current){let A=k.current.getModifiedEditor(),O=rs(N.current,t||"",s||n||"text",i||"");O!==A.getModel()&&A.setModel(O)}},[i],_),un(()=>{let A=k.current.getModifiedEditor();A.getOption(N.current.editor.EditorOption.readOnly)?A.setValue(t||""):t!==A.getValue()&&(A.executeEdits("",[{range:A.getModel().getFullModelRange(),text:t||"",forceMoveMarkers:!0}]),A.pushUndoStop())},[t],_),un(()=>{k.current?.getModel()?.original.setValue(e||"")},[e],_),un(()=>{let{original:A,modified:O}=k.current.getModel();N.current.editor.setModelLanguage(A,r||n||"text"),N.current.editor.setModelLanguage(O,s||n||"text")},[n,r,s],_),un(()=>{N.current?.editor.setTheme(c)},[c],_),un(()=>{k.current?.updateOptions(u)},[u],_);let P=v.useCallback(()=>{if(!N.current)return;E.current(N.current);let A=rs(N.current,e||"",r||n||"text",o||""),O=rs(N.current,t||"",s||n||"text",i||"");k.current?.setModel({original:A,modified:O})},[n,t,s,e,r,o,i]),L=v.useCallback(()=>{!D.current&&S.current&&(k.current=N.current.editor.createDiffEditor(S.current,{automaticLayout:!0,...u}),P(),N.current?.editor.setTheme(c),x(!0),D.current=!0)},[u,c,P]);v.useEffect(()=>{_&&w.current(k.current,N.current)},[_]),v.useEffect(()=>{!M&&!_&&L()},[M,_,L]);function $(){let A=k.current?.getModel();a||A?.original?.dispose(),l||A?.modified?.dispose(),k.current?.dispose()}return v.createElement(Iu,{width:g,height:f,isEditorReady:_,loading:d,_ref:S,className:m,wrapperProps:p})}var tx=ex;v.memo(tx);function nx(e){let t=v.useRef();return v.useEffect(()=>{t.current=e},[e]),t.current}var rx=nx,ri=new Map;function sx({defaultValue:e,defaultLanguage:t,defaultPath:n,value:r,language:s,path:o,theme:i="light",line:a,loading:l="Loading...",options:c={},overrideServices:d={},saveViewState:u=!0,keepCurrentModel:f=!1,width:g="100%",height:m="100%",className:p,wrapperProps:y={},beforeMount:b=Ws,onMount:_=Ws,onChange:x,onValidate:M=Ws}){let[C,k]=v.useState(!1),[N,S]=v.useState(!0),w=v.useRef(null),E=v.useRef(null),D=v.useRef(null),P=v.useRef(_),L=v.useRef(b),$=v.useRef(),A=v.useRef(r),O=rx(o),T=v.useRef(!1),z=v.useRef(!1);Lu(()=>{let G=Du.init();return G.then(U=>(w.current=U)&&S(!1)).catch(U=>U?.type!=="cancelation"&&console.error("Monaco initialization: error:",U)),()=>E.current?j():G.cancel()}),un(()=>{let G=rs(w.current,e||r||"",t||s||"",o||n||"");G!==E.current?.getModel()&&(u&&ri.set(O,E.current?.saveViewState()),E.current?.setModel(G),u&&E.current?.restoreViewState(ri.get(o)))},[o],C),un(()=>{E.current?.updateOptions(c)},[c],C),un(()=>{!E.current||r===void 0||(E.current.getOption(w.current.editor.EditorOption.readOnly)?E.current.setValue(r):r!==E.current.getValue()&&(z.current=!0,E.current.executeEdits("",[{range:E.current.getModel().getFullModelRange(),text:r,forceMoveMarkers:!0}]),E.current.pushUndoStop(),z.current=!1))},[r],C),un(()=>{let G=E.current?.getModel();G&&s&&w.current?.editor.setModelLanguage(G,s)},[s],C),un(()=>{a!==void 0&&E.current?.revealLine(a)},[a],C),un(()=>{w.current?.editor.setTheme(i)},[i],C);let H=v.useCallback(()=>{if(!(!D.current||!w.current)&&!T.current){L.current(w.current);let G=o||n,U=rs(w.current,r||e||"",t||s||"",G||"");E.current=w.current?.editor.create(D.current,{model:U,automaticLayout:!0,...c},d),u&&E.current.restoreViewState(ri.get(G)),w.current.editor.setTheme(i),a!==void 0&&E.current.revealLine(a),k(!0),T.current=!0}},[e,t,n,r,s,o,c,d,u,i,a]);v.useEffect(()=>{C&&P.current(E.current,w.current)},[C]),v.useEffect(()=>{!N&&!C&&H()},[N,C,H]),A.current=r,v.useEffect(()=>{C&&x&&($.current?.dispose(),$.current=E.current?.onDidChangeModelContent(G=>{z.current||x(E.current.getValue(),G)}))},[C,x]),v.useEffect(()=>{if(C){let G=w.current.editor.onDidChangeMarkers(U=>{let W=E.current.getModel()?.uri;if(W&&U.find(Y=>Y.path===W.path)){let Y=w.current.editor.getModelMarkers({resource:W});M?.(Y)}});return()=>{G?.dispose()}}return()=>{}},[C,M]);function j(){$.current?.dispose(),f?u&&ri.set(o,E.current.saveViewState()):E.current.getModel()?.dispose(),E.current.dispose()}return v.createElement(Iu,{width:g,height:m,isEditorReady:C,loading:l,_ref:D,className:p,wrapperProps:y})}var ox=sx,ix=v.memo(ox),ax=ix;const lx=({value:e,onChange:t,language:n="json",height:r="100%",readOnly:s=!1,theme:o="vs-dark"})=>{const i=v.useRef(null),a=(c,d)=>{i.current=c,n==="json"&&d.languages.json.jsonDefaults.setDiagnosticsOptions({validate:!0,schemaValidation:"error",allowComments:!1}),c.updateOptions({readOnly:s,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,lineNumbers:"on",renderWhitespace:"selection",automaticLayout:!0})},l=c=>{t&&c!==void 0&&t({target:{value:c}})};return v.useEffect(()=>{i.current&&i.current.updateOptions({readOnly:s})},[s]),h.jsx("div",{className:"border border-gray-300 dark:border-gray-700 rounded-lg overflow-hidden",children:h.jsx(ax,{height:r,language:n,value:e,theme:o,onChange:l,onMount:a,options:{readOnly:s,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,lineNumbers:"on",renderWhitespace:"selection",automaticLayout:!0}})})},cx=e=>{const t=new Date(e);return new Intl.DateTimeFormat("ko-KR",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}).format(t)},$u=v.memo(({version:e,isSelected:t,onSelect:n})=>{const r=v.useCallback(()=>{n?.(e.id)},[n,e.id]);return h.jsx(Ga,{role:"listitem","aria-selected":t,className:` + `},mu=sv(lv)(pu),cv={config:iv},dv=function(){for(var e=arguments.length,n=new Array(e),r=0;r{r.current=!1}:t,e)}var on=Tv;function Ts(){}function Vr(t,e,n,r){return Av(t,r)||Dv(t,e,n,r)}function Av(t,e){return t.editor.getModel(Cu(t,e))}function Dv(t,e,n,r){return t.editor.createModel(e,n,r?Cu(t,r):void 0)}function Cu(t,e){return t.Uri.parse(e)}function Iv({original:t,modified:e,language:n,originalLanguage:r,modifiedLanguage:s,originalModelPath:o,modifiedModelPath:i,keepCurrentOriginalModel:a=!1,keepCurrentModifiedModel:l=!1,theme:c="light",loading:d="Loading...",options:u={},height:f="100%",width:g="100%",className:m,wrapperProps:p={},beforeMount:y=Ts,onMount:b=Ts}){let[_,x]=v.useState(!1),[E,C]=v.useState(!0),k=v.useRef(null),N=v.useRef(null),S=v.useRef(null),w=v.useRef(b),M=v.useRef(y),T=v.useRef(!1);ku(()=>{let A=xu.init();return A.then(D=>(N.current=D)&&C(!1)).catch(D=>D?.type!=="cancelation"&&console.error("Monaco initialization: error:",D)),()=>k.current?$():A.cancel()}),on(()=>{if(k.current&&N.current){let A=k.current.getOriginalEditor(),D=Vr(N.current,t||"",r||n||"text",o||"");D!==A.getModel()&&A.setModel(D)}},[o],_),on(()=>{if(k.current&&N.current){let A=k.current.getModifiedEditor(),D=Vr(N.current,e||"",s||n||"text",i||"");D!==A.getModel()&&A.setModel(D)}},[i],_),on(()=>{let A=k.current.getModifiedEditor();A.getOption(N.current.editor.EditorOption.readOnly)?A.setValue(e||""):e!==A.getValue()&&(A.executeEdits("",[{range:A.getModel().getFullModelRange(),text:e||"",forceMoveMarkers:!0}]),A.pushUndoStop())},[e],_),on(()=>{k.current?.getModel()?.original.setValue(t||"")},[t],_),on(()=>{let{original:A,modified:D}=k.current.getModel();N.current.editor.setModelLanguage(A,r||n||"text"),N.current.editor.setModelLanguage(D,s||n||"text")},[n,r,s],_),on(()=>{N.current?.editor.setTheme(c)},[c],_),on(()=>{k.current?.updateOptions(u)},[u],_);let P=v.useCallback(()=>{if(!N.current)return;M.current(N.current);let A=Vr(N.current,t||"",r||n||"text",o||""),D=Vr(N.current,e||"",s||n||"text",i||"");k.current?.setModel({original:A,modified:D})},[n,e,s,t,r,o,i]),L=v.useCallback(()=>{!T.current&&S.current&&(k.current=N.current.editor.createDiffEditor(S.current,{automaticLayout:!0,...u}),P(),N.current?.editor.setTheme(c),x(!0),T.current=!0)},[u,c,P]);v.useEffect(()=>{_&&w.current(k.current,N.current)},[_]),v.useEffect(()=>{!E&&!_&&L()},[E,_,L]);function $(){let A=k.current?.getModel();a||A?.original?.dispose(),l||A?.modified?.dispose(),k.current?.dispose()}return v.createElement(wu,{width:g,height:f,isEditorReady:_,loading:d,_ref:S,className:m,wrapperProps:p})}var Lv=Iv;v.memo(Lv);function Pv(t){let e=v.useRef();return v.useEffect(()=>{e.current=t},[t]),e.current}var $v=Pv,Xo=new Map;function Fv({defaultValue:t,defaultLanguage:e,defaultPath:n,value:r,language:s,path:o,theme:i="light",line:a,loading:l="Loading...",options:c={},overrideServices:d={},saveViewState:u=!0,keepCurrentModel:f=!1,width:g="100%",height:m="100%",className:p,wrapperProps:y={},beforeMount:b=Ts,onMount:_=Ts,onChange:x,onValidate:E=Ts}){let[C,k]=v.useState(!1),[N,S]=v.useState(!0),w=v.useRef(null),M=v.useRef(null),T=v.useRef(null),P=v.useRef(_),L=v.useRef(b),$=v.useRef(),A=v.useRef(r),D=$v(o),O=v.useRef(!1),z=v.useRef(!1);ku(()=>{let W=xu.init();return W.then(U=>(w.current=U)&&S(!1)).catch(U=>U?.type!=="cancelation"&&console.error("Monaco initialization: error:",U)),()=>M.current?j():W.cancel()}),on(()=>{let W=Vr(w.current,t||r||"",e||s||"",o||n||"");W!==M.current?.getModel()&&(u&&Xo.set(D,M.current?.saveViewState()),M.current?.setModel(W),u&&M.current?.restoreViewState(Xo.get(o)))},[o],C),on(()=>{M.current?.updateOptions(c)},[c],C),on(()=>{!M.current||r===void 0||(M.current.getOption(w.current.editor.EditorOption.readOnly)?M.current.setValue(r):r!==M.current.getValue()&&(z.current=!0,M.current.executeEdits("",[{range:M.current.getModel().getFullModelRange(),text:r,forceMoveMarkers:!0}]),M.current.pushUndoStop(),z.current=!1))},[r],C),on(()=>{let W=M.current?.getModel();W&&s&&w.current?.editor.setModelLanguage(W,s)},[s],C),on(()=>{a!==void 0&&M.current?.revealLine(a)},[a],C),on(()=>{w.current?.editor.setTheme(i)},[i],C);let H=v.useCallback(()=>{if(!(!T.current||!w.current)&&!O.current){L.current(w.current);let W=o||n,U=Vr(w.current,r||t||"",e||s||"",W||"");M.current=w.current?.editor.create(T.current,{model:U,automaticLayout:!0,...c},d),u&&M.current.restoreViewState(Xo.get(W)),w.current.editor.setTheme(i),a!==void 0&&M.current.revealLine(a),k(!0),O.current=!0}},[t,e,n,r,s,o,c,d,u,i,a]);v.useEffect(()=>{C&&P.current(M.current,w.current)},[C]),v.useEffect(()=>{!N&&!C&&H()},[N,C,H]),A.current=r,v.useEffect(()=>{C&&x&&($.current?.dispose(),$.current=M.current?.onDidChangeModelContent(W=>{z.current||x(M.current.getValue(),W)}))},[C,x]),v.useEffect(()=>{if(C){let W=w.current.editor.onDidChangeMarkers(U=>{let B=M.current.getModel()?.uri;if(B&&U.find(G=>G.path===B.path)){let G=w.current.editor.getModelMarkers({resource:B});E?.(G)}});return()=>{W?.dispose()}}return()=>{}},[C,E]);function j(){$.current?.dispose(),f?u&&Xo.set(o,M.current.saveViewState()):M.current.getModel()?.dispose(),M.current.dispose()}return v.createElement(wu,{width:g,height:m,isEditorReady:C,loading:l,_ref:T,className:p,wrapperProps:y})}var zv=Fv,Vv=v.memo(zv),Hv=Vv;const Uv=({value:t,onChange:e,language:n="json",height:r="100%",readOnly:s=!1,theme:o="vs-dark"})=>{const i=v.useRef(null),a=(c,d)=>{i.current=c,n==="json"&&d.languages.json.jsonDefaults.setDiagnosticsOptions({validate:!0,schemaValidation:"error",allowComments:!1}),c.updateOptions({readOnly:s,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,lineNumbers:"on",renderWhitespace:"selection",automaticLayout:!0})},l=c=>{e&&c!==void 0&&e({target:{value:c}})};return v.useEffect(()=>{i.current&&i.current.updateOptions({readOnly:s})},[s]),h.jsx("div",{className:"border border-gray-300 dark:border-gray-700 rounded-lg overflow-hidden",children:h.jsx(Hv,{height:r,language:n,value:t,theme:o,onChange:l,onMount:a,options:{readOnly:s,minimap:{enabled:!1},scrollBeyondLastLine:!1,fontSize:14,lineNumbers:"on",renderWhitespace:"selection",automaticLayout:!0}})})},Bv=t=>{const e=new Date(t);return new Intl.DateTimeFormat("ko-KR",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit"}).format(e)},Su=v.memo(({version:t,isSelected:e,onSelect:n})=>{const r=v.useCallback(()=>{n?.(t.id)},[n,t.id]);return h.jsx(La,{role:"listitem","aria-selected":e,className:` p-3 rounded-lg border cursor-pointer transition-colors - ${t?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500"} - `,onClick:r,children:h.jsxs(I,{className:"flex justify-between items-start",children:[h.jsxs(I,{className:"flex-1",children:[h.jsxs(Q,{className:"font-semibold text-gray-900 dark:text-gray-100",children:["버전 ",e.version]}),h.jsx(Q,{className:"ml-2 text-sm text-gray-500 dark:text-gray-400",children:cx(e.created_at)})]}),h.jsxs(I,{className:"flex gap-3 text-sm",children:[h.jsxs(Q,{className:"text-green-600 dark:text-green-400",children:["+",e.changes_summary.added]}),h.jsxs(Q,{className:"text-red-600 dark:text-red-400",children:["-",e.changes_summary.removed]})]})]})})});$u.displayName="VersionListItem";const Fu=({versions:e,selectedId:t,onSelect:n})=>e.length===0?h.jsx(I,{className:"p-4 text-center text-gray-500 dark:text-gray-400",children:"버전이 없습니다."}):h.jsx(Wa,{role:"list",className:"space-y-2",children:e.map(r=>h.jsx($u,{version:r,isSelected:t===r.id,onSelect:n},r.id))}),dx=({layoutId:e,versions:t,onRestore:n})=>{const[r,s]=v.useState(),o=l=>{s(l)},i=()=>{r!==void 0&&n(r)},a=r===void 0;return h.jsxs(I,{className:"space-y-4",role:"region","aria-label":"레이아웃 히스토리 패널",children:[h.jsx(I,{className:"border border-gray-300 dark:border-gray-600 rounded-lg p-4",children:h.jsx(Fu,{versions:t,selectedId:r,onSelect:o})}),h.jsx(I,{className:"flex justify-end",children:h.jsx(ie,{variant:"primary",disabled:a,onClick:i,"aria-label":"선택한 버전으로 복원",children:"복원"})})]})},ux=({emailLabel:e="Email",passwordLabel:t="Password",submitButtonText:n="Sign In",processingText:r="Processing...",emailPlaceholder:s="Email",passwordPlaceholder:o="Password",forgotPasswordText:i="Forgot your password?",forgotPasswordUrl:a="/admin/password/reset",emailRequiredError:l="Please enter your email.",emailInvalidError:c="Please enter a valid email address.",passwordRequiredError:d="Please enter your password.",onSubmit:u,loadingActions:f={},isLoading:g=!1,disabled:m=!1,apiError:p="",className:y="",style:b})=>{const[_,x]=v.useState(""),[M,C]=v.useState(""),[k,N]=v.useState({}),S=p,w=Object.values(f).some($=>$===!0),E=g||w,D=m||E,P=()=>{const $={},A=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;return!_||_.trim()===""?$.email=l:A.test(_)||($.email=c),(!M||M.trim()==="")&&($.password=d),N($),Object.keys($).length===0},L=async $=>{if(!P()){$.preventDefault();return}N({}),u?await u($):$.preventDefault()};return h.jsx(I,{className:`w-full ${y}`,style:b,children:h.jsxs(Ya,{onSubmit:L,className:"space-y-6",noValidate:!0,children:[S&&h.jsx(I,{className:"p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800",children:h.jsx(wt,{className:"text-sm text-red-600 dark:text-red-400",children:S})}),h.jsxs(I,{children:[h.jsx(dn,{htmlFor:"email",className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2",children:e}),h.jsx(_t,{id:"email",name:"email",type:"email",value:_,onChange:$=>x($.target.value),placeholder:s,disabled:D,className:` + ${e?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 hover:border-gray-400 dark:border-gray-600 dark:hover:border-gray-500"} + `,onClick:r,children:h.jsxs(I,{className:"flex justify-between items-start",children:[h.jsxs(I,{className:"flex-1",children:[h.jsxs(Q,{className:"font-semibold text-gray-900 dark:text-gray-100",children:["버전 ",t.version]}),h.jsx(Q,{className:"ml-2 text-sm text-gray-500 dark:text-gray-400",children:Bv(t.created_at)})]}),h.jsxs(I,{className:"flex gap-3 text-sm",children:[h.jsxs(Q,{className:"text-green-600 dark:text-green-400",children:["+",t.changes_summary.added]}),h.jsxs(Q,{className:"text-red-600 dark:text-red-400",children:["-",t.changes_summary.removed]})]})]})})});Su.displayName="VersionListItem";const _u=({versions:t,selectedId:e,onSelect:n})=>t.length===0?h.jsx(I,{className:"p-4 text-center text-gray-500 dark:text-gray-400",children:"버전이 없습니다."}):h.jsx(Ia,{role:"list",className:"space-y-2",children:t.map(r=>h.jsx(Su,{version:r,isSelected:e===r.id,onSelect:n},r.id))}),jv=({layoutId:t,versions:e,onRestore:n})=>{const[r,s]=v.useState(),o=l=>{s(l)},i=()=>{r!==void 0&&n(r)},a=r===void 0;return h.jsxs(I,{className:"space-y-4",role:"region","aria-label":"레이아웃 히스토리 패널",children:[h.jsx(I,{className:"border border-gray-300 dark:border-gray-600 rounded-lg p-4",children:h.jsx(_u,{versions:e,selectedId:r,onSelect:o})}),h.jsx(I,{className:"flex justify-end",children:h.jsx(oe,{variant:"primary",disabled:a,onClick:i,"aria-label":"선택한 버전으로 복원",children:"복원"})})]})},Wv=({emailLabel:t="Email",passwordLabel:e="Password",submitButtonText:n="Sign In",processingText:r="Processing...",emailPlaceholder:s="Email",passwordPlaceholder:o="Password",forgotPasswordText:i="Forgot your password?",forgotPasswordUrl:a="/admin/password/reset",emailRequiredError:l="Please enter your email.",emailInvalidError:c="Please enter a valid email address.",passwordRequiredError:d="Please enter your password.",onSubmit:u,loadingActions:f={},isLoading:g=!1,disabled:m=!1,apiError:p="",className:y="",style:b})=>{const[_,x]=v.useState(""),[E,C]=v.useState(""),[k,N]=v.useState({}),S=p,w=Object.values(f).some($=>$===!0),M=g||w,T=m||M,P=()=>{const $={},A=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;return!_||_.trim()===""?$.email=l:A.test(_)||($.email=c),(!E||E.trim()==="")&&($.password=d),N($),Object.keys($).length===0},L=async $=>{if(!P()){$.preventDefault();return}N({}),u?await u($):$.preventDefault()};return h.jsx(I,{className:`w-full ${y}`,style:b,children:h.jsxs(Pa,{onSubmit:L,className:"space-y-6",noValidate:!0,children:[S&&h.jsx(I,{className:"p-4 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800",children:h.jsx(yt,{className:"text-sm text-red-600 dark:text-red-400",children:S})}),h.jsxs(I,{children:[h.jsx(sn,{htmlFor:"email",className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2",children:t}),h.jsx(kt,{id:"email",name:"email",type:"email",value:_,onChange:$=>x($.target.value),placeholder:s,disabled:T,className:` w-full px-4 py-3 rounded-lg border ${k.email?"border-red-500 focus:border-red-500 focus:ring-red-500":"border-gray-300 dark:border-gray-600 focus:border-blue-500 focus:ring-blue-500"} bg-white dark:bg-gray-800 @@ -128,7 +127,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 - `}),k.email&&h.jsx(wt,{className:"mt-2 text-sm text-red-600 dark:text-red-400",children:k.email})]}),h.jsxs(I,{children:[h.jsx(dn,{htmlFor:"password",className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2",children:t}),h.jsx(_t,{id:"password",name:"password",type:"password",value:M,onChange:$=>C($.target.value),placeholder:o,disabled:D,className:` + `}),k.email&&h.jsx(yt,{className:"mt-2 text-sm text-red-600 dark:text-red-400",children:k.email})]}),h.jsxs(I,{children:[h.jsx(sn,{htmlFor:"password",className:"block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2",children:e}),h.jsx(kt,{id:"password",name:"password",type:"password",value:E,onChange:$=>C($.target.value),placeholder:o,disabled:T,className:` w-full px-4 py-3 rounded-lg border ${k.password?"border-red-500 focus:border-red-500 focus:ring-red-500":"border-gray-300 dark:border-gray-600 focus:border-blue-500 focus:ring-blue-500"} bg-white dark:bg-gray-800 @@ -137,7 +136,7 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 - `}),k.password&&h.jsx(wt,{className:"mt-2 text-sm text-red-600 dark:text-red-400",children:k.password})]}),a&&h.jsx(I,{className:"flex items-center justify-end",children:h.jsx(Kr,{href:a,className:"text-sm text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300 transition-colors duration-200",children:i})}),h.jsx(ie,{type:"submit",disabled:D,className:` + `}),k.password&&h.jsx(yt,{className:"mt-2 text-sm text-red-600 dark:text-red-400",children:k.password})]}),a&&h.jsx(I,{className:"flex items-center justify-end",children:h.jsx(Lr,{href:a,className:"text-sm text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300 transition-colors duration-200",children:i})}),h.jsx(oe,{type:"submit",disabled:T,className:` w-full px-4 py-3 rounded-lg font-medium bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 @@ -147,16 +146,16 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200 flex items-center justify-center - `,children:E?h.jsxs(h.Fragment,{children:[h.jsxs(Qn,{className:"animate-spin -ml-1 mr-3 h-5 w-5 text-white",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[h.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),h.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}),r]}):n})]})})},zu=e=>e==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e,vl=e=>{const t=zu(e);document.documentElement.setAttribute("data-theme",t),t==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")},fx=()=>{const e=localStorage.getItem("g7_color_scheme");return e&&["auto","light","dark"].includes(e)?e:"auto"},hx=({onThemeChange:e,className:t="",autoText:n="System",lightText:r="Light",darkText:s="Dark",id:o,editorAttrs:i})=>{const a=fx(),[l,c]=v.useState(a),[d,u]=v.useState(!1),f=v.useRef(null);v.useEffect(()=>{vl(l)},[]),v.useEffect(()=>{if(l!=="auto")return;const p=window.matchMedia("(prefers-color-scheme: dark)"),y=()=>{vl("auto")};return p.addEventListener("change",y),()=>p.removeEventListener("change",y)},[l]),v.useEffect(()=>{const p=y=>{f.current&&!f.current.contains(y.target)&&u(!1)};return document.addEventListener("mousedown",p),()=>document.removeEventListener("mousedown",p)},[]);const g=p=>{c(p),localStorage.setItem("g7_color_scheme",p),vl(p),u(!1),e?.(p)},m=()=>zu(l)==="dark"?ce.Moon:ce.Sun;return h.jsxs(I,{ref:f,className:`relative ${t}`,id:o,...i,children:[h.jsx(ie,{onClick:()=>u(!d),className:"w-9 h-9 flex items-center justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full transition-colors","aria-label":"Toggle theme",children:h.jsx(se,{name:m(),className:"text-sm text-slate-500 dark:text-slate-400"})}),d&&h.jsx(I,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:h.jsxs(I,{className:"py-2",children:[h.jsxs(ie,{onClick:()=>g("auto"),className:` + `,children:M?h.jsxs(h.Fragment,{children:[h.jsxs(Un,{className:"animate-spin -ml-1 mr-3 h-5 w-5 text-white",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",children:[h.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),h.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"})]}),r]}):n})]})})},Eu=t=>t==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t,cl=t=>{const e=Eu(t);document.documentElement.setAttribute("data-theme",e),e==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")},Gv=()=>{const t=localStorage.getItem("g7_color_scheme");return t&&["auto","light","dark"].includes(t)?t:"auto"},Yv=({onThemeChange:t,className:e="",autoText:n="System",lightText:r="Light",darkText:s="Dark",id:o,editorAttrs:i})=>{const a=Gv(),[l,c]=v.useState(a),[d,u]=v.useState(!1),f=v.useRef(null);v.useEffect(()=>{cl(l)},[]),v.useEffect(()=>{if(l!=="auto")return;const p=window.matchMedia("(prefers-color-scheme: dark)"),y=()=>{cl("auto")};return p.addEventListener("change",y),()=>p.removeEventListener("change",y)},[l]),v.useEffect(()=>{const p=y=>{f.current&&!f.current.contains(y.target)&&u(!1)};return document.addEventListener("mousedown",p),()=>document.removeEventListener("mousedown",p)},[]);const g=p=>{c(p),localStorage.setItem("g7_color_scheme",p),cl(p),u(!1),t?.(p)},m=()=>Eu(l)==="dark"?ce.Moon:ce.Sun;return h.jsxs(I,{ref:f,className:`relative ${e}`,id:o,...i,children:[h.jsx(oe,{onClick:()=>u(!d),className:"w-9 h-9 flex items-center justify-center hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full transition-colors","aria-label":"Toggle theme",children:h.jsx(re,{name:m(),className:"text-sm text-slate-500 dark:text-slate-400"})}),d&&h.jsx(I,{className:"absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-50",children:h.jsxs(I,{className:"py-2",children:[h.jsxs(oe,{onClick:()=>g("auto"),className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${l==="auto"?"bg-gray-50 dark:bg-gray-700":""} - `,children:[h.jsx(se,{name:ce.Settings,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:n}),l==="auto"&&h.jsx(se,{name:ce.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),h.jsxs(ie,{onClick:()=>g("light"),className:` + `,children:[h.jsx(re,{name:ce.Settings,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:n}),l==="auto"&&h.jsx(re,{name:ce.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),h.jsxs(oe,{onClick:()=>g("light"),className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${l==="light"?"bg-gray-50 dark:bg-gray-700":""} - `,children:[h.jsx(se,{name:ce.Sun,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:r}),l==="light"&&h.jsx(se,{name:ce.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),h.jsxs(ie,{onClick:()=>g("dark"),className:` + `,children:[h.jsx(re,{name:ce.Sun,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:r}),l==="light"&&h.jsx(re,{name:ce.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]}),h.jsxs(oe,{onClick:()=>g("dark"),className:` w-full px-4 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700 flex items-center gap-3 transition-colors ${l==="dark"?"bg-gray-50 dark:bg-gray-700":""} - `,children:[h.jsx(se,{name:ce.Moon,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:s}),l==="dark"&&h.jsx(se,{name:ce.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]})]})})]})},Vu=window.G7Core?.createLogger?.("Comp:ColumnSelector")??{log:(...e)=>console.log("[Comp:ColumnSelector]",...e),warn:(...e)=>console.warn("[Comp:ColumnSelector]",...e),error:(...e)=>console.error("[Comp:ColumnSelector]",...e)},Hu=(e,t)=>window.G7Core?.t?.(e,t)??e,gx=()=>window.G7Core,px=({id:e,columns:t,visibleColumns:n,onColumnVisibilityChange:r,triggerLabel:s,triggerIconName:o=ce.Sliders,position:i="right",className:a="",style:l,requiredText:c,menuTitle:d,menuDescription:u,defaultColumns:f,triggerClassName:g,labelClassName:m,dynamicColumns:p,dynamicColumnsInsertAfter:y,editorAttrs:b})=>{const _=s??Hu("common.column_selector"),x=c??Hu("common.required"),M=v.useMemo(()=>{if(!p||!Array.isArray(p)||p.length===0)return t;if(y){const V=t.findIndex(X=>X.field===y);if(V!==-1)return[...t.slice(0,V+1),...p,...t.slice(V+1)]}return[...t,...p]},[t,p,y]),[C,k]=v.useState(!1),N=v.useRef(null),S=v.useRef(null),[w,E]=v.useState(!1),[D,P]=v.useState(i),L=v.useMemo(()=>{try{const V=window.G7Core?.AuthManager?.getInstance();return V?V.getUser()?.uuid:void 0}catch{return}},[]),$=e?`g7_columns_${e}${L!==void 0?`_${L}`:""}`:null,A=()=>{if(!$)return null;try{const V=localStorage.getItem($);if(V){const X=JSON.parse(V);if(Array.isArray(X)&&X.length>0)return X}}catch(V){Vu.warn("Failed to load from localStorage:",V)}return null},O=V=>{if($)try{localStorage.setItem($,JSON.stringify(V))}catch(X){Vu.warn("Failed to save to localStorage:",X)}},T=()=>{const V=A();return V||(n&&n.length>0?n:f&&f.length>0?f:M.filter(X=>X.visible!==!1).map(X=>X.field))},[z,H]=v.useState(T);v.useEffect(()=>{w||(r&&z.length>0&&r(z),E(!0))},[w,r,z]),v.useEffect(()=>{w&&n&&n.length>0&&(A()||H(n))},[n,w]);const j=v.useRef([]),G=v.useRef(null);v.useEffect(()=>{if(w&&p&&p.length>0){const V=p.map(q=>q.field);if(G.current===null){const q=A();if(G.current=q!==null,q!==null){j.current=V;return}}const X=j.current,K=V.filter(q=>!X.includes(q));if(K.length>0){const q=n&&n.length>0?n:z,ne=K.filter(ee=>!q.includes(ee));if(ne.length>0){const ee=[...q,...ne];H(ee),O(ee),r?.(ee)}}j.current=V}},[p,w]);const U=n&&n.length>0?n:z;v.useEffect(()=>{const V=X=>{N.current&&!N.current.contains(X.target)&&k(!1)};return C&&document.addEventListener("mousedown",V),()=>{document.removeEventListener("mousedown",V)}},[C]),v.useEffect(()=>{if(C&&S.current&&N.current){const V=S.current.getBoundingClientRect(),X=N.current.getBoundingClientRect(),K=window.innerWidth,q=V.width||256;i==="right"?X.right-q<0?P("left"):P("right"):X.left+q>K?P("right"):P("left")}},[C,i]);const W=V=>{if(M.find(q=>q.field===V)?.required)return;const K=U.includes(V)?U.filter(q=>q!==V):[...U,V];H(K),O(K),r?.(K)},Y=D==="left"?"left-0":"right-0";return h.jsxs(I,{ref:N,className:`relative inline-block ${a}`,style:l,id:e,...b,children:[h.jsxs(ie,{onClick:()=>k(!C),className:gx()?.style?.mergeClasses?.("flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",g)??`flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors ${g??""}`,children:[h.jsx(se,{name:o,className:"w-5 h-5"}),_&&h.jsx(Q,{className:`${m||"hidden sm:inline"} text-sm`,children:_})]}),C&&h.jsxs(I,{ref:S,className:`absolute ${Y} z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg`,children:[(d||u)&&h.jsxs(I,{className:"px-4 pt-3 pb-2 border-b border-gray-200 dark:border-gray-700",children:[d&&h.jsx(Q,{className:"block text-sm font-medium text-gray-900 dark:text-white",children:d}),u&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-1",children:u})]}),h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:M.map(V=>{const X=U.includes(V.field),K=V.required===!0;return h.jsxs(I,{onClick:()=>!K&&W(V.field),className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${K?"cursor-default":"cursor-pointer"}`,children:[h.jsx(_n,{checked:X,disabled:K,readOnly:!0,className:"h-4 w-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 pointer-events-none"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:V.header}),K&&x&&h.jsx(Q,{className:"ml-auto text-xs text-gray-400 dark:text-gray-500",children:x})]},V.field)})})]})]})},mx=window.G7Core?.createLogger?.("Comp:PageTransition")??{log:(...e)=>console.log("[Comp:PageTransition]",...e),warn:(...e)=>console.warn("[Comp:PageTransition]",...e),error:(...e)=>console.error("[Comp:PageTransition]",...e)},bx=({className:e="",style:t})=>{const[n,r]=v.useState(!1);return v.useEffect(()=>{const s=window.G7Core?.TransitionManager;if(!s){mx.warn("TransitionManager를 찾을 수 없습니다.");return}return r(s.getIsPending()),s.subscribe(i=>{r(i)})},[]),n?h.jsxs(I,{className:`fixed top-0 left-0 right-0 z-[999] ${e}`,style:t,role:"progressbar","aria-label":"페이지 로딩 중",children:[h.jsx(I,{className:"h-1 bg-gradient-to-r from-blue-500 via-blue-600 to-blue-500 animate-pulse",children:h.jsx(I,{className:"h-full bg-blue-700 animate-[loading_1.5s_ease-in-out_infinite]",style:{width:"30%",animation:"loading 1.5s ease-in-out infinite"}})}),h.jsx("style",{children:` + `,children:[h.jsx(re,{name:ce.Moon,className:"w-5 h-5 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"flex-1 text-left text-gray-900 dark:text-white",children:s}),l==="dark"&&h.jsx(re,{name:ce.Check,className:"w-4 h-4 text-blue-600 dark:text-blue-400 ml-auto"})]})]})})]})},Mu=window.G7Core?.createLogger?.("Comp:ColumnSelector")??{log:(...t)=>console.log("[Comp:ColumnSelector]",...t),warn:(...t)=>console.warn("[Comp:ColumnSelector]",...t),error:(...t)=>console.error("[Comp:ColumnSelector]",...t)},Nu=(t,e)=>window.G7Core?.t?.(t,e)??t,Xv=()=>window.G7Core,Kv=({id:t,columns:e,visibleColumns:n,onColumnVisibilityChange:r,triggerLabel:s,triggerIconName:o=ce.Sliders,position:i="right",className:a="",style:l,requiredText:c,menuTitle:d,menuDescription:u,defaultColumns:f,triggerClassName:g,labelClassName:m,dynamicColumns:p,dynamicColumnsInsertAfter:y,editorAttrs:b})=>{const _=s??Nu("common.column_selector"),x=c??Nu("common.required"),E=v.useMemo(()=>{if(!p||!Array.isArray(p)||p.length===0)return e;if(y){const V=e.findIndex(K=>K.field===y);if(V!==-1)return[...e.slice(0,V+1),...p,...e.slice(V+1)]}return[...e,...p]},[e,p,y]),[C,k]=v.useState(!1),N=v.useRef(null),S=v.useRef(null),[w,M]=v.useState(!1),[T,P]=v.useState(i),L=v.useMemo(()=>{try{const V=window.G7Core?.AuthManager?.getInstance();return V?V.getUser()?.uuid:void 0}catch{return}},[]),$=t?`g7_columns_${t}${L!==void 0?`_${L}`:""}`:null,A=()=>{if(!$)return null;try{const V=localStorage.getItem($);if(V){const K=JSON.parse(V);if(Array.isArray(K)&&K.length>0)return K}}catch(V){Mu.warn("Failed to load from localStorage:",V)}return null},D=V=>{if($)try{localStorage.setItem($,JSON.stringify(V))}catch(K){Mu.warn("Failed to save to localStorage:",K)}},O=()=>{const V=A();return V||(n&&n.length>0?n:f&&f.length>0?f:E.filter(K=>K.visible!==!1).map(K=>K.field))},[z,H]=v.useState(O);v.useEffect(()=>{w||(r&&z.length>0&&r(z),M(!0))},[w,r,z]),v.useEffect(()=>{w&&n&&n.length>0&&(A()||H(n))},[n,w]);const j=v.useRef([]),W=v.useRef(null);v.useEffect(()=>{if(w&&p&&p.length>0){const V=p.map(Z=>Z.field);if(W.current===null){const Z=A();if(W.current=Z!==null,Z!==null){j.current=V;return}}const K=j.current,X=V.filter(Z=>!K.includes(Z));if(X.length>0){const Z=n&&n.length>0?n:z,ee=X.filter(ne=>!Z.includes(ne));if(ee.length>0){const ne=[...Z,...ee];H(ne),D(ne),r?.(ne)}}j.current=V}},[p,w]);const U=n&&n.length>0?n:z;v.useEffect(()=>{const V=K=>{N.current&&!N.current.contains(K.target)&&k(!1)};return C&&document.addEventListener("mousedown",V),()=>{document.removeEventListener("mousedown",V)}},[C]),v.useEffect(()=>{if(C&&S.current&&N.current){const V=S.current.getBoundingClientRect(),K=N.current.getBoundingClientRect(),X=window.innerWidth,Z=V.width||256;i==="right"?K.right-Z<0?P("left"):P("right"):K.left+Z>X?P("right"):P("left")}},[C,i]);const B=V=>{if(E.find(Z=>Z.field===V)?.required)return;const X=U.includes(V)?U.filter(Z=>Z!==V):[...U,V];H(X),D(X),r?.(X)},G=T==="left"?"left-0":"right-0";return h.jsxs(I,{ref:N,className:`relative inline-block ${a}`,style:l,id:t,...b,children:[h.jsxs(oe,{onClick:()=>k(!C),className:Xv()?.style?.mergeClasses?.("flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors",g)??`flex items-center gap-2 px-3 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors ${g??""}`,children:[h.jsx(re,{name:o,className:"w-5 h-5"}),_&&h.jsx(Q,{className:`${m||"hidden sm:inline"} text-sm`,children:_})]}),C&&h.jsxs(I,{ref:S,className:`absolute ${G} z-50 mt-2 w-64 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg`,children:[(d||u)&&h.jsxs(I,{className:"px-4 pt-3 pb-2 border-b border-gray-200 dark:border-gray-700",children:[d&&h.jsx(Q,{className:"block text-sm font-medium text-gray-900 dark:text-white",children:d}),u&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-1",children:u})]}),h.jsx(I,{className:"p-2 max-h-64 overflow-y-auto",children:E.map(V=>{const K=U.includes(V.field),X=V.required===!0;return h.jsxs(I,{onClick:()=>!X&&B(V.field),className:`flex items-center gap-2 px-3 py-2 rounded hover:bg-gray-50 dark:hover:bg-gray-700 ${X?"cursor-default":"cursor-pointer"}`,children:[h.jsx(vn,{checked:K,disabled:X,readOnly:!0,className:"h-4 w-4 text-blue-600 rounded border-gray-300 dark:border-gray-600 pointer-events-none"}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300",children:V.header}),X&&x&&h.jsx(Q,{className:"ml-auto text-xs text-gray-400 dark:text-gray-500",children:x})]},V.field)})})]})]})},qv=window.G7Core?.createLogger?.("Comp:PageTransition")??{log:(...t)=>console.log("[Comp:PageTransition]",...t),warn:(...t)=>console.warn("[Comp:PageTransition]",...t),error:(...t)=>console.error("[Comp:PageTransition]",...t)},Zv=({className:t="",style:e})=>{const[n,r]=v.useState(!1);return v.useEffect(()=>{const s=window.G7Core?.TransitionManager;if(!s){qv.warn("TransitionManager를 찾을 수 없습니다.");return}return r(s.getIsPending()),s.subscribe(i=>{r(i)})},[]),n?h.jsxs(I,{className:`fixed top-0 left-0 right-0 z-[999] ${t}`,style:e,role:"progressbar","aria-label":"페이지 로딩 중",children:[h.jsx(I,{className:"h-1 bg-gradient-to-r from-blue-500 via-blue-600 to-blue-500 animate-pulse",children:h.jsx(I,{className:"h-full bg-blue-700 animate-[loading_1.5s_ease-in-out_infinite]",style:{width:"30%",animation:"loading 1.5s ease-in-out infinite"}})}),h.jsx("style",{children:` @keyframes loading { 0% { transform: translateX(-100%); @@ -168,12 +167,12 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho transform: translateX(-100%); } } - `})]}):null},yx=(e,t)=>window.G7Core?.t?.(e,t)??e,vx=({options:e})=>{const t=document.documentElement.classList.contains("dark"),n=t?"rgb(17,24,39)":"rgb(249,250,251)",r=t?"#6b7280":"#9ca3af",s=t?"#9ca3af":"#6b7280";return h.jsxs("div",{style:{position:"absolute",inset:0,zIndex:2147483647,overflow:"hidden",background:n,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"flex-start",paddingTop:"15%",gap:"12px"},children:[h.jsx("div",{style:{width:"32px",height:"32px",border:`3px solid ${r}`,borderTopColor:"transparent",borderRadius:"50%",animation:"g7-spin 0.8s linear infinite"}}),h.jsx("span",{style:{color:s,fontSize:"14px"},children:e?.text||yx("nav.loading")})]})};function Sr(e){"@babel/helpers - typeof";return Sr=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Sr(e)}function xx(e,t){if(Sr(e)!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(Sr(r)!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function Uu(e){var t=xx(e,"string");return Sr(t)=="symbol"?t:t+""}function Gs(e,t,n){return(t=Uu(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Bu(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(s){return Object.getOwnPropertyDescriptor(e,s).enumerable})),n.push.apply(n,r)}return n}function Ee(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0?Ht(is,--en):0,os--,Mt===10&&(os=1,li--),Mt}function fn(){return Mt=en2||qs(Mt)>3?"":" "}function Kx(e,t){for(;--t&&fn()&&!(Mt<48||Mt>102||Mt>57&&Mt<65||Mt>70&&Mt<97););return Ks(e,di()+(t<6&&Un()==32&&fn()==32))}function El(e){for(;fn();)switch(Mt){case e:return en;case 34:case 39:e!==34&&e!==39&&El(Mt);break;case 40:e===41&&El(e);break;case 92:fn();break}return en}function qx(e,t){for(;fn()&&e+Mt!==57;)if(e+Mt===84&&Un()===47)break;return"/*"+Ks(t,en-1)+"*"+ii(e===47?e:fn())}function Zx(e){for(;!qs(Un());)fn();return Ks(e,en)}function Qx(e){return Ju(fi("",null,null,null,[""],e=Qu(e),0,[0],e))}function fi(e,t,n,r,s,o,i,a,l){for(var c=0,d=0,u=i,f=0,g=0,m=0,p=1,y=1,b=1,_=0,x="",M=s,C=o,k=r,N=x;y;)switch(m=_,_=fn()){case 40:if(m!=108&&Ht(N,u-1)==58){Sl(N+=ot(ui(_),"&","&\f"),"&\f")!=-1&&(b=-1);break}case 34:case 39:case 91:N+=ui(_);break;case 9:case 10:case 13:case 32:N+=Xx(m);break;case 92:N+=Kx(di()-1,7);continue;case 47:switch(Un()){case 42:case 47:ai(Jx(qx(fn(),di()),t,n),l);break;default:N+="/"}break;case 123*p:a[c++]=Hn(N)*b;case 125*p:case 59:case 0:switch(_){case 0:case 125:y=0;case 59+d:b==-1&&(N=ot(N,/\f/g,"")),g>0&&Hn(N)-u&&ai(g>32?ef(N+";",r,n,u-1):ef(ot(N," ","")+";",r,n,u-2),l);break;case 59:N+=";";default:if(ai(k=Ru(N,t,n,c,d,s,a,x,M=[],C=[],u),o),_===123)if(d===0)fi(N,t,k,k,M,o,u,a,C);else switch(f===99&&Ht(N,3)===110?100:f){case 100:case 108:case 109:case 115:fi(e,k,k,r&&ai(Ru(e,k,k,0,0,s,a,x,s,M=[],u),C),s,C,u,a,r?M:C);break;default:fi(N,k,k,k,[""],C,0,a,C)}}c=d=g=0,p=b=1,x=N="",u=i;break;case 58:u=1+Hn(N),g=m;default:if(p<1){if(_==123)--p;else if(_==125&&p++==0&&Yx()==125)continue}switch(N+=ii(_),_*p){case 38:b=d>0?1:(N+="\f",-1);break;case 44:a[c++]=(Hn(N)-1)*b,b=1;break;case 64:Un()===45&&(N+=ui(fn())),f=Un(),d=u=Hn(x=N+=Zx(di())),_++;break;case 45:m===45&&Hn(N)==2&&(p=0)}}return o}function Ru(e,t,n,r,s,o,i,a,l,c,d){for(var u=s-1,f=s===0?o:[""],g=_l(f),m=0,p=0,y=0;m0?f[b]+" "+_:ot(_,/&\f/g,f[b])))&&(l[y++]=x);return ci(e,t,n,s===0?kl:a,l,c,d)}function Jx(e,t,n){return ci(e,t,n,Xu,ii(Gx()),Ys(e,2,-2),0)}function ef(e,t,n,r){return ci(e,t,n,Cl,Ys(e,0,r),Ys(e,r+1,-1),r)}function as(e,t){for(var n="",r=_l(e),s=0;s6)switch(Ht(e,t+1)){case 109:if(Ht(e,t+4)!==45)break;case 102:return ot(e,/(.+:)(.+)-([^]+)/,"$1"+st+"$2-$3$1"+oi+(Ht(e,t+3)==108?"$3":"$2-$3"))+e;case 115:return~Sl(e,"stretch")?nf(ot(e,"stretch","fill-available"),t)+e:e}break;case 4949:if(Ht(e,t+1)!==115)break;case 6444:switch(Ht(e,Hn(e)-3-(~Sl(e,"!important")&&10))){case 107:return ot(e,":",":"+st)+e;case 101:return ot(e,/(.+:)([^;!]+)(;|!.+)?/,"$1"+st+(Ht(e,14)===45?"inline-":"")+"box$3$1"+st+"$2$3$1"+jt+"$2box$3")+e}break;case 5936:switch(Ht(e,t+11)){case 114:return st+e+jt+ot(e,/[svh]\w+-[tblr]{2}/,"tb")+e;case 108:return st+e+jt+ot(e,/[svh]\w+-[tblr]{2}/,"tb-rl")+e;case 45:return st+e+jt+ot(e,/[svh]\w+-[tblr]{2}/,"lr")+e}return st+e+jt+e+e}return e}var lw=function(t,n,r,s){if(t.length>-1&&!t.return)switch(t.type){case Cl:t.return=nf(t.value,t.length);break;case Ku:return as([Xs(t,{value:ot(t.value,"@","@"+st)})],s);case kl:if(t.length)return Wx(t.props,function(o){switch(jx(o,/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":return as([Xs(t,{props:[ot(o,/:(read-\w+)/,":"+oi+"$1")]})],s);case"::placeholder":return as([Xs(t,{props:[ot(o,/:(plac\w+)/,":"+st+"input-$1")]}),Xs(t,{props:[ot(o,/:(plac\w+)/,":"+oi+"$1")]}),Xs(t,{props:[ot(o,/:(plac\w+)/,jt+"input-$1")]})],s)}return""})}},cw=[lw],dw=function(t){var n=t.key;if(n==="css"){var r=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(r,function(p){var y=p.getAttribute("data-emotion");y.indexOf(" ")!==-1&&(document.head.appendChild(p),p.setAttribute("data-s",""))})}var s=t.stylisPlugins||cw,o={},i,a=[];i=t.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+n+' "]'),function(p){for(var y=p.getAttribute("data-emotion").split(" "),b=1;b=4;++r,s-=4)n=e.charCodeAt(r)&255|(e.charCodeAt(++r)&255)<<8|(e.charCodeAt(++r)&255)<<16|(e.charCodeAt(++r)&255)<<24,n=(n&65535)*1540483477+((n>>>16)*59797<<16),n^=n>>>24,t=(n&65535)*1540483477+((n>>>16)*59797<<16)^(t&65535)*1540483477+((t>>>16)*59797<<16);switch(s){case 3:t^=(e.charCodeAt(r+2)&255)<<16;case 2:t^=(e.charCodeAt(r+1)&255)<<8;case 1:t^=e.charCodeAt(r)&255,t=(t&65535)*1540483477+((t>>>16)*59797<<16)}return t^=t>>>13,t=(t&65535)*1540483477+((t>>>16)*59797<<16),((t^t>>>15)>>>0).toString(36)}var yw={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,scale:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},vw=/[A-Z]|^ms/g,xw=/_EMO_([^_]+?)_([^]*?)_EMO_/g,lf=function(t){return t.charCodeAt(1)===45},cf=function(t){return t!=null&&typeof t!="boolean"},Tl=nw(function(e){return lf(e)?e:e.replace(vw,"-$&").toLowerCase()}),df=function(t,n){switch(t){case"animation":case"animationName":if(typeof n=="string")return n.replace(xw,function(r,s,o){return Bn={name:s,styles:o,next:Bn},s})}return yw[t]!==1&&!lf(t)&&typeof n=="number"&&n!==0?n+"px":n};function Zs(e,t,n){if(n==null)return"";var r=n;if(r.__emotion_styles!==void 0)return r;switch(typeof n){case"boolean":return"";case"object":{var s=n;if(s.anim===1)return Bn={name:s.name,styles:s.styles,next:Bn},s.name;var o=n;if(o.styles!==void 0){var i=o.next;if(i!==void 0)for(;i!==void 0;)Bn={name:i.name,styles:i.styles,next:Bn},i=i.next;var a=o.styles+";";return a}return ww(e,t,n)}case"function":{if(e!==void 0){var l=Bn,c=n(e);return Bn=l,Zs(e,t,c)}break}}var d=n;return d}function ww(e,t,n){var r="";if(Array.isArray(n))for(var s=0;s({x:e,y:e});function Pw(e){const{x:t,y:n,width:r,height:s}=e;return{width:r,height:s,top:n,left:t,right:t+r,bottom:n+s,x:t,y:n}}function pi(){return typeof window<"u"}function gf(e){return mf(e)?(e.nodeName||"").toLowerCase():"#document"}function _r(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function pf(e){var t;return(t=(mf(e)?e.ownerDocument:e.document)||window.document)==null?void 0:t.documentElement}function mf(e){return pi()?e instanceof Node||e instanceof _r(e).Node:!1}function $w(e){return pi()?e instanceof Element||e instanceof _r(e).Element:!1}function Ll(e){return pi()?e instanceof HTMLElement||e instanceof _r(e).HTMLElement:!1}function bf(e){return!pi()||typeof ShadowRoot>"u"?!1:e instanceof ShadowRoot||e instanceof _r(e).ShadowRoot}function yf(e){const{overflow:t,overflowX:n,overflowY:r,display:s}=vf(e);return/auto|scroll|overlay|hidden|clip/.test(t+r+n)&&s!=="inline"&&s!=="contents"}function Fw(e){return/^(html|body|#document)$/.test(gf(e))}function vf(e){return _r(e).getComputedStyle(e)}function zw(e){if(gf(e)==="html")return e;const t=e.assignedSlot||e.parentNode||bf(e)&&e.host||pf(e);return bf(t)?t.host:t}function xf(e){const t=zw(e);return Fw(t)?(e.ownerDocument||e).body:Ll(t)&&yf(t)?t:xf(t)}function mi(e,t,n){var r;t===void 0&&(t=[]),n===void 0&&(n=!0);const s=xf(e),o=s===((r=e.ownerDocument)==null?void 0:r.body),i=_r(s);if(o){const a=Vw(i);return t.concat(i,i.visualViewport||[],yf(s)?s:[],a&&n?mi(a):[])}else return t.concat(s,mi(s,[],n))}function Vw(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function Hw(e){const t=vf(e);let n=parseFloat(t.width)||0,r=parseFloat(t.height)||0;const s=Ll(e),o=s?e.offsetWidth:n,i=s?e.offsetHeight:r,a=hi(n)!==o||hi(r)!==i;return a&&(n=o,r=i),{width:n,height:r,$:a}}function Pl(e){return $w(e)?e:e.contextElement}function Uw(e){const t=Pl(e);if(!Ll(t))return Il(1);const n=t.getBoundingClientRect(),{width:r,height:s,$:o}=Hw(t);let i=(o?hi(n.width):n.width)/r,a=(o?hi(n.height):n.height)/s;return(!i||!Number.isFinite(i))&&(i=1),(!a||!Number.isFinite(a))&&(a=1),{x:i,y:a}}function wf(e,t,n,r){t===void 0&&(t=!1);const s=e.getBoundingClientRect();Pl(e);let o=Il(1);t&&(o=Uw(e));const i=Il(0);let a=(s.left+i.x)/o.x,l=(s.top+i.y)/o.y,c=s.width/o.x,d=s.height/o.y;return Pw({width:c,height:d,x:a,y:l})}function kf(e,t){return e.x===t.x&&e.y===t.y&&e.width===t.width&&e.height===t.height}function Bw(e,t,n){let r=null,s;const o=pf(e);function i(){var d;clearTimeout(s),(d=r)==null||d.disconnect(),r=null}function a(d,u){d===void 0&&(d=!1),u===void 0&&(u=1),i();const f=e.getBoundingClientRect(),{left:g,top:m,width:p,height:y}=f;if(d||t(),!p||!y)return;const b=gi(m),_=gi(o.clientWidth-(g+p)),x=gi(o.clientHeight-(m+y)),M=gi(g),k={rootMargin:-b+"px "+-_+"px "+-x+"px "+-M+"px",threshold:Lw(0,Iw(1,u))||1};let N=!0;function S(w){const E=w[0].intersectionRatio;if(!kf(f,e.getBoundingClientRect()))return a();if(E!==u){if(!N)return a();E?a(!1,E):s=setTimeout(()=>{a(!1,1e-7)},1e3)}N=!1}try{r=new IntersectionObserver(S,{...k,root:o.ownerDocument})}catch{r=new IntersectionObserver(S,k)}r.observe(e)}const l=_r(e),c=()=>a(n);return l.addEventListener("resize",c),a(!0),()=>{l.removeEventListener("resize",c),i()}}function jw(e,t,n,r){r===void 0&&(r={});const{ancestorScroll:s=!0,ancestorResize:o=!0,elementResize:i=typeof ResizeObserver=="function",layoutShift:a=typeof IntersectionObserver=="function",animationFrame:l=!1}=r,c=Pl(e),d=s||o?[...c?mi(c):[],...t?mi(t):[]]:[];d.forEach(b=>{s&&b.addEventListener("scroll",n),o&&b.addEventListener("resize",n)});const u=c&&a?Bw(c,n,o):null;let f=-1,g=null;i&&(g=new ResizeObserver(b=>{let[_]=b;_&&_.target===c&&g&&t&&(g.unobserve(t),cancelAnimationFrame(f),f=requestAnimationFrame(()=>{var x;(x=g)==null||x.observe(t)})),n()}),c&&!l&&g.observe(c),t&&g.observe(t));let m,p=l?wf(e):null;l&&y();function y(){const b=wf(e);p&&!kf(p,b)&&n(),p=b,m=requestAnimationFrame(y)}return n(),()=>{var b;d.forEach(_=>{s&&_.removeEventListener("scroll",n),o&&_.removeEventListener("resize",n)}),u?.(),(b=g)==null||b.disconnect(),g=null,l&&cancelAnimationFrame(m)}}var $l=v.useLayoutEffect,Ww=["className","clearValue","cx","getStyles","getClassNames","getValue","hasValue","isMulti","isRtl","options","selectOption","selectProps","setValue","theme"],bi=function(){};function Gw(e,t){return t?t[0]==="-"?e+t:e+"__"+t:e}function Yw(e,t){for(var n=arguments.length,r=new Array(n>2?n-2:0),s=2;s-1}function Xw(e){return vi(e)?window.innerHeight:e.clientHeight}function Sf(e){return vi(e)?window.pageYOffset:e.scrollTop}function xi(e,t){if(vi(e)){window.scrollTo(0,t);return}e.scrollTop=t}function Kw(e){var t=getComputedStyle(e),n=t.position==="absolute",r=/(auto|scroll)/;if(t.position==="fixed")return document.documentElement;for(var s=e;s=s.parentElement;)if(t=getComputedStyle(s),!(n&&t.position==="static")&&r.test(t.overflow+t.overflowY+t.overflowX))return s;return document.documentElement}function qw(e,t,n,r){return n*((e=e/r-1)*e*e+1)+t}function wi(e,t){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:200,r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:bi,s=Sf(e),o=t-s,i=10,a=0;function l(){a+=i;var c=qw(a,s,o,n);xi(e,c),an.bottom?xi(e,Math.min(t.offsetTop+t.clientHeight-e.offsetHeight+s,e.scrollHeight)):r.top-s1?n-1:0),s=1;s=m)return{placement:"bottom",maxHeight:t};if(w>=m&&!i)return o&&wi(l,E,P),{placement:"bottom",maxHeight:t};if(!i&&w>=r||i&&N>=r){o&&wi(l,E,P);var L=i?N-M:w-M;return{placement:"bottom",maxHeight:L}}if(s==="auto"||i){var $=t,A=i?k:S;return A>=r&&($=Math.min(A-M-a,t)),{placement:"top",maxHeight:$}}if(s==="bottom")return o&&xi(l,E),{placement:"bottom",maxHeight:t};break;case"top":if(k>=m)return{placement:"top",maxHeight:t};if(S>=m&&!i)return o&&wi(l,D,P),{placement:"top",maxHeight:t};if(!i&&S>=r||i&&k>=r){var O=t;return(!i&&S>=r||i&&k>=r)&&(O=i?k-C:S-C),o&&wi(l,D,P),{placement:"top",maxHeight:O}}return{placement:"bottom",maxHeight:t};default:throw new Error('Invalid placement provided "'.concat(s,'".'))}return c}function i1(e){var t={bottom:"top",top:"bottom"};return e?t[e]:"bottom"}var Nf=function(t){return t==="auto"?"bottom":t},a1=function(t,n){var r,s=t.placement,o=t.theme,i=o.borderRadius,a=o.spacing,l=o.colors;return Ee((r={label:"menu"},Gs(r,i1(s),"100%"),Gs(r,"position","absolute"),Gs(r,"width","100%"),Gs(r,"zIndex",1),r),n?{}:{backgroundColor:l.neutral0,borderRadius:i,boxShadow:"0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)",marginBottom:a.menuGutter,marginTop:a.menuGutter})},Tf=v.createContext(null),l1=function(t){var n=t.children,r=t.minMenuHeight,s=t.maxMenuHeight,o=t.menuPlacement,i=t.menuPosition,a=t.menuShouldScrollIntoView,l=t.theme,c=v.useContext(Tf)||{},d=c.setPortalPlacement,u=v.useRef(null),f=v.useState(s),g=Jn(f,2),m=g[0],p=g[1],y=v.useState(null),b=Jn(y,2),_=b[0],x=b[1],M=l.spacing.controlHeight;return $l(function(){var C=u.current;if(C){var k=i==="fixed",N=a&&!k,S=o1({maxHeight:s,menuEl:C,minHeight:r,placement:o,shouldScroll:N,isFixedPosition:k,controlHeight:M});p(S.maxHeight),x(S.placement),d?.(S.placement)}},[s,o,i,a,r,d,M]),n({ref:u,placerProps:Ee(Ee({},t),{},{placement:_||Nf(o),maxHeight:m})})},c1=function(t){var n=t.children,r=t.innerRef,s=t.innerProps;return Se("div",Oe({},Ct(t,"menu",{menu:!0}),{ref:r},s),n)},d1=c1,u1=function(t,n){var r=t.maxHeight,s=t.theme.spacing.baseUnit;return Ee({maxHeight:r,overflowY:"auto",position:"relative",WebkitOverflowScrolling:"touch"},n?{}:{paddingBottom:s,paddingTop:s})},f1=function(t){var n=t.children,r=t.innerProps,s=t.innerRef,o=t.isMulti;return Se("div",Oe({},Ct(t,"menuList",{"menu-list":!0,"menu-list--is-multi":o}),{ref:s},r),n)},Of=function(t,n){var r=t.theme,s=r.spacing.baseUnit,o=r.colors;return Ee({textAlign:"center"},n?{}:{color:o.neutral40,padding:"".concat(s*2,"px ").concat(s*3,"px")})},h1=Of,g1=Of,p1=function(t){var n=t.children,r=n===void 0?"No options":n,s=t.innerProps,o=Vn(t,r1);return Se("div",Oe({},Ct(Ee(Ee({},o),{},{children:r,innerProps:s}),"noOptionsMessage",{"menu-notice":!0,"menu-notice--no-options":!0}),s),r)},m1=function(t){var n=t.children,r=n===void 0?"Loading...":n,s=t.innerProps,o=Vn(t,s1);return Se("div",Oe({},Ct(Ee(Ee({},o),{},{children:r,innerProps:s}),"loadingMessage",{"menu-notice":!0,"menu-notice--loading":!0}),s),r)},b1=function(t){var n=t.rect,r=t.offset,s=t.position;return{left:n.left,position:s,top:r,width:n.width,zIndex:1}},y1=function(t){var n=t.appendTo,r=t.children,s=t.controlElement,o=t.innerProps,i=t.menuPlacement,a=t.menuPosition,l=v.useRef(null),c=v.useRef(null),d=v.useState(Nf(i)),u=Jn(d,2),f=u[0],g=u[1],m=v.useMemo(function(){return{setPortalPlacement:g}},[]),p=v.useState(null),y=Jn(p,2),b=y[0],_=y[1],x=v.useCallback(function(){if(s){var N=Zw(s),S=a==="fixed"?0:window.pageYOffset,w=N[f]+S;(w!==b?.offset||N.left!==b?.rect.left||N.width!==b?.rect.width)&&_({offset:w,rect:N})}},[s,a,f,b?.offset,b?.rect.left,b?.rect.width]);$l(function(){x()},[x]);var M=v.useCallback(function(){typeof c.current=="function"&&(c.current(),c.current=null),s&&l.current&&(c.current=jw(s,l.current,x,{elementResize:"ResizeObserver"in window}))},[s,x]);$l(function(){M()},[M]);var C=v.useCallback(function(N){l.current=N,M()},[M]);if(!n&&a!=="fixed"||!b)return null;var k=Se("div",Oe({ref:C},Ct(Ee(Ee({},t),{},{offset:b.offset,position:a,rect:b.rect}),"menuPortal",{"menu-portal":!0}),o),r);return Se(Tf.Provider,{value:m},n?$n.createPortal(k,n):k)},v1=function(t){var n=t.isDisabled,r=t.isRtl;return{label:"container",direction:r?"rtl":void 0,pointerEvents:n?"none":void 0,position:"relative"}},x1=function(t){var n=t.children,r=t.innerProps,s=t.isDisabled,o=t.isRtl;return Se("div",Oe({},Ct(t,"container",{"--is-disabled":s,"--is-rtl":o}),r),n)},w1=function(t,n){var r=t.theme.spacing,s=t.isMulti,o=t.hasValue,i=t.selectProps.controlShouldRenderValue;return Ee({alignItems:"center",display:s&&o&&i?"flex":"grid",flex:1,flexWrap:"wrap",WebkitOverflowScrolling:"touch",position:"relative",overflow:"hidden"},n?{}:{padding:"".concat(r.baseUnit/2,"px ").concat(r.baseUnit*2,"px")})},k1=function(t){var n=t.children,r=t.innerProps,s=t.isMulti,o=t.hasValue;return Se("div",Oe({},Ct(t,"valueContainer",{"value-container":!0,"value-container--is-multi":s,"value-container--has-value":o}),r),n)},C1=function(){return{alignItems:"center",alignSelf:"stretch",display:"flex",flexShrink:0}},S1=function(t){var n=t.children,r=t.innerProps;return Se("div",Oe({},Ct(t,"indicatorsContainer",{indicators:!0}),r),n)},Af,_1=["size"],E1=["innerProps","isRtl","size"],M1={name:"8mmkcg",styles:"display:inline-block;fill:currentColor;line-height:1;stroke:currentColor;stroke-width:0"},Df=function(t){var n=t.size,r=Vn(t,_1);return Se("svg",Oe({height:n,width:n,viewBox:"0 0 20 20","aria-hidden":"true",focusable:"false",css:M1},r))},Fl=function(t){return Se(Df,Oe({size:20},t),Se("path",{d:"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"}))},If=function(t){return Se(Df,Oe({size:20},t),Se("path",{d:"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"}))},Lf=function(t,n){var r=t.isFocused,s=t.theme,o=s.spacing.baseUnit,i=s.colors;return Ee({label:"indicatorContainer",display:"flex",transition:"color 150ms"},n?{}:{color:r?i.neutral60:i.neutral20,padding:o*2,":hover":{color:r?i.neutral80:i.neutral40}})},N1=Lf,T1=function(t){var n=t.children,r=t.innerProps;return Se("div",Oe({},Ct(t,"dropdownIndicator",{indicator:!0,"dropdown-indicator":!0}),r),n||Se(If,null))},O1=Lf,A1=function(t){var n=t.children,r=t.innerProps;return Se("div",Oe({},Ct(t,"clearIndicator",{indicator:!0,"clear-indicator":!0}),r),n||Se(Fl,null))},D1=function(t,n){var r=t.isDisabled,s=t.theme,o=s.spacing.baseUnit,i=s.colors;return Ee({label:"indicatorSeparator",alignSelf:"stretch",width:1},n?{}:{backgroundColor:r?i.neutral10:i.neutral20,marginBottom:o*2,marginTop:o*2})},I1=function(t){var n=t.innerProps;return Se("span",Oe({},n,Ct(t,"indicatorSeparator",{"indicator-separator":!0})))},L1=Aw(Af||(Af=Dw([` + `})]}):null},Qv=(t,e)=>window.G7Core?.t?.(t,e)??t,Jv=({options:t})=>{const e=document.documentElement.classList.contains("dark"),n=e?"rgb(17,24,39)":"rgb(249,250,251)",r=e?"#6b7280":"#9ca3af",s=e?"#9ca3af":"#6b7280";return h.jsxs("div",{style:{position:"absolute",inset:0,zIndex:2147483647,overflow:"hidden",background:n,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"flex-start",paddingTop:"15%",gap:"12px"},children:[h.jsx("div",{style:{width:"32px",height:"32px",border:`3px solid ${r}`,borderTopColor:"transparent",borderRadius:"50%",animation:"g7-spin 0.8s linear infinite"}}),h.jsx("span",{style:{color:s,fontSize:"14px"},children:t?.text||Qv("nav.loading")})]})};function gr(t){"@babel/helpers - typeof";return gr=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},gr(t)}function Rv(t,e){if(gr(t)!="object"||!t)return t;var n=t[Symbol.toPrimitive];if(n!==void 0){var r=n.call(t,e);if(gr(r)!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(e==="string"?String:Number)(t)}function Ou(t){var e=Rv(t,"string");return gr(e)=="symbol"?e:e+""}function As(t,e,n){return(e=Ou(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function Tu(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter(function(s){return Object.getOwnPropertyDescriptor(t,s).enumerable})),n.push.apply(n,r)}return n}function Me(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,r=Array(e);n0?Pt(Br,--Qt):0,Ur--,Ct===10&&(Ur=1,Jo--),Ct}function an(){return Ct=Qt2||Ps(Ct)>3?"":" "}function Mx(t,e){for(;--e&&an()&&!(Ct<48||Ct>102||Ct>57&&Ct<65||Ct>70&&Ct<97););return Ls(t,ei()+(e<6&&In()==32&&an()==32))}function ml(t){for(;an();)switch(Ct){case t:return Qt;case 34:case 39:t!==34&&t!==39&&ml(Ct);break;case 40:t===41&&ml(t);break;case 92:an();break}return Qt}function Nx(t,e){for(;an()&&t+Ct!==57;)if(t+Ct===84&&In()===47)break;return"/*"+Ls(e,Qt-1)+"*"+Zo(t===47?t:an())}function Ox(t){for(;!Ps(In());)an();return Ls(t,Qt)}function Tx(t){return Hu(ni("",null,null,null,[""],t=Vu(t),0,[0],t))}function ni(t,e,n,r,s,o,i,a,l){for(var c=0,d=0,u=i,f=0,g=0,m=0,p=1,y=1,b=1,_=0,x="",E=s,C=o,k=r,N=x;y;)switch(m=_,_=an()){case 40:if(m!=108&&Pt(N,u-1)==58){gl(N+=tt(ti(_),"&","&\f"),"&\f")!=-1&&(b=-1);break}case 34:case 39:case 91:N+=ti(_);break;case 9:case 10:case 13:case 32:N+=Ex(m);break;case 92:N+=Mx(ei()-1,7);continue;case 47:switch(In()){case 42:case 47:Qo(Ax(Nx(an(),ei()),e,n),l);break;default:N+="/"}break;case 123*p:a[c++]=Dn(N)*b;case 125*p:case 59:case 0:switch(_){case 0:case 125:y=0;case 59+d:b==-1&&(N=tt(N,/\f/g,"")),g>0&&Dn(N)-u&&Qo(g>32?Bu(N+";",r,n,u-1):Bu(tt(N," ","")+";",r,n,u-2),l);break;case 59:N+=";";default:if(Qo(k=Uu(N,e,n,c,d,s,a,x,E=[],C=[],u),o),_===123)if(d===0)ni(N,e,k,k,E,o,u,a,C);else switch(f===99&&Pt(N,3)===110?100:f){case 100:case 108:case 109:case 115:ni(t,k,k,r&&Qo(Uu(t,k,k,0,0,s,a,x,s,E=[],u),C),s,C,u,a,r?E:C);break;default:ni(N,k,k,k,[""],C,0,a,C)}}c=d=g=0,p=b=1,x=N="",u=i;break;case 58:u=1+Dn(N),g=m;default:if(p<1){if(_==123)--p;else if(_==125&&p++==0&&_x()==125)continue}switch(N+=Zo(_),_*p){case 38:b=d>0?1:(N+="\f",-1);break;case 44:a[c++]=(Dn(N)-1)*b,b=1;break;case 64:In()===45&&(N+=ti(an())),f=In(),d=u=Dn(x=N+=Ox(ei())),_++;break;case 45:m===45&&Dn(N)==2&&(p=0)}}return o}function Uu(t,e,n,r,s,o,i,a,l,c,d){for(var u=s-1,f=s===0?o:[""],g=pl(f),m=0,p=0,y=0;m0?f[b]+" "+_:tt(_,/&\f/g,f[b])))&&(l[y++]=x);return Ro(t,e,n,s===0?fl:a,l,c,d)}function Ax(t,e,n){return Ro(t,e,n,Pu,Zo(Sx()),Ds(t,2,-2),0)}function Bu(t,e,n,r){return Ro(t,e,n,hl,Ds(t,0,r),Ds(t,r+1,-1),r)}function jr(t,e){for(var n="",r=pl(t),s=0;s6)switch(Pt(t,e+1)){case 109:if(Pt(t,e+4)!==45)break;case 102:return tt(t,/(.+:)(.+)-([^]+)/,"$1"+et+"$2-$3$1"+qo+(Pt(t,e+3)==108?"$3":"$2-$3"))+t;case 115:return~gl(t,"stretch")?Wu(tt(t,"stretch","fill-available"),e)+t:t}break;case 4949:if(Pt(t,e+1)!==115)break;case 6444:switch(Pt(t,Dn(t)-3-(~gl(t,"!important")&&10))){case 107:return tt(t,":",":"+et)+t;case 101:return tt(t,/(.+:)([^;!]+)(;|!.+)?/,"$1"+et+(Pt(t,14)===45?"inline-":"")+"box$3$1"+et+"$2$3$1"+Ft+"$2box$3")+t}break;case 5936:switch(Pt(t,e+11)){case 114:return et+t+Ft+tt(t,/[svh]\w+-[tblr]{2}/,"tb")+t;case 108:return et+t+Ft+tt(t,/[svh]\w+-[tblr]{2}/,"tb-rl")+t;case 45:return et+t+Ft+tt(t,/[svh]\w+-[tblr]{2}/,"lr")+t}return et+t+Ft+t+t}return t}var Ux=function(e,n,r,s){if(e.length>-1&&!e.return)switch(e.type){case hl:e.return=Wu(e.value,e.length);break;case $u:return jr([Is(e,{value:tt(e.value,"@","@"+et)})],s);case fl:if(e.length)return Cx(e.props,function(o){switch(kx(o,/(::plac\w+|:read-\w+)/)){case":read-only":case":read-write":return jr([Is(e,{props:[tt(o,/:(read-\w+)/,":"+qo+"$1")]})],s);case"::placeholder":return jr([Is(e,{props:[tt(o,/:(plac\w+)/,":"+et+"input-$1")]}),Is(e,{props:[tt(o,/:(plac\w+)/,":"+qo+"$1")]}),Is(e,{props:[tt(o,/:(plac\w+)/,Ft+"input-$1")]})],s)}return""})}},Bx=[Ux],jx=function(e){var n=e.key;if(n==="css"){var r=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(r,function(p){var y=p.getAttribute("data-emotion");y.indexOf(" ")!==-1&&(document.head.appendChild(p),p.setAttribute("data-s",""))})}var s=e.stylisPlugins||Bx,o={},i,a=[];i=e.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+n+' "]'),function(p){for(var y=p.getAttribute("data-emotion").split(" "),b=1;b=4;++r,s-=4)n=t.charCodeAt(r)&255|(t.charCodeAt(++r)&255)<<8|(t.charCodeAt(++r)&255)<<16|(t.charCodeAt(++r)&255)<<24,n=(n&65535)*1540483477+((n>>>16)*59797<<16),n^=n>>>24,e=(n&65535)*1540483477+((n>>>16)*59797<<16)^(e&65535)*1540483477+((e>>>16)*59797<<16);switch(s){case 3:e^=(t.charCodeAt(r+2)&255)<<16;case 2:e^=(t.charCodeAt(r+1)&255)<<8;case 1:e^=t.charCodeAt(r)&255,e=(e&65535)*1540483477+((e>>>16)*59797<<16)}return e^=e>>>13,e=(e&65535)*1540483477+((e>>>16)*59797<<16),((e^e>>>15)>>>0).toString(36)}var Qx={animationIterationCount:1,aspectRatio:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,scale:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},Jx=/[A-Z]|^ms/g,Rx=/_EMO_([^_]+?)_([^]*?)_EMO_/g,qu=function(e){return e.charCodeAt(1)===45},Zu=function(e){return e!=null&&typeof e!="boolean"},vl=Px(function(t){return qu(t)?t:t.replace(Jx,"-$&").toLowerCase()}),Qu=function(e,n){switch(e){case"animation":case"animationName":if(typeof n=="string")return n.replace(Rx,function(r,s,o){return Ln={name:s,styles:o,next:Ln},s})}return Qx[e]!==1&&!qu(e)&&typeof n=="number"&&n!==0?n+"px":n};function $s(t,e,n){if(n==null)return"";var r=n;if(r.__emotion_styles!==void 0)return r;switch(typeof n){case"boolean":return"";case"object":{var s=n;if(s.anim===1)return Ln={name:s.name,styles:s.styles,next:Ln},s.name;var o=n;if(o.styles!==void 0){var i=o.next;if(i!==void 0)for(;i!==void 0;)Ln={name:i.name,styles:i.styles,next:Ln},i=i.next;var a=o.styles+";";return a}return ew(t,e,n)}case"function":{if(t!==void 0){var l=Ln,c=n(t);return Ln=l,$s(t,e,c)}break}}var d=n;return d}function ew(t,e,n){var r="";if(Array.isArray(n))for(var s=0;s({x:t,y:t});function gw(t){const{x:e,y:n,width:r,height:s}=t;return{width:r,height:s,top:n,left:e,right:e+r,bottom:n+s,x:e,y:n}}function ii(){return typeof window<"u"}function tf(t){return rf(t)?(t.nodeName||"").toLowerCase():"#document"}function Wn(t){var e;return(t==null||(e=t.ownerDocument)==null?void 0:e.defaultView)||window}function nf(t){var e;return(e=(rf(t)?t.ownerDocument:t.document)||window.document)==null?void 0:e.documentElement}function rf(t){return ii()?t instanceof Node||t instanceof Wn(t).Node:!1}function pw(t){return ii()?t instanceof Element||t instanceof Wn(t).Element:!1}function Cl(t){return ii()?t instanceof HTMLElement||t instanceof Wn(t).HTMLElement:!1}function sf(t){return!ii()||typeof ShadowRoot>"u"?!1:t instanceof ShadowRoot||t instanceof Wn(t).ShadowRoot}function of(t){const{overflow:e,overflowX:n,overflowY:r,display:s}=_l(t);return/auto|scroll|overlay|hidden|clip/.test(e+r+n)&&s!=="inline"&&s!=="contents"}let Sl;function mw(){return Sl==null&&(Sl=typeof CSS<"u"&&CSS.supports&&CSS.supports("-webkit-backdrop-filter","none")),Sl}function bw(t){return/^(html|body|#document)$/.test(tf(t))}function _l(t){return Wn(t).getComputedStyle(t)}function yw(t){if(tf(t)==="html")return t;const e=t.assignedSlot||t.parentNode||sf(t)&&t.host||nf(t);return sf(e)?e.host:e}function af(t){const e=yw(t);return bw(e)?t.ownerDocument?t.ownerDocument.body:t.body:Cl(e)&&of(e)?e:af(e)}function ai(t,e,n){var r;e===void 0&&(e=[]),n===void 0&&(n=!0);const s=af(t),o=s===((r=t.ownerDocument)==null?void 0:r.body),i=Wn(s);if(o){const a=El(i);return e.concat(i,i.visualViewport||[],of(s)?s:[],a&&n?ai(a):[])}else return e.concat(s,ai(s,[],n))}function El(t){return t.parent&&Object.getPrototypeOf(t.parent)?t.frameElement:null}function vw(t){const e=_l(t);let n=parseFloat(e.width)||0,r=parseFloat(e.height)||0;const s=Cl(t),o=s?t.offsetWidth:n,i=s?t.offsetHeight:r,a=ri(n)!==o||ri(r)!==i;return a&&(n=o,r=i),{width:n,height:r,$:a}}function Ml(t){return pw(t)?t:t.contextElement}function lf(t){const e=Ml(t);if(!Cl(e))return oi(1);const n=e.getBoundingClientRect(),{width:r,height:s,$:o}=vw(e);let i=(o?ri(n.width):n.width)/r,a=(o?ri(n.height):n.height)/s;return(!i||!Number.isFinite(i))&&(i=1),(!a||!Number.isFinite(a))&&(a=1),{x:i,y:a}}const xw=oi(0);function ww(t){const e=Wn(t);return!mw()||!e.visualViewport?xw:{x:e.visualViewport.offsetLeft,y:e.visualViewport.offsetTop}}function kw(t,e,n){return!1}function cf(t,e,n,r){e===void 0&&(e=!1);const s=t.getBoundingClientRect(),o=Ml(t);let i=oi(1);e&&(i=lf(t));const a=kw()?ww(o):oi(0);let l=(s.left+a.x)/i.x,c=(s.top+a.y)/i.y,d=s.width/i.x,u=s.height/i.y;if(o){const f=Wn(o),g=r;let m=f,p=El(m);for(;p&&r&&g!==m;){const y=lf(p),b=p.getBoundingClientRect(),_=_l(p),x=b.left+(p.clientLeft+parseFloat(_.paddingLeft))*y.x,E=b.top+(p.clientTop+parseFloat(_.paddingTop))*y.y;l*=y.x,c*=y.y,d*=y.x,u*=y.y,l+=x,c+=E,m=Wn(p),p=El(m)}}return gw({width:d,height:u,x:l,y:c})}function df(t,e){return t.x===e.x&&t.y===e.y&&t.width===e.width&&t.height===e.height}function Cw(t,e){let n=null,r;const s=nf(t);function o(){var a;clearTimeout(r),(a=n)==null||a.disconnect(),n=null}function i(a,l){a===void 0&&(a=!1),l===void 0&&(l=1),o();const c=t.getBoundingClientRect(),{left:d,top:u,width:f,height:g}=c;if(a||e(),!f||!g)return;const m=si(u),p=si(s.clientWidth-(d+f)),y=si(s.clientHeight-(u+g)),b=si(d),x={rootMargin:-m+"px "+-p+"px "+-y+"px "+-b+"px",threshold:hw(0,fw(1,l))||1};let E=!0;function C(k){const N=k[0].intersectionRatio;if(N!==l){if(!E)return i();N?i(!1,N):r=setTimeout(()=>{i(!1,1e-7)},1e3)}N===1&&!df(c,t.getBoundingClientRect())&&i(),E=!1}try{n=new IntersectionObserver(C,{...x,root:s.ownerDocument})}catch{n=new IntersectionObserver(C,x)}n.observe(t)}return i(!0),o}function Sw(t,e,n,r){r===void 0&&(r={});const{ancestorScroll:s=!0,ancestorResize:o=!0,elementResize:i=typeof ResizeObserver=="function",layoutShift:a=typeof IntersectionObserver=="function",animationFrame:l=!1}=r,c=Ml(t),d=s||o?[...c?ai(c):[],...e?ai(e):[]]:[];d.forEach(b=>{s&&b.addEventListener("scroll",n,{passive:!0}),o&&b.addEventListener("resize",n)});const u=c&&a?Cw(c,n):null;let f=-1,g=null;i&&(g=new ResizeObserver(b=>{let[_]=b;_&&_.target===c&&g&&e&&(g.unobserve(e),cancelAnimationFrame(f),f=requestAnimationFrame(()=>{var x;(x=g)==null||x.observe(e)})),n()}),c&&!l&&g.observe(c),e&&g.observe(e));let m,p=l?cf(t):null;l&&y();function y(){const b=cf(t);p&&!df(p,b)&&n(),p=b,m=requestAnimationFrame(y)}return n(),()=>{var b;d.forEach(_=>{s&&_.removeEventListener("scroll",n),o&&_.removeEventListener("resize",n)}),u?.(),(b=g)==null||b.disconnect(),g=null,l&&cancelAnimationFrame(m)}}var Nl=v.useLayoutEffect,_w=["className","clearValue","cx","getStyles","getClassNames","getValue","hasValue","isMulti","isRtl","options","selectOption","selectProps","setValue","theme"],li=function(){};function Ew(t,e){return e?e[0]==="-"?t+e:t+"__"+e:t}function Mw(t,e){for(var n=arguments.length,r=new Array(n>2?n-2:0),s=2;s-1}function Nw(t){return di(t)?window.innerHeight:t.clientHeight}function ff(t){return di(t)?window.pageYOffset:t.scrollTop}function ui(t,e){if(di(t)){window.scrollTo(0,e);return}t.scrollTop=e}function Ow(t){var e=getComputedStyle(t),n=e.position==="absolute",r=/(auto|scroll)/;if(e.position==="fixed")return document.documentElement;for(var s=t;s=s.parentElement;)if(e=getComputedStyle(s),!(n&&e.position==="static")&&r.test(e.overflow+e.overflowY+e.overflowX))return s;return document.documentElement}function Tw(t,e,n,r){return n*((t=t/r-1)*t*t+1)+e}function fi(t,e){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:200,r=arguments.length>3&&arguments[3]!==void 0?arguments[3]:li,s=ff(t),o=e-s,i=10,a=0;function l(){a+=i;var c=Tw(a,s,o,n);ui(t,c),an.bottom?ui(t,Math.min(e.offsetTop+e.clientHeight-t.offsetHeight+s,t.scrollHeight)):r.top-s1?n-1:0),s=1;s=m)return{placement:"bottom",maxHeight:e};if(w>=m&&!i)return o&&fi(l,M,P),{placement:"bottom",maxHeight:e};if(!i&&w>=r||i&&N>=r){o&&fi(l,M,P);var L=i?N-E:w-E;return{placement:"bottom",maxHeight:L}}if(s==="auto"||i){var $=e,A=i?k:S;return A>=r&&($=Math.min(A-E-a,e)),{placement:"top",maxHeight:$}}if(s==="bottom")return o&&ui(l,M),{placement:"bottom",maxHeight:e};break;case"top":if(k>=m)return{placement:"top",maxHeight:e};if(S>=m&&!i)return o&&fi(l,T,P),{placement:"top",maxHeight:e};if(!i&&S>=r||i&&k>=r){var D=e;return(!i&&S>=r||i&&k>=r)&&(D=i?k-C:S-C),o&&fi(l,T,P),{placement:"top",maxHeight:D}}return{placement:"bottom",maxHeight:e};default:throw new Error('Invalid placement provided "'.concat(s,'".'))}return c}function Uw(t){var e={bottom:"top",top:"bottom"};return t?e[t]:"bottom"}var mf=function(e){return e==="auto"?"bottom":e},Bw=function(e,n){var r,s=e.placement,o=e.theme,i=o.borderRadius,a=o.spacing,l=o.colors;return Me((r={label:"menu"},As(r,Uw(s),"100%"),As(r,"position","absolute"),As(r,"width","100%"),As(r,"zIndex",1),r),n?{}:{backgroundColor:l.neutral0,borderRadius:i,boxShadow:"0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)",marginBottom:a.menuGutter,marginTop:a.menuGutter})},bf=v.createContext(null),jw=function(e){var n=e.children,r=e.minMenuHeight,s=e.maxMenuHeight,o=e.menuPlacement,i=e.menuPosition,a=e.menuShouldScrollIntoView,l=e.theme,c=v.useContext(bf)||{},d=c.setPortalPlacement,u=v.useRef(null),f=v.useState(s),g=jn(f,2),m=g[0],p=g[1],y=v.useState(null),b=jn(y,2),_=b[0],x=b[1],E=l.spacing.controlHeight;return Nl(function(){var C=u.current;if(C){var k=i==="fixed",N=a&&!k,S=Hw({maxHeight:s,menuEl:C,minHeight:r,placement:o,shouldScroll:N,isFixedPosition:k,controlHeight:E});p(S.maxHeight),x(S.placement),d?.(S.placement)}},[s,o,i,a,r,d,E]),n({ref:u,placerProps:Me(Me({},e),{},{placement:_||mf(o),maxHeight:m})})},Ww=function(e){var n=e.children,r=e.innerRef,s=e.innerProps;return _e("div",Ae({},vt(e,"menu",{menu:!0}),{ref:r},s),n)},Gw=Ww,Yw=function(e,n){var r=e.maxHeight,s=e.theme.spacing.baseUnit;return Me({maxHeight:r,overflowY:"auto",position:"relative",WebkitOverflowScrolling:"touch"},n?{}:{paddingBottom:s,paddingTop:s})},Xw=function(e){var n=e.children,r=e.innerProps,s=e.innerRef,o=e.isMulti;return _e("div",Ae({},vt(e,"menuList",{"menu-list":!0,"menu-list--is-multi":o}),{ref:s},r),n)},yf=function(e,n){var r=e.theme,s=r.spacing.baseUnit,o=r.colors;return Me({textAlign:"center"},n?{}:{color:o.neutral40,padding:"".concat(s*2,"px ").concat(s*3,"px")})},Kw=yf,qw=yf,Zw=function(e){var n=e.children,r=n===void 0?"No options":n,s=e.innerProps,o=An(e,zw);return _e("div",Ae({},vt(Me(Me({},o),{},{children:r,innerProps:s}),"noOptionsMessage",{"menu-notice":!0,"menu-notice--no-options":!0}),s),r)},Qw=function(e){var n=e.children,r=n===void 0?"Loading...":n,s=e.innerProps,o=An(e,Vw);return _e("div",Ae({},vt(Me(Me({},o),{},{children:r,innerProps:s}),"loadingMessage",{"menu-notice":!0,"menu-notice--loading":!0}),s),r)},Jw=function(e){var n=e.rect,r=e.offset,s=e.position;return{left:n.left,position:s,top:r,width:n.width,zIndex:1}},Rw=function(e){var n=e.appendTo,r=e.children,s=e.controlElement,o=e.innerProps,i=e.menuPlacement,a=e.menuPosition,l=v.useRef(null),c=v.useRef(null),d=v.useState(mf(i)),u=jn(d,2),f=u[0],g=u[1],m=v.useMemo(function(){return{setPortalPlacement:g}},[]),p=v.useState(null),y=jn(p,2),b=y[0],_=y[1],x=v.useCallback(function(){if(s){var N=Aw(s),S=a==="fixed"?0:window.pageYOffset,w=N[f]+S;(w!==b?.offset||N.left!==b?.rect.left||N.width!==b?.rect.width)&&_({offset:w,rect:N})}},[s,a,f,b?.offset,b?.rect.left,b?.rect.width]);Nl(function(){x()},[x]);var E=v.useCallback(function(){typeof c.current=="function"&&(c.current(),c.current=null),s&&l.current&&(c.current=Sw(s,l.current,x,{elementResize:"ResizeObserver"in window}))},[s,x]);Nl(function(){E()},[E]);var C=v.useCallback(function(N){l.current=N,E()},[E]);if(!n&&a!=="fixed"||!b)return null;var k=_e("div",Ae({ref:C},vt(Me(Me({},e),{},{offset:b.offset,position:a,rect:b.rect}),"menuPortal",{"menu-portal":!0}),o),r);return _e(bf.Provider,{value:m},n?Tn.createPortal(k,n):k)},e1=function(e){var n=e.isDisabled,r=e.isRtl;return{label:"container",direction:r?"rtl":void 0,pointerEvents:n?"none":void 0,position:"relative"}},t1=function(e){var n=e.children,r=e.innerProps,s=e.isDisabled,o=e.isRtl;return _e("div",Ae({},vt(e,"container",{"--is-disabled":s,"--is-rtl":o}),r),n)},n1=function(e,n){var r=e.theme.spacing,s=e.isMulti,o=e.hasValue,i=e.selectProps.controlShouldRenderValue;return Me({alignItems:"center",display:s&&o&&i?"flex":"grid",flex:1,flexWrap:"wrap",WebkitOverflowScrolling:"touch",position:"relative",overflow:"hidden"},n?{}:{padding:"".concat(r.baseUnit/2,"px ").concat(r.baseUnit*2,"px")})},r1=function(e){var n=e.children,r=e.innerProps,s=e.isMulti,o=e.hasValue;return _e("div",Ae({},vt(e,"valueContainer",{"value-container":!0,"value-container--is-multi":s,"value-container--has-value":o}),r),n)},s1=function(){return{alignItems:"center",alignSelf:"stretch",display:"flex",flexShrink:0}},o1=function(e){var n=e.children,r=e.innerProps;return _e("div",Ae({},vt(e,"indicatorsContainer",{indicators:!0}),r),n)},vf,i1=["size"],a1=["innerProps","isRtl","size"],l1={name:"8mmkcg",styles:"display:inline-block;fill:currentColor;line-height:1;stroke:currentColor;stroke-width:0"},xf=function(e){var n=e.size,r=An(e,i1);return _e("svg",Ae({height:n,width:n,viewBox:"0 0 20 20","aria-hidden":"true",focusable:"false",css:l1},r))},Ol=function(e){return _e(xf,Ae({size:20},e),_e("path",{d:"M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"}))},wf=function(e){return _e(xf,Ae({size:20},e),_e("path",{d:"M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"}))},kf=function(e,n){var r=e.isFocused,s=e.theme,o=s.spacing.baseUnit,i=s.colors;return Me({label:"indicatorContainer",display:"flex",transition:"color 150ms"},n?{}:{color:r?i.neutral60:i.neutral20,padding:o*2,":hover":{color:r?i.neutral80:i.neutral40}})},c1=kf,d1=function(e){var n=e.children,r=e.innerProps;return _e("div",Ae({},vt(e,"dropdownIndicator",{indicator:!0,"dropdown-indicator":!0}),r),n||_e(wf,null))},u1=kf,f1=function(e){var n=e.children,r=e.innerProps;return _e("div",Ae({},vt(e,"clearIndicator",{indicator:!0,"clear-indicator":!0}),r),n||_e(Ol,null))},h1=function(e,n){var r=e.isDisabled,s=e.theme,o=s.spacing.baseUnit,i=s.colors;return Me({label:"indicatorSeparator",alignSelf:"stretch",width:1},n?{}:{backgroundColor:r?i.neutral10:i.neutral20,marginBottom:o*2,marginTop:o*2})},g1=function(e){var n=e.innerProps;return _e("span",Ae({},n,vt(e,"indicatorSeparator",{"indicator-separator":!0})))},p1=dw(vf||(vf=uw([` 0%, 80%, 100% { opacity: 0; } 40% { opacity: 1; } -`]))),P1=function(t,n){var r=t.isFocused,s=t.size,o=t.theme,i=o.colors,a=o.spacing.baseUnit;return Ee({label:"loadingIndicator",display:"flex",transition:"color 150ms",alignSelf:"center",fontSize:s,lineHeight:1,marginRight:s,textAlign:"center",verticalAlign:"middle"},n?{}:{color:r?i.neutral60:i.neutral20,padding:a*2})},zl=function(t){var n=t.delay,r=t.offset;return Se("span",{css:Dl({animation:"".concat(L1," 1s ease-in-out ").concat(n,"ms infinite;"),backgroundColor:"currentColor",borderRadius:"1em",display:"inline-block",marginLeft:r?"1em":void 0,height:"1em",verticalAlign:"top",width:"1em"},"","")})},$1=function(t){var n=t.innerProps,r=t.isRtl,s=t.size,o=s===void 0?4:s,i=Vn(t,E1);return Se("div",Oe({},Ct(Ee(Ee({},i),{},{innerProps:n,isRtl:r,size:o}),"loadingIndicator",{indicator:!0,"loading-indicator":!0}),n),Se(zl,{delay:0,offset:r}),Se(zl,{delay:160,offset:!0}),Se(zl,{delay:320,offset:!r}))},F1=function(t,n){var r=t.isDisabled,s=t.isFocused,o=t.theme,i=o.colors,a=o.borderRadius,l=o.spacing;return Ee({label:"control",alignItems:"center",cursor:"default",display:"flex",flexWrap:"wrap",justifyContent:"space-between",minHeight:l.controlHeight,outline:"0 !important",position:"relative",transition:"all 100ms"},n?{}:{backgroundColor:r?i.neutral5:i.neutral0,borderColor:r?i.neutral10:s?i.primary:i.neutral20,borderRadius:a,borderStyle:"solid",borderWidth:1,boxShadow:s?"0 0 0 1px ".concat(i.primary):void 0,"&:hover":{borderColor:s?i.primary:i.neutral30}})},z1=function(t){var n=t.children,r=t.isDisabled,s=t.isFocused,o=t.innerRef,i=t.innerProps,a=t.menuIsOpen;return Se("div",Oe({ref:o},Ct(t,"control",{control:!0,"control--is-disabled":r,"control--is-focused":s,"control--menu-is-open":a}),i,{"aria-disabled":r||void 0}),n)},V1=z1,H1=["data"],U1=function(t,n){var r=t.theme.spacing;return n?{}:{paddingBottom:r.baseUnit*2,paddingTop:r.baseUnit*2}},B1=function(t){var n=t.children,r=t.cx,s=t.getStyles,o=t.getClassNames,i=t.Heading,a=t.headingProps,l=t.innerProps,c=t.label,d=t.theme,u=t.selectProps;return Se("div",Oe({},Ct(t,"group",{group:!0}),l),Se(i,Oe({},a,{selectProps:u,theme:d,getStyles:s,getClassNames:o,cx:r}),c),Se("div",null,n))},j1=function(t,n){var r=t.theme,s=r.colors,o=r.spacing;return Ee({label:"group",cursor:"default",display:"block"},n?{}:{color:s.neutral40,fontSize:"75%",fontWeight:500,marginBottom:"0.25em",paddingLeft:o.baseUnit*3,paddingRight:o.baseUnit*3,textTransform:"uppercase"})},W1=function(t){var n=Cf(t);n.data;var r=Vn(n,H1);return Se("div",Oe({},Ct(t,"groupHeading",{"group-heading":!0}),r))},G1=B1,Y1=["innerRef","isDisabled","isHidden","inputClassName"],X1=function(t,n){var r=t.isDisabled,s=t.value,o=t.theme,i=o.spacing,a=o.colors;return Ee(Ee({visibility:r?"hidden":"visible",transform:s?"translateZ(0)":""},K1),n?{}:{margin:i.baseUnit/2,paddingBottom:i.baseUnit/2,paddingTop:i.baseUnit/2,color:a.neutral80})},Pf={gridArea:"1 / 2",font:"inherit",minWidth:"2px",border:0,margin:0,outline:0,padding:0},K1={flex:"1 1 auto",display:"inline-grid",gridArea:"1 / 1 / 2 / 3",gridTemplateColumns:"0 min-content","&:after":Ee({content:'attr(data-value) " "',visibility:"hidden",whiteSpace:"pre"},Pf)},q1=function(t){return Ee({label:"input",color:"inherit",background:0,opacity:t?0:1,width:"100%"},Pf)},Z1=function(t){var n=t.cx,r=t.value,s=Cf(t),o=s.innerRef,i=s.isDisabled,a=s.isHidden,l=s.inputClassName,c=Vn(s,Y1);return Se("div",Oe({},Ct(t,"input",{"input-container":!0}),{"data-value":r||""}),Se("input",Oe({className:n({input:!0},l),ref:o,style:q1(a),disabled:i},c)))},Q1=Z1,J1=function(t,n){var r=t.theme,s=r.spacing,o=r.borderRadius,i=r.colors;return Ee({label:"multiValue",display:"flex",minWidth:0},n?{}:{backgroundColor:i.neutral10,borderRadius:o/2,margin:s.baseUnit/2})},R1=function(t,n){var r=t.theme,s=r.borderRadius,o=r.colors,i=t.cropWithEllipsis;return Ee({overflow:"hidden",textOverflow:i||i===void 0?"ellipsis":void 0,whiteSpace:"nowrap"},n?{}:{borderRadius:s/2,color:o.neutral80,fontSize:"85%",padding:3,paddingLeft:6})},ek=function(t,n){var r=t.theme,s=r.spacing,o=r.borderRadius,i=r.colors,a=t.isFocused;return Ee({alignItems:"center",display:"flex"},n?{}:{borderRadius:o/2,backgroundColor:a?i.dangerLight:void 0,paddingLeft:s.baseUnit,paddingRight:s.baseUnit,":hover":{backgroundColor:i.dangerLight,color:i.danger}})},$f=function(t){var n=t.children,r=t.innerProps;return Se("div",r,n)},tk=$f,nk=$f;function rk(e){var t=e.children,n=e.innerProps;return Se("div",Oe({role:"button"},n),t||Se(Fl,{size:14}))}var sk=function(t){var n=t.children,r=t.components,s=t.data,o=t.innerProps,i=t.isDisabled,a=t.removeProps,l=t.selectProps,c=r.Container,d=r.Label,u=r.Remove;return Se(c,{data:s,innerProps:Ee(Ee({},Ct(t,"multiValue",{"multi-value":!0,"multi-value--is-disabled":i})),o),selectProps:l},Se(d,{data:s,innerProps:Ee({},Ct(t,"multiValueLabel",{"multi-value__label":!0})),selectProps:l},n),Se(u,{data:s,innerProps:Ee(Ee({},Ct(t,"multiValueRemove",{"multi-value__remove":!0})),{},{"aria-label":"Remove ".concat(n||"option")},a),selectProps:l}))},ok=sk,ik=function(t,n){var r=t.isDisabled,s=t.isFocused,o=t.isSelected,i=t.theme,a=i.spacing,l=i.colors;return Ee({label:"option",cursor:"default",display:"block",fontSize:"inherit",width:"100%",userSelect:"none",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)"},n?{}:{backgroundColor:o?l.primary:s?l.primary25:"transparent",color:r?l.neutral20:o?l.neutral0:"inherit",padding:"".concat(a.baseUnit*2,"px ").concat(a.baseUnit*3,"px"),":active":{backgroundColor:r?void 0:o?l.primary:l.primary50}})},ak=function(t){var n=t.children,r=t.isDisabled,s=t.isFocused,o=t.isSelected,i=t.innerRef,a=t.innerProps;return Se("div",Oe({},Ct(t,"option",{option:!0,"option--is-disabled":r,"option--is-focused":s,"option--is-selected":o}),{ref:i,"aria-disabled":r},a),n)},lk=ak,ck=function(t,n){var r=t.theme,s=r.spacing,o=r.colors;return Ee({label:"placeholder",gridArea:"1 / 1 / 2 / 3"},n?{}:{color:o.neutral50,marginLeft:s.baseUnit/2,marginRight:s.baseUnit/2})},dk=function(t){var n=t.children,r=t.innerProps;return Se("div",Oe({},Ct(t,"placeholder",{placeholder:!0}),r),n)},uk=dk,fk=function(t,n){var r=t.isDisabled,s=t.theme,o=s.spacing,i=s.colors;return Ee({label:"singleValue",gridArea:"1 / 1 / 2 / 3",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},n?{}:{color:r?i.neutral40:i.neutral80,marginLeft:o.baseUnit/2,marginRight:o.baseUnit/2})},hk=function(t){var n=t.children,r=t.isDisabled,s=t.innerProps;return Se("div",Oe({},Ct(t,"singleValue",{"single-value":!0,"single-value--is-disabled":r}),s),n)},gk=hk,Ci={ClearIndicator:A1,Control:V1,DropdownIndicator:T1,DownChevron:If,CrossIcon:Fl,Group:G1,GroupHeading:W1,IndicatorsContainer:S1,IndicatorSeparator:I1,Input:Q1,LoadingIndicator:$1,Menu:d1,MenuList:f1,MenuPortal:y1,LoadingMessage:m1,NoOptionsMessage:p1,MultiValue:ok,MultiValueContainer:tk,MultiValueLabel:nk,MultiValueRemove:rk,Option:lk,Placeholder:uk,SelectContainer:x1,SingleValue:gk,ValueContainer:k1},pk=function(t){return Ee(Ee({},Ci),t.components)},Ff=Number.isNaN||function(t){return typeof t=="number"&&t!==t};function mk(e,t){return!!(e===t||Ff(e)&&Ff(t))}function bk(e,t){if(e.length!==t.length)return!1;for(var n=0;n1?"s":""," ").concat(o.join(","),", selected.");case"select-option":return i?"option ".concat(s," is disabled. Select another option."):"option ".concat(s,", selected.");default:return""}},onFocus:function(t){var n=t.context,r=t.focused,s=t.options,o=t.label,i=o===void 0?"":o,a=t.selectValue,l=t.isDisabled,c=t.isSelected,d=t.isAppleDevice,u=function(p,y){return p&&p.length?"".concat(p.indexOf(y)+1," of ").concat(p.length):""};if(n==="value"&&a)return"value ".concat(i," focused, ").concat(u(a,r),".");if(n==="menu"&&d){var f=l?" disabled":"",g="".concat(c?" selected":"").concat(f);return"".concat(i).concat(g,", ").concat(u(s,r),".")}return""},onFilter:function(t){var n=t.inputValue,r=t.resultsMessage;return"".concat(r).concat(n?" for search term "+n:"",".")}},kk=function(t){var n=t.ariaSelection,r=t.focusedOption,s=t.focusedValue,o=t.focusableOptions,i=t.isFocused,a=t.selectValue,l=t.selectProps,c=t.id,d=t.isAppleDevice,u=l.ariaLiveMessages,f=l.getOptionLabel,g=l.inputValue,m=l.isMulti,p=l.isOptionDisabled,y=l.isSearchable,b=l.menuIsOpen,_=l.options,x=l.screenReaderStatus,M=l.tabSelectsValue,C=l.isLoading,k=l["aria-label"],N=l["aria-live"],S=v.useMemo(function(){return Ee(Ee({},wk),u||{})},[u]),w=v.useMemo(function(){var A="";if(n&&S.onChange){var O=n.option,T=n.options,z=n.removedValue,H=n.removedValues,j=n.value,G=function(q){return Array.isArray(q)?null:q},U=z||O||G(j),W=U?f(U):"",Y=T||H||void 0,V=Y?Y.map(f):[],X=Ee({isDisabled:U&&p(U,a),label:W,labels:V},n);A=S.onChange(X)}return A},[n,S,p,a,f]),E=v.useMemo(function(){var A="",O=r||s,T=!!(r&&a&&a.includes(r));if(O&&S.onFocus){var z={focused:O,label:f(O),isDisabled:p(O,a),isSelected:T,options:o,context:O===r?"menu":"value",selectValue:a,isAppleDevice:d};A=S.onFocus(z)}return A},[r,s,f,p,S,o,a,d]),D=v.useMemo(function(){var A="";if(b&&_.length&&!C&&S.onFilter){var O=x({count:o.length});A=S.onFilter({inputValue:g,resultsMessage:O})}return A},[o,g,b,S,_,x,C]),P=n?.action==="initial-input-focus",L=v.useMemo(function(){var A="";if(S.guidance){var O=s?"value":b?"menu":"input";A=S.guidance({"aria-label":k,context:O,isDisabled:r&&p(r,a),isMulti:m,isSearchable:y,tabSelectsValue:M,isInitialFocus:P})}return A},[k,r,s,m,p,y,b,S,a,M,P]),$=Se(v.Fragment,null,Se("span",{id:"aria-selection"},w),Se("span",{id:"aria-focused"},E),Se("span",{id:"aria-results"},D),Se("span",{id:"aria-guidance"},L));return Se(v.Fragment,null,Se(zf,{id:c},P&&$),Se(zf,{"aria-live":N,"aria-atomic":"false","aria-relevant":"additions text",role:"log"},i&&!P&&$))},Ck=kk,Vl=[{base:"A",letters:"AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷBḂḄḆɃƂƁ"},{base:"C",letters:"CⒸCĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹDḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"DZDŽ"},{base:"Dz",letters:"DzDž"},{base:"E",letters:"EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻFḞƑꝻ"},{base:"G",letters:"GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿJĴɈ"},{base:"K",letters:"KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"LJ"},{base:"Lj",letters:"Lj"},{base:"M",letters:"MⓂMḾṀṂⱮƜ"},{base:"N",letters:"NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"NJ"},{base:"Nj",letters:"Nj"},{base:"O",letters:"OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"P",letters:"PⓅPṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆQꝖꝘɊ"},{base:"R",letters:"RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋVṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌWẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍXẊẌ"},{base:"Y",letters:"YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑbḃḅḇƀƃɓ"},{base:"c",letters:"cⓒcćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓdḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"dzdž"},{base:"e",letters:"eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕfḟƒꝼ"},{base:"g",letters:"gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙjĵǰɉ"},{base:"k",letters:"kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"lj"},{base:"m",letters:"mⓜmḿṁṃɱɯ"},{base:"n",letters:"nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ"},{base:"nj",letters:"nj"},{base:"o",letters:"oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟpṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠqɋꝗꝙ"},{base:"r",letters:"rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢsßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥvṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦwẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧxẋẍ"},{base:"y",letters:"yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩzźẑżžẓẕƶȥɀⱬꝣ"}],Sk=new RegExp("["+Vl.map(function(e){return e.letters}).join("")+"]","g"),Vf={},Hl=0;Hl-1}},Nk=["innerRef"];function Tk(e){var t=e.innerRef,n=Vn(e,Nk),r=n1(n,"onExited","in","enter","exit","appear");return Se("input",Oe({ref:t},r,{css:Dl({label:"dummyInput",background:0,border:0,caretColor:"transparent",fontSize:"inherit",gridArea:"1 / 1 / 2 / 3",outline:0,padding:0,width:1,color:"transparent",left:-100,opacity:0,position:"relative",transform:"scale(.01)"},"","")}))}var Ok=function(t){t.cancelable&&t.preventDefault(),t.stopPropagation()};function Ak(e){var t=e.isEnabled,n=e.onBottomArrive,r=e.onBottomLeave,s=e.onTopArrive,o=e.onTopLeave,i=v.useRef(!1),a=v.useRef(!1),l=v.useRef(0),c=v.useRef(null),d=v.useCallback(function(y,b){if(c.current!==null){var _=c.current,x=_.scrollTop,M=_.scrollHeight,C=_.clientHeight,k=c.current,N=b>0,S=M-C-x,w=!1;S>b&&i.current&&(r&&r(y),i.current=!1),N&&a.current&&(o&&o(y),a.current=!1),N&&b>S?(n&&!i.current&&n(y),k.scrollTop=M,w=!0,i.current=!0):!N&&-b>x&&(s&&!a.current&&s(y),k.scrollTop=0,w=!0,a.current=!0),w&&Ok(y)}},[n,r,s,o]),u=v.useCallback(function(y){d(y,y.deltaY)},[d]),f=v.useCallback(function(y){l.current=y.changedTouches[0].clientY},[]),g=v.useCallback(function(y){var b=l.current-y.changedTouches[0].clientY;d(y,b)},[d]),m=v.useCallback(function(y){if(y){var b=Rw?{passive:!1}:!1;y.addEventListener("wheel",u,b),y.addEventListener("touchstart",f,b),y.addEventListener("touchmove",g,b)}},[g,f,u]),p=v.useCallback(function(y){y&&(y.removeEventListener("wheel",u,!1),y.removeEventListener("touchstart",f,!1),y.removeEventListener("touchmove",g,!1))},[g,f,u]);return v.useEffect(function(){if(t){var y=c.current;return m(y),function(){p(y)}}},[t,m,p]),function(y){c.current=y}}var Bf=["boxSizing","height","overflow","paddingRight","position"],jf={boxSizing:"border-box",overflow:"hidden",position:"relative",height:"100%"};function Wf(e){e.cancelable&&e.preventDefault()}function Gf(e){e.stopPropagation()}function Yf(){var e=this.scrollTop,t=this.scrollHeight,n=e+this.offsetHeight;e===0?this.scrollTop=1:n===t&&(this.scrollTop=e-1)}function Xf(){return"ontouchstart"in window||navigator.maxTouchPoints}var Kf=!!(typeof window<"u"&&window.document&&window.document.createElement),Js=0,ls={capture:!1,passive:!1};function Dk(e){var t=e.isEnabled,n=e.accountForScrollbars,r=n===void 0?!0:n,s=v.useRef({}),o=v.useRef(null),i=v.useCallback(function(l){if(Kf){var c=document.body,d=c&&c.style;if(r&&Bf.forEach(function(m){var p=d&&d[m];s.current[m]=p}),r&&Js<1){var u=parseInt(s.current.paddingRight,10)||0,f=document.body?document.body.clientWidth:0,g=window.innerWidth-f+u||0;Object.keys(jf).forEach(function(m){var p=jf[m];d&&(d[m]=p)}),d&&(d.paddingRight="".concat(g,"px"))}c&&Xf()&&(c.addEventListener("touchmove",Wf,ls),l&&(l.addEventListener("touchstart",Yf,ls),l.addEventListener("touchmove",Gf,ls))),Js+=1}},[r]),a=v.useCallback(function(l){if(Kf){var c=document.body,d=c&&c.style;Js=Math.max(Js-1,0),r&&Js<1&&Bf.forEach(function(u){var f=s.current[u];d&&(d[u]=f)}),c&&Xf()&&(c.removeEventListener("touchmove",Wf,ls),l&&(l.removeEventListener("touchstart",Yf,ls),l.removeEventListener("touchmove",Gf,ls)))}},[r]);return v.useEffect(function(){if(t){var l=o.current;return i(l),function(){a(l)}}},[t,i,a]),function(l){o.current=l}}var Ik=function(t){var n=t.target;return n.ownerDocument.activeElement&&n.ownerDocument.activeElement.blur()},Lk={name:"1kfdb0e",styles:"position:fixed;left:0;bottom:0;right:0;top:0"};function Pk(e){var t=e.children,n=e.lockEnabled,r=e.captureEnabled,s=r===void 0?!0:r,o=e.onBottomArrive,i=e.onBottomLeave,a=e.onTopArrive,l=e.onTopLeave,c=Ak({isEnabled:s,onBottomArrive:o,onBottomLeave:i,onTopArrive:a,onTopLeave:l}),d=Dk({isEnabled:n}),u=function(g){c(g),d(g)};return Se(v.Fragment,null,n&&Se("div",{onClick:Ik,css:Lk}),t(u))}var $k={name:"1a0ro4n-requiredInput",styles:"label:requiredInput;opacity:0;pointer-events:none;position:absolute;bottom:0;left:0;right:0;width:100%"},Fk=function(t){var n=t.name,r=t.onFocus;return Se("input",{required:!0,name:n,tabIndex:-1,"aria-hidden":"true",onFocus:r,css:$k,value:"",onChange:function(){}})},zk=Fk;function jl(e){var t;return typeof window<"u"&&window.navigator!=null?e.test(((t=window.navigator.userAgentData)===null||t===void 0?void 0:t.platform)||window.navigator.platform):!1}function Vk(){return jl(/^iPhone/i)}function qf(){return jl(/^Mac/i)}function Hk(){return jl(/^iPad/i)||qf()&&navigator.maxTouchPoints>1}function Uk(){return Vk()||Hk()}function Bk(){return qf()||Uk()}var jk=function(t){return t.label},Zf=function(t){return t.label},Qf=function(t){return t.value},Wk=function(t){return!!t.isDisabled},Gk={clearIndicator:O1,container:v1,control:F1,dropdownIndicator:N1,group:U1,groupHeading:j1,indicatorsContainer:C1,indicatorSeparator:D1,input:X1,loadingIndicator:P1,loadingMessage:g1,menu:a1,menuList:u1,menuPortal:b1,multiValue:J1,multiValueLabel:R1,multiValueRemove:ek,noOptionsMessage:h1,option:ik,placeholder:ck,singleValue:fk,valueContainer:w1},Yk={primary:"#2684FF",primary75:"#4C9AFF",primary50:"#B2D4FF",primary25:"#DEEBFF",danger:"#DE350B",dangerLight:"#FFBDAD",neutral0:"hsl(0, 0%, 100%)",neutral5:"hsl(0, 0%, 95%)",neutral10:"hsl(0, 0%, 90%)",neutral20:"hsl(0, 0%, 80%)",neutral30:"hsl(0, 0%, 70%)",neutral40:"hsl(0, 0%, 60%)",neutral50:"hsl(0, 0%, 50%)",neutral60:"hsl(0, 0%, 40%)",neutral70:"hsl(0, 0%, 30%)",neutral80:"hsl(0, 0%, 20%)",neutral90:"hsl(0, 0%, 10%)"},Xk=4,Jf=4,Kk=38,qk=Jf*2,Zk={baseUnit:Jf,controlHeight:Kk,menuGutter:qk},Wl={borderRadius:Xk,colors:Yk,spacing:Zk},Qk={"aria-live":"polite",backspaceRemovesValue:!0,blurInputOnSelect:Ef(),captureMenuScroll:!Ef(),classNames:{},closeMenuOnSelect:!0,closeMenuOnScroll:!1,components:{},controlShouldRenderValue:!0,escapeClearsValue:!1,filterOption:Mk(),formatGroupLabel:jk,getOptionLabel:Zf,getOptionValue:Qf,isDisabled:!1,isLoading:!1,isMulti:!1,isRtl:!1,isSearchable:!0,isOptionDisabled:Wk,loadingMessage:function(){return"Loading..."},maxMenuHeight:300,minMenuHeight:140,menuIsOpen:!1,menuPlacement:"bottom",menuPosition:"absolute",menuShouldBlockScroll:!1,menuShouldScrollIntoView:!Qw(),noOptionsMessage:function(){return"No options"},openMenuOnFocus:!1,openMenuOnClick:!0,options:[],pageSize:5,placeholder:"Select...",screenReaderStatus:function(t){var n=t.count;return"".concat(n," result").concat(n!==1?"s":""," available")},styles:{},tabIndex:0,tabSelectsValue:!0,unstyled:!1};function Rf(e,t,n,r){var s=sh(e,t,n),o=oh(e,t,n),i=rh(e,t),a=_i(e,t);return{type:"option",data:t,isDisabled:s,isSelected:o,label:i,value:a,index:r}}function Si(e,t){return e.options.map(function(n,r){if("options"in n){var s=n.options.map(function(i,a){return Rf(e,i,t,a)}).filter(function(i){return nh(e,i)});return s.length>0?{type:"group",data:n,options:s,index:r}:void 0}var o=Rf(e,n,t,r);return nh(e,o)?o:void 0}).filter(e1)}function eh(e){return e.reduce(function(t,n){return n.type==="group"?t.push.apply(t,ss(n.options.map(function(r){return r.data}))):t.push(n.data),t},[])}function th(e,t){return e.reduce(function(n,r){return r.type==="group"?n.push.apply(n,ss(r.options.map(function(s){return{data:s.data,id:"".concat(t,"-").concat(r.index,"-").concat(s.index)}}))):n.push({data:r.data,id:"".concat(t,"-").concat(r.index)}),n},[])}function Jk(e,t){return eh(Si(e,t))}function nh(e,t){var n=e.inputValue,r=n===void 0?"":n,s=t.data,o=t.isSelected,i=t.label,a=t.value;return(!ah(e)||!o)&&ih(e,{label:i,value:a,data:s},r)}function Rk(e,t){var n=e.focusedValue,r=e.selectValue,s=r.indexOf(n);if(s>-1){var o=t.indexOf(n);if(o>-1)return n;if(s-1?n:t[0]}var Gl=function(t,n){var r,s=(r=t.find(function(o){return o.data===n}))===null||r===void 0?void 0:r.id;return s||null},rh=function(t,n){return t.getOptionLabel(n)},_i=function(t,n){return t.getOptionValue(n)};function sh(e,t,n){return typeof e.isOptionDisabled=="function"?e.isOptionDisabled(t,n):!1}function oh(e,t,n){if(n.indexOf(t)>-1)return!0;if(typeof e.isOptionSelected=="function")return e.isOptionSelected(t,n);var r=_i(e,t);return n.some(function(s){return _i(e,s)===r})}function ih(e,t,n){return e.filterOption?e.filterOption(t,n):!0}var ah=function(t){var n=t.hideSelectedOptions,r=t.isMulti;return n===void 0?r:n},tC=1,Yl=(function(e){Nx(n,e);var t=Ax(n);function n(r){var s;if(Ex(this,n),s=t.call(this,r),s.state={ariaSelection:null,focusedOption:null,focusedOptionId:null,focusableOptionsWithIds:[],focusedValue:null,inputIsHidden:!1,isFocused:!1,selectValue:[],clearFocusValueOnUpdate:!1,prevWasFocused:!1,inputIsHiddenAfterUpdate:void 0,prevProps:void 0,instancePrefix:"",isAppleDevice:!1},s.blockOptionHover=!1,s.isComposing=!1,s.commonProps=void 0,s.initialTouchX=0,s.initialTouchY=0,s.openAfterFocus=!1,s.scrollToFocusedOptionOnUpdate=!1,s.userIsDragging=void 0,s.controlRef=null,s.getControlRef=function(l){s.controlRef=l},s.focusedOptionRef=null,s.getFocusedOptionRef=function(l){s.focusedOptionRef=l},s.menuListRef=null,s.getMenuListRef=function(l){s.menuListRef=l},s.inputRef=null,s.getInputRef=function(l){s.inputRef=l},s.focus=s.focusInput,s.blur=s.blurInput,s.onChange=function(l,c){var d=s.props,u=d.onChange,f=d.name;c.name=f,s.ariaOnChange(l,c),u(l,c)},s.setValue=function(l,c,d){var u=s.props,f=u.closeMenuOnSelect,g=u.isMulti,m=u.inputValue;s.onInputChange("",{action:"set-value",prevInputValue:m}),f&&(s.setState({inputIsHiddenAfterUpdate:!g}),s.onMenuClose()),s.setState({clearFocusValueOnUpdate:!0}),s.onChange(l,{action:c,option:d})},s.selectOption=function(l){var c=s.props,d=c.blurInputOnSelect,u=c.isMulti,f=c.name,g=s.state.selectValue,m=u&&s.isOptionSelected(l,g),p=s.isOptionDisabled(l,g);if(m){var y=s.getOptionValue(l);s.setValue(g.filter(function(b){return s.getOptionValue(b)!==y}),"deselect-option",l)}else if(!p)u?s.setValue([].concat(ss(g),[l]),"select-option",l):s.setValue(l,"select-option");else{s.ariaOnChange(l,{action:"select-option",option:l,name:f});return}d&&s.blurInput()},s.removeValue=function(l){var c=s.props.isMulti,d=s.state.selectValue,u=s.getOptionValue(l),f=d.filter(function(m){return s.getOptionValue(m)!==u}),g=Qs(c,f,f[0]||null);s.onChange(g,{action:"remove-value",removedValue:l}),s.focusInput()},s.clearValue=function(){var l=s.state.selectValue;s.onChange(Qs(s.props.isMulti,[],null),{action:"clear",removedValues:l})},s.popValue=function(){var l=s.props.isMulti,c=s.state.selectValue,d=c[c.length-1],u=c.slice(0,c.length-1),f=Qs(l,u,u[0]||null);d&&s.onChange(f,{action:"pop-value",removedValue:d})},s.getFocusedOptionId=function(l){return Gl(s.state.focusableOptionsWithIds,l)},s.getFocusableOptionsWithIds=function(){return th(Si(s.props,s.state.selectValue),s.getElementId("option"))},s.getValue=function(){return s.state.selectValue},s.cx=function(){for(var l=arguments.length,c=new Array(l),d=0;dg||f>g}},s.onTouchEnd=function(l){s.userIsDragging||(s.controlRef&&!s.controlRef.contains(l.target)&&s.menuListRef&&!s.menuListRef.contains(l.target)&&s.blurInput(),s.initialTouchX=0,s.initialTouchY=0)},s.onControlTouchEnd=function(l){s.userIsDragging||s.onControlMouseDown(l)},s.onClearIndicatorTouchEnd=function(l){s.userIsDragging||s.onClearIndicatorMouseDown(l)},s.onDropdownIndicatorTouchEnd=function(l){s.userIsDragging||s.onDropdownIndicatorMouseDown(l)},s.handleInputChange=function(l){var c=s.props.inputValue,d=l.currentTarget.value;s.setState({inputIsHiddenAfterUpdate:!1}),s.onInputChange(d,{action:"input-change",prevInputValue:c}),s.props.menuIsOpen||s.onMenuOpen()},s.onInputFocus=function(l){s.props.onFocus&&s.props.onFocus(l),s.setState({inputIsHiddenAfterUpdate:!1,isFocused:!0}),(s.openAfterFocus||s.props.openMenuOnFocus)&&s.openMenu("first"),s.openAfterFocus=!1},s.onInputBlur=function(l){var c=s.props.inputValue;if(s.menuListRef&&s.menuListRef.contains(document.activeElement)){s.inputRef.focus();return}s.props.onBlur&&s.props.onBlur(l),s.onInputChange("",{action:"input-blur",prevInputValue:c}),s.onMenuClose(),s.setState({focusedValue:null,isFocused:!1})},s.onOptionHover=function(l){if(!(s.blockOptionHover||s.state.focusedOption===l)){var c=s.getFocusableOptions(),d=c.indexOf(l);s.setState({focusedOption:l,focusedOptionId:d>-1?s.getFocusedOptionId(l):null})}},s.shouldHideSelectedOptions=function(){return ah(s.props)},s.onValueInputFocus=function(l){l.preventDefault(),l.stopPropagation(),s.focus()},s.onKeyDown=function(l){var c=s.props,d=c.isMulti,u=c.backspaceRemovesValue,f=c.escapeClearsValue,g=c.inputValue,m=c.isClearable,p=c.isDisabled,y=c.menuIsOpen,b=c.onKeyDown,_=c.tabSelectsValue,x=c.openMenuOnFocus,M=s.state,C=M.focusedOption,k=M.focusedValue,N=M.selectValue;if(!p&&!(typeof b=="function"&&(b(l),l.defaultPrevented))){switch(s.blockOptionHover=!0,l.key){case"ArrowLeft":if(!d||g)return;s.focusValue("previous");break;case"ArrowRight":if(!d||g)return;s.focusValue("next");break;case"Delete":case"Backspace":if(g)return;if(k)s.removeValue(k);else{if(!u)return;d?s.popValue():m&&s.clearValue()}break;case"Tab":if(s.isComposing||l.shiftKey||!y||!_||!C||x&&s.isOptionSelected(C,N))return;s.selectOption(C);break;case"Enter":if(l.keyCode===229)break;if(y){if(!C||s.isComposing)return;s.selectOption(C);break}return;case"Escape":y?(s.setState({inputIsHiddenAfterUpdate:!1}),s.onInputChange("",{action:"menu-close",prevInputValue:g}),s.onMenuClose()):m&&f&&s.clearValue();break;case" ":if(g)return;if(!y){s.openMenu("first");break}if(!C)return;s.selectOption(C);break;case"ArrowUp":y?s.focusOption("up"):s.openMenu("last");break;case"ArrowDown":y?s.focusOption("down"):s.openMenu("first");break;case"PageUp":if(!y)return;s.focusOption("pageup");break;case"PageDown":if(!y)return;s.focusOption("pagedown");break;case"Home":if(!y)return;s.focusOption("first");break;case"End":if(!y)return;s.focusOption("last");break;default:return}l.preventDefault()}},s.state.instancePrefix="react-select-"+(s.props.instanceId||++tC),s.state.selectValue=yi(r.value),r.menuIsOpen&&s.state.selectValue.length){var o=s.getFocusableOptionsWithIds(),i=s.buildFocusableOptions(),a=i.indexOf(s.state.selectValue[0]);s.state.focusableOptionsWithIds=o,s.state.focusedOption=i[a],s.state.focusedOptionId=Gl(o,i[a])}return s}return Mx(n,[{key:"componentDidMount",value:function(){this.startListeningComposition(),this.startListeningToTouch(),this.props.closeMenuOnScroll&&document&&document.addEventListener&&document.addEventListener("scroll",this.onScroll,!0),this.props.autoFocus&&this.focusInput(),this.props.menuIsOpen&&this.state.focusedOption&&this.menuListRef&&this.focusedOptionRef&&_f(this.menuListRef,this.focusedOptionRef),Bk()&&this.setState({isAppleDevice:!0})}},{key:"componentDidUpdate",value:function(s){var o=this.props,i=o.isDisabled,a=o.menuIsOpen,l=this.state.isFocused;(l&&!i&&s.isDisabled||l&&a&&!s.menuIsOpen)&&this.focusInput(),l&&i&&!s.isDisabled?this.setState({isFocused:!1},this.onMenuClose):!l&&!i&&s.isDisabled&&this.inputRef===document.activeElement&&this.setState({isFocused:!0}),this.menuListRef&&this.focusedOptionRef&&this.scrollToFocusedOptionOnUpdate&&(_f(this.menuListRef,this.focusedOptionRef),this.scrollToFocusedOptionOnUpdate=!1)}},{key:"componentWillUnmount",value:function(){this.stopListeningComposition(),this.stopListeningToTouch(),document.removeEventListener("scroll",this.onScroll,!0)}},{key:"onMenuOpen",value:function(){this.props.onMenuOpen()}},{key:"onMenuClose",value:function(){this.onInputChange("",{action:"menu-close",prevInputValue:this.props.inputValue}),this.props.onMenuClose()}},{key:"onInputChange",value:function(s,o){this.props.onInputChange(s,o)}},{key:"focusInput",value:function(){this.inputRef&&this.inputRef.focus()}},{key:"blurInput",value:function(){this.inputRef&&this.inputRef.blur()}},{key:"openMenu",value:function(s){var o=this,i=this.state,a=i.selectValue,l=i.isFocused,c=this.buildFocusableOptions(),d=s==="first"?0:c.length-1;if(!this.props.isMulti){var u=c.indexOf(a[0]);u>-1&&(d=u)}this.scrollToFocusedOptionOnUpdate=!(l&&this.menuListRef),this.setState({inputIsHiddenAfterUpdate:!1,focusedValue:null,focusedOption:c[d],focusedOptionId:this.getFocusedOptionId(c[d])},function(){return o.onMenuOpen()})}},{key:"focusValue",value:function(s){var o=this.state,i=o.selectValue,a=o.focusedValue;if(this.props.isMulti){this.setState({focusedOption:null});var l=i.indexOf(a);a||(l=-1);var c=i.length-1,d=-1;if(i.length){switch(s){case"previous":l===0?d=0:l===-1?d=c:d=l-1;break;case"next":l>-1&&l0&&arguments[0]!==void 0?arguments[0]:"first",o=this.props.pageSize,i=this.state.focusedOption,a=this.getFocusableOptions();if(a.length){var l=0,c=a.indexOf(i);i||(c=-1),s==="up"?l=c>0?c-1:a.length-1:s==="down"?l=(c+1)%a.length:s==="pageup"?(l=c-o,l<0&&(l=0)):s==="pagedown"?(l=c+o,l>a.length-1&&(l=a.length-1)):s==="last"&&(l=a.length-1),this.scrollToFocusedOptionOnUpdate=!0,this.setState({focusedOption:a[l],focusedValue:null,focusedOptionId:this.getFocusedOptionId(a[l])})}}},{key:"getTheme",value:(function(){return this.props.theme?typeof this.props.theme=="function"?this.props.theme(Wl):Ee(Ee({},Wl),this.props.theme):Wl})},{key:"getCommonProps",value:function(){var s=this.clearValue,o=this.cx,i=this.getStyles,a=this.getClassNames,l=this.getValue,c=this.selectOption,d=this.setValue,u=this.props,f=u.isMulti,g=u.isRtl,m=u.options,p=this.hasValue();return{clearValue:s,cx:o,getStyles:i,getClassNames:a,getValue:l,hasValue:p,isMulti:f,isRtl:g,options:m,selectOption:c,selectProps:u,setValue:d,theme:this.getTheme()}}},{key:"hasValue",value:function(){var s=this.state.selectValue;return s.length>0}},{key:"hasOptions",value:function(){return!!this.getFocusableOptions().length}},{key:"isClearable",value:function(){var s=this.props,o=s.isClearable,i=s.isMulti;return o===void 0?i:o}},{key:"isOptionDisabled",value:function(s,o){return sh(this.props,s,o)}},{key:"isOptionSelected",value:function(s,o){return oh(this.props,s,o)}},{key:"filterOption",value:function(s,o){return ih(this.props,s,o)}},{key:"formatOptionLabel",value:function(s,o){if(typeof this.props.formatOptionLabel=="function"){var i=this.props.inputValue,a=this.state.selectValue;return this.props.formatOptionLabel(s,{context:o,inputValue:i,selectValue:a})}else return this.getOptionLabel(s)}},{key:"formatGroupLabel",value:function(s){return this.props.formatGroupLabel(s)}},{key:"startListeningComposition",value:(function(){document&&document.addEventListener&&(document.addEventListener("compositionstart",this.onCompositionStart,!1),document.addEventListener("compositionend",this.onCompositionEnd,!1))})},{key:"stopListeningComposition",value:function(){document&&document.removeEventListener&&(document.removeEventListener("compositionstart",this.onCompositionStart),document.removeEventListener("compositionend",this.onCompositionEnd))}},{key:"startListeningToTouch",value:(function(){document&&document.addEventListener&&(document.addEventListener("touchstart",this.onTouchStart,!1),document.addEventListener("touchmove",this.onTouchMove,!1),document.addEventListener("touchend",this.onTouchEnd,!1))})},{key:"stopListeningToTouch",value:function(){document&&document.removeEventListener&&(document.removeEventListener("touchstart",this.onTouchStart),document.removeEventListener("touchmove",this.onTouchMove),document.removeEventListener("touchend",this.onTouchEnd))}},{key:"renderInput",value:(function(){var s=this.props,o=s.isDisabled,i=s.isSearchable,a=s.inputId,l=s.inputValue,c=s.tabIndex,d=s.form,u=s.menuIsOpen,f=s.required,g=this.getComponents(),m=g.Input,p=this.state,y=p.inputIsHidden,b=p.ariaSelection,_=this.commonProps,x=a||this.getElementId("input"),M=Ee(Ee(Ee({"aria-autocomplete":"list","aria-expanded":u,"aria-haspopup":!0,"aria-errormessage":this.props["aria-errormessage"],"aria-invalid":this.props["aria-invalid"],"aria-label":this.props["aria-label"],"aria-labelledby":this.props["aria-labelledby"],"aria-required":f,role:"combobox","aria-activedescendant":this.state.isAppleDevice?void 0:this.state.focusedOptionId||""},u&&{"aria-controls":this.getElementId("listbox")}),!i&&{"aria-readonly":!0}),this.hasValue()?b?.action==="initial-input-focus"&&{"aria-describedby":this.getElementId("live-region")}:{"aria-describedby":this.getElementId("placeholder")});return i?F.createElement(m,Oe({},_,{autoCapitalize:"none",autoComplete:"off",autoCorrect:"off",id:x,innerRef:this.getInputRef,isDisabled:o,isHidden:y,onBlur:this.onInputBlur,onChange:this.handleInputChange,onFocus:this.onInputFocus,spellCheck:"false",tabIndex:c,form:d,type:"text",value:l},M)):F.createElement(Tk,Oe({id:x,innerRef:this.getInputRef,onBlur:this.onInputBlur,onChange:bi,onFocus:this.onInputFocus,disabled:o,tabIndex:c,inputMode:"none",form:d,value:""},M))})},{key:"renderPlaceholderOrValue",value:function(){var s=this,o=this.getComponents(),i=o.MultiValue,a=o.MultiValueContainer,l=o.MultiValueLabel,c=o.MultiValueRemove,d=o.SingleValue,u=o.Placeholder,f=this.commonProps,g=this.props,m=g.controlShouldRenderValue,p=g.isDisabled,y=g.isMulti,b=g.inputValue,_=g.placeholder,x=this.state,M=x.selectValue,C=x.focusedValue,k=x.isFocused;if(!this.hasValue()||!m)return b?null:F.createElement(u,Oe({},f,{key:"placeholder",isDisabled:p,isFocused:k,innerProps:{id:this.getElementId("placeholder")}}),_);if(y)return M.map(function(S,w){var E=S===C,D="".concat(s.getOptionLabel(S),"-").concat(s.getOptionValue(S));return F.createElement(i,Oe({},f,{components:{Container:a,Label:l,Remove:c},isFocused:E,isDisabled:p,key:D,index:w,removeProps:{onClick:function(){return s.removeValue(S)},onTouchEnd:function(){return s.removeValue(S)},onMouseDown:function(L){L.preventDefault()}},data:S}),s.formatOptionLabel(S,"value"))});if(b)return null;var N=M[0];return F.createElement(d,Oe({},f,{data:N,isDisabled:p}),this.formatOptionLabel(N,"value"))}},{key:"renderClearIndicator",value:function(){var s=this.getComponents(),o=s.ClearIndicator,i=this.commonProps,a=this.props,l=a.isDisabled,c=a.isLoading,d=this.state.isFocused;if(!this.isClearable()||!o||l||!this.hasValue()||c)return null;var u={onMouseDown:this.onClearIndicatorMouseDown,onTouchEnd:this.onClearIndicatorTouchEnd,"aria-hidden":"true"};return F.createElement(o,Oe({},i,{innerProps:u,isFocused:d}))}},{key:"renderLoadingIndicator",value:function(){var s=this.getComponents(),o=s.LoadingIndicator,i=this.commonProps,a=this.props,l=a.isDisabled,c=a.isLoading,d=this.state.isFocused;if(!o||!c)return null;var u={"aria-hidden":"true"};return F.createElement(o,Oe({},i,{innerProps:u,isDisabled:l,isFocused:d}))}},{key:"renderIndicatorSeparator",value:function(){var s=this.getComponents(),o=s.DropdownIndicator,i=s.IndicatorSeparator;if(!o||!i)return null;var a=this.commonProps,l=this.props.isDisabled,c=this.state.isFocused;return F.createElement(i,Oe({},a,{isDisabled:l,isFocused:c}))}},{key:"renderDropdownIndicator",value:function(){var s=this.getComponents(),o=s.DropdownIndicator;if(!o)return null;var i=this.commonProps,a=this.props.isDisabled,l=this.state.isFocused,c={onMouseDown:this.onDropdownIndicatorMouseDown,onTouchEnd:this.onDropdownIndicatorTouchEnd,"aria-hidden":"true"};return F.createElement(o,Oe({},i,{innerProps:c,isDisabled:a,isFocused:l}))}},{key:"renderMenu",value:function(){var s=this,o=this.getComponents(),i=o.Group,a=o.GroupHeading,l=o.Menu,c=o.MenuList,d=o.MenuPortal,u=o.LoadingMessage,f=o.NoOptionsMessage,g=o.Option,m=this.commonProps,p=this.state.focusedOption,y=this.props,b=y.captureMenuScroll,_=y.inputValue,x=y.isLoading,M=y.loadingMessage,C=y.minMenuHeight,k=y.maxMenuHeight,N=y.menuIsOpen,S=y.menuPlacement,w=y.menuPosition,E=y.menuPortalTarget,D=y.menuShouldBlockScroll,P=y.menuShouldScrollIntoView,L=y.noOptionsMessage,$=y.onMenuScrollToTop,A=y.onMenuScrollToBottom;if(!N)return null;var O=function(W,Y){var V=W.type,X=W.data,K=W.isDisabled,q=W.isSelected,ne=W.label,ee=W.value,de=p===X,J=K?void 0:function(){return s.onOptionHover(X)},le=K?void 0:function(){return s.selectOption(X)},pe="".concat(s.getElementId("option"),"-").concat(Y),ae={id:pe,onClick:le,onMouseMove:J,onMouseOver:J,tabIndex:-1,role:"option","aria-selected":s.state.isAppleDevice?void 0:q};return F.createElement(g,Oe({},m,{innerProps:ae,data:X,isDisabled:K,isSelected:q,key:pe,label:ne,type:V,value:ee,isFocused:de,innerRef:de?s.getFocusedOptionRef:void 0}),s.formatOptionLabel(W.data,"menu"))},T;if(this.hasOptions())T=this.getCategorizedOptions().map(function(U){if(U.type==="group"){var W=U.data,Y=U.options,V=U.index,X="".concat(s.getElementId("group"),"-").concat(V),K="".concat(X,"-heading");return F.createElement(i,Oe({},m,{key:X,data:W,options:Y,Heading:a,headingProps:{id:K,data:U.data},label:s.formatGroupLabel(U.data)}),U.options.map(function(q){return O(q,"".concat(V,"-").concat(q.index))}))}else if(U.type==="option")return O(U,"".concat(U.index))});else if(x){var z=M({inputValue:_});if(z===null)return null;T=F.createElement(u,m,z)}else{var H=L({inputValue:_});if(H===null)return null;T=F.createElement(f,m,H)}var j={minMenuHeight:C,maxMenuHeight:k,menuPlacement:S,menuPosition:w,menuShouldScrollIntoView:P},G=F.createElement(l1,Oe({},m,j),function(U){var W=U.ref,Y=U.placerProps,V=Y.placement,X=Y.maxHeight;return F.createElement(l,Oe({},m,j,{innerRef:W,innerProps:{onMouseDown:s.onMenuMouseDown,onMouseMove:s.onMenuMouseMove},isLoading:x,placement:V}),F.createElement(Pk,{captureEnabled:b,onTopArrive:$,onBottomArrive:A,lockEnabled:D},function(K){return F.createElement(c,Oe({},m,{innerRef:function(ne){s.getMenuListRef(ne),K(ne)},innerProps:{role:"listbox","aria-multiselectable":m.isMulti,id:s.getElementId("listbox")},isLoading:x,maxHeight:X,focusedOption:p}),T)}))});return E||w==="fixed"?F.createElement(d,Oe({},m,{appendTo:E,controlElement:this.controlRef,menuPlacement:S,menuPosition:w}),G):G}},{key:"renderFormField",value:function(){var s=this,o=this.props,i=o.delimiter,a=o.isDisabled,l=o.isMulti,c=o.name,d=o.required,u=this.state.selectValue;if(d&&!this.hasValue()&&!a)return F.createElement(zk,{name:c,onFocus:this.onValueInputFocus});if(!(!c||a))if(l)if(i){var f=u.map(function(p){return s.getOptionValue(p)}).join(i);return F.createElement("input",{name:c,type:"hidden",value:f})}else{var g=u.length>0?u.map(function(p,y){return F.createElement("input",{key:"i-".concat(y),name:c,type:"hidden",value:s.getOptionValue(p)})}):F.createElement("input",{name:c,type:"hidden",value:""});return F.createElement("div",null,g)}else{var m=u[0]?this.getOptionValue(u[0]):"";return F.createElement("input",{name:c,type:"hidden",value:m})}}},{key:"renderLiveRegion",value:function(){var s=this.commonProps,o=this.state,i=o.ariaSelection,a=o.focusedOption,l=o.focusedValue,c=o.isFocused,d=o.selectValue,u=this.getFocusableOptions();return F.createElement(Ck,Oe({},s,{id:this.getElementId("live-region"),ariaSelection:i,focusedOption:a,focusedValue:l,isFocused:c,selectValue:d,focusableOptions:u,isAppleDevice:this.state.isAppleDevice}))}},{key:"render",value:function(){var s=this.getComponents(),o=s.Control,i=s.IndicatorsContainer,a=s.SelectContainer,l=s.ValueContainer,c=this.props,d=c.className,u=c.id,f=c.isDisabled,g=c.menuIsOpen,m=this.state.isFocused,p=this.commonProps=this.getCommonProps();return F.createElement(a,Oe({},p,{className:d,innerProps:{id:u,onKeyDown:this.onKeyDown},isDisabled:f,isFocused:m}),this.renderLiveRegion(),F.createElement(o,Oe({},p,{innerRef:this.getControlRef,innerProps:{onMouseDown:this.onControlMouseDown,onTouchEnd:this.onControlTouchEnd},isDisabled:f,isFocused:m,menuIsOpen:g}),F.createElement(l,Oe({},p,{isDisabled:f}),this.renderPlaceholderOrValue(),this.renderInput()),F.createElement(i,Oe({},p,{isDisabled:f}),this.renderClearIndicator(),this.renderLoadingIndicator(),this.renderIndicatorSeparator(),this.renderDropdownIndicator())),this.renderMenu(),this.renderFormField())}}],[{key:"getDerivedStateFromProps",value:function(s,o){var i=o.prevProps,a=o.clearFocusValueOnUpdate,l=o.inputIsHiddenAfterUpdate,c=o.ariaSelection,d=o.isFocused,u=o.prevWasFocused,f=o.instancePrefix,g=s.options,m=s.value,p=s.menuIsOpen,y=s.inputValue,b=s.isMulti,_=yi(m),x={};if(i&&(m!==i.value||g!==i.options||p!==i.menuIsOpen||y!==i.inputValue)){var M=p?Jk(s,_):[],C=p?th(Si(s,_),"".concat(f,"-option")):[],k=a?Rk(o,_):null,N=eC(o,M),S=Gl(C,N);x={selectValue:_,focusedOption:N,focusedOptionId:S,focusableOptionsWithIds:C,focusedValue:k,clearFocusValueOnUpdate:!1}}var w=l!=null&&s!==i?{inputIsHidden:l,inputIsHiddenAfterUpdate:void 0}:{},E=c,D=d&&u;return d&&!D&&(E={value:Qs(b,_,_[0]||null),options:_,action:"initial-input-focus"},D=!u),c?.action==="initial-input-focus"&&(E=null),Ee(Ee(Ee({},x),w),{},{prevProps:s,ariaSelection:E,prevWasFocused:D})}}]),n})(v.Component);Yl.defaultProps=Qk;var nC=v.forwardRef(function(e,t){var n=Wu(e);return F.createElement(Yl,Oe({ref:t},n))}),lh=nC,rC=["allowCreateWhileLoading","createOptionPosition","formatCreateLabel","isValidNewOption","getNewOptionData","onCreateOption","options","onChange"],ch=function(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"",n=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,s=String(t).toLowerCase(),o=String(r.getOptionValue(n)).toLowerCase(),i=String(r.getOptionLabel(n)).toLowerCase();return o===s||i===s},Xl={formatCreateLabel:function(t){return'Create "'.concat(t,'"')},isValidNewOption:function(t,n,r,s){return!(!t||n.some(function(o){return ch(t,o,s)})||r.some(function(o){return ch(t,o,s)}))},getNewOptionData:function(t,n){return{label:n,value:t,__isNew__:!0}}};function sC(e){var t=e.allowCreateWhileLoading,n=t===void 0?!1:t,r=e.createOptionPosition,s=r===void 0?"last":r,o=e.formatCreateLabel,i=o===void 0?Xl.formatCreateLabel:o,a=e.isValidNewOption,l=a===void 0?Xl.isValidNewOption:a,c=e.getNewOptionData,d=c===void 0?Xl.getNewOptionData:c,u=e.onCreateOption,f=e.options,g=f===void 0?[]:f,m=e.onChange,p=Vn(e,rC),y=p.getOptionValue,b=y===void 0?Qf:y,_=p.getOptionLabel,x=_===void 0?Zf:_,M=p.inputValue,C=p.isLoading,k=p.isMulti,N=p.value,S=p.name,w=v.useMemo(function(){return l(M,yi(N),g,{getOptionValue:b,getOptionLabel:x})?d(M,i(M)):void 0},[i,d,x,b,M,l,g,N]),E=v.useMemo(function(){return(n||!C)&&w?s==="first"?[w].concat(ss(g)):[].concat(ss(g),[w]):g},[n,s,C,w,g]),D=v.useCallback(function(P,L){if(L.action!=="select-option")return m(P,L);var $=Array.isArray(P)?P:[P];if($[$.length-1]===w){if(u)u(M);else{var A=d(M,M),O={action:"create-option",name:S,option:A};m(Qs(k,[].concat(ss(yi(N)),[A]),A),O)}return}m(P,L)},[d,M,k,S,w,u,m,N]);return Ee(Ee({},p),{},{options:E,onChange:D})}var oC=v.forwardRef(function(e,t){var n=Wu(e),r=sC(n);return F.createElement(Yl,Oe({ref:t},r))}),dh=oC;const Ut={control:"input-tag",valueContainer:"gap-1 flex flex-wrap",multiValue:{blue:"bg-blue-100 dark:bg-blue-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",green:"bg-green-100 dark:bg-green-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",amber:"bg-amber-100 dark:bg-amber-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",red:"bg-red-100 dark:bg-red-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",purple:"bg-purple-100 dark:bg-purple-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",gray:"bg-gray-100 dark:bg-gray-700/50 rounded px-1.5 py-0.5 flex items-center gap-1"},multiValueLabel:{blue:"text-blue-700 dark:text-blue-300 text-xs",green:"text-green-700 dark:text-green-300 text-xs font-semibold",amber:"text-amber-700 dark:text-amber-300 text-xs font-semibold",red:"text-red-700 dark:text-red-300 text-xs font-semibold",purple:"text-purple-700 dark:text-purple-300 text-xs font-semibold",gray:"text-gray-700 dark:text-gray-300 text-xs"},multiValueRemove:{blue:"text-blue-700 dark:text-blue-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",green:"text-green-700 dark:text-green-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",amber:"text-amber-700 dark:text-amber-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",red:"text-red-700 dark:text-red-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",purple:"text-purple-700 dark:text-purple-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",gray:"text-gray-700 dark:text-gray-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1"},input:"text-gray-900 dark:text-white text-sm",placeholder:"text-gray-400 dark:text-gray-500 text-sm",menu:"mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",menuList:"py-1",option:{base:"px-3 py-2 cursor-pointer text-gray-900 dark:text-white text-[13px]",selected:"bg-blue-100 dark:bg-blue-900/20",focused:"bg-gray-100 dark:bg-gray-700",default:"bg-white dark:bg-gray-800"},noOptionsMessage:"px-3 py-2 text-gray-500 dark:text-gray-400 text-[13px]",checkbox:{base:"w-4 h-4 rounded border flex items-center justify-center text-white text-[10px] shrink-0",selected:"bg-blue-500 border-blue-500",unselected:"border-gray-400 dark:border-gray-500"}},uh=e=>{const{data:t,isSelected:n}=e;return h.jsx(Ci.Option,{...e,children:h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx("span",{className:`${Ut.checkbox.base} ${n?Ut.checkbox.selected:Ut.checkbox.unselected}`,children:n?"✓":""}),h.jsxs("div",{className:"flex-1 min-w-0",children:[h.jsxs("div",{className:"flex items-center gap-1",children:[h.jsx("span",{className:"text-[13px]",children:t.label}),t.count!==void 0&&t.count>0&&h.jsxs("span",{className:"text-gray-500 dark:text-gray-400 text-[10px]",children:["(",t.count,")"]})]}),t.description&&h.jsx("p",{className:"text-xs text-gray-500 dark:text-gray-400 truncate",children:t.description})]})]})})},fh=e=>h.jsx(Ci.GroupHeading,{...e,children:h.jsx("div",{className:"px-3 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-700",children:e.data.label})}),iC=e=>{const{data:t}=e;return h.jsxs(Ci.MultiValueLabel,{...e,children:[h.jsx("span",{children:t.label}),t.count!==void 0&&t.count>0&&h.jsxs("span",{className:"text-xs opacity-60 ml-1",children:["(",t.count,")"]})]})},aC=({value:e,options:t=[],onChange:n,creatable:r=!1,placeholder:s="검색 또는 입력...",noOptionsMessage:o="검색 결과가 없습니다",formatCreateLabel:i,createLabelSuffix:a="추가",maxItems:l,disabled:c=!1,onBeforeRemove:d,onCreateOption:u,className:f="",tagVariants:g,defaultVariant:m="blue",name:p,isMulti:y=!0,isClearable:b=!0,isSearchable:_=!0,delimiters:x=[","],splitOnPaste:M=!0,onInputChange:C,id:k,editorAttrs:N})=>{const S=c===!0||c==="true",w=v.useCallback(J=>J==null?[]:Array.isArray(J)?J:[J],[]),[E,D]=v.useState(()=>w(e)),[P,L]=v.useState(""),$=v.useRef(void 0);v.useEffect(()=>{const J=(pe,ae)=>!pe&&!ae?!0:!pe||!ae||pe.length!==ae.length?!1:pe.every((ye,he)=>ye===ae[he]),le=w(e);J(le,$.current)||(D(le),$.current=le)},[e,w]);const A=v.useMemo(()=>i?typeof i=="string"?J=>`${i} "${J}"`:i:J=>h.jsxs("span",{className:"text-[13px]",children:[h.jsx("strong",{children:J})," ",a]}),[i,a]),O=v.useMemo(()=>!E||!Array.isArray(E)?[]:E.map(J=>t.find(pe=>pe.value===J)||{value:J,label:String(J)}).filter(J=>J!==void 0),[E,t]),T=v.useMemo(()=>{const J=new Set(t.map(ke=>ke.value)),le=E?E.filter(ke=>!J.has(ke)).map(ke=>({value:ke,label:String(ke)})):[],pe=[...t,...le];if(!pe.some(ke=>ke.group))return pe;const ye=new Map,he=[];pe.forEach(ke=>{if(ke.group){const Fe=ye.get(ke.group)||[];Fe.push(ke),ye.set(ke.group,Fe)}else he.push(ke)});const ge=[];return ye.forEach((ke,Fe)=>{ge.push({label:Fe,options:ke})}),he.length>0&&ge.push({label:"",options:he}),ge},[t,E]),z=v.useCallback(J=>({target:{value:J},currentTarget:{value:J},preventDefault:()=>{},stopPropagation:()=>{}}),[]),H=v.useCallback(J=>{if(!x?.length)return[J.trim()].filter(Boolean);const le=x.map(ae=>ae.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),pe=new RegExp(`[${le.join("")}]`);return J.split(pe).map(ae=>ae.trim()).filter(Boolean)},[x]),j=v.useCallback((J,le)=>{if(le.action!=="input-change"){L(J);return}if(C&&C(z(J)),!x?.some(he=>J.includes(he))||!r||!y){L(J);return}const ye=H(J).filter(he=>he&&!E.includes(he));if(ye.length>0){const he=l?l-E.length:1/0,ge=ye.slice(0,he);if(ge.length>0){const ke=[...E,...ge];D(ke),n(z(ke))}}L("")},[x,r,y,H,E,l,n,z,C]),G=v.useCallback(J=>{if(!M||!r||!y)return;const le=J.clipboardData.getData("text"),pe=H(le);if(pe.length<=1)return;J.preventDefault();const ae=pe.filter(ye=>ye&&!E.includes(ye));if(ae.length>0){const ye=l?l-E.length:1/0,he=ae.slice(0,ye);if(he.length>0){const ge=[...E,...he];D(ge),n(z(ge))}}},[M,r,y,H,E,l,n,z]),U=v.useCallback((J,le)=>{if((le.action==="remove-value"||le.action==="pop-value")&&le.removedValue&&d){const ae=d(le.removedValue);if(ae!==!0){const ye=typeof ae=="string"?ae:`"${le.removedValue.label}"은(는) 제거할 수 없습니다.`;alert(ye);return}}if(l&&J.length>l)return;const pe=J.map(ae=>ae.value);D(pe),n(z(pe))},[n,d,l,z]),W=v.useCallback((J,le)=>{if(le.action==="clear"&&E.length>0&&d){const ye=t.find(ge=>ge.value===E[0])||{value:E[0],label:String(E[0])},he=d(ye);if(he!==!0){const ge=typeof he=="string"?he:`"${ye.label}"은(는) 제거할 수 없습니다.`;alert(ge);return}}const pe=J?J.value:null,ae=J?[J.value]:[];D(ae),n(z(pe))},[n,d,E,t,z]),Y=v.useCallback(J=>{u&&u(J);const le=[...E||[],J];D(le),n(z(le))},[n,u,E,z]),V=v.useCallback(J=>!J||!J.trim()?!1:!E?.includes(J.trim()),[E]),X=v.useCallback(J=>{const le=P.trim();if(J.key==="Tab"&&r&&y&&le){if(E.includes(le))J.preventDefault(),L("");else if(!l||E.lengthg&&g[J]?g[J]:m,[g,m]),q={control:()=>`${Ut.control} ${f} ${S?"opacity-60 cursor-not-allowed":""}`,valueContainer:()=>Ut.valueContainer,multiValue:J=>{const le=K(J.data.value);return Ut.multiValue[le]},multiValueLabel:J=>{const le=K(J.data.value);return Ut.multiValueLabel[le]},multiValueRemove:J=>{const le=K(J.data.value);return`${Ut.multiValueRemove[le]} ${S?"pointer-events-none":""}`},singleValue:()=>"text-gray-900 dark:text-white text-sm",input:()=>Ut.input,placeholder:()=>Ut.placeholder,menu:()=>Ut.menu,menuList:()=>Ut.menuList,menuPortal:()=>"z-[9999]",option:J=>`${Ut.option.base} ${J.isSelected?Ut.option.selected:J.isFocused?Ut.option.focused:Ut.option.default}`,noOptionsMessage:()=>Ut.noOptionsMessage,clearIndicator:()=>"text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 cursor-pointer px-1",dropdownIndicator:()=>"text-gray-400 dark:text-gray-500 px-1"},ne={options:T,onKeyDown:X,placeholder:s,noOptionsMessage:()=>o,isDisabled:S,isSearchable:_,menuIsOpen:void 0,openMenuOnClick:!0,openMenuOnFocus:!0,menuPortalTarget:typeof document<"u"?document.body:null,menuPosition:"fixed",classNamePrefix:"tag-input",unstyled:!0,classNames:q,...r&&y||C?{inputValue:P,onInputChange:j}:{}},ee=y?E.join(","):E[0]??"";if(!y){const J={...ne,isMulti:!1,value:O[0]||null,onChange:W,isClearable:b,closeMenuOnSelect:!0,components:{Option:uh,GroupHeading:fh,IndicatorSeparator:()=>null,...b?{}:{ClearIndicator:()=>null}}};return r?h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ee}),h.jsx(dh,{...J,onCreateOption:Y,formatCreateLabel:A,isValidNewOption:V,createOptionPosition:"first"})]}):h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ee}),h.jsx(lh,{...J})]})}const de={...ne,isMulti:!0,value:O,onChange:U,closeMenuOnSelect:!1,hideSelectedOptions:!1,backspaceRemovesValue:!1,isClearable:!1,components:{Option:uh,GroupHeading:fh,MultiValueLabel:iC,ClearIndicator:()=>null,DropdownIndicator:()=>null,IndicatorSeparator:()=>null}};return r?h.jsxs(I,{id:k,...N,onPaste:G,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ee}),h.jsx(dh,{...de,onCreateOption:Y,formatCreateLabel:A,isValidNewOption:V,createOptionPosition:"first"})]}):h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ee}),h.jsx(lh,{...de})]})},cs=(e,t)=>window.G7Core?.t?.(e,t)??e,lC=()=>window.G7Core?.locale?.supported?.()??["ko","en"],cC=()=>window.G7Core?.locale?.current?.()??"ko",Kl=e=>{const t=cs(`common.language_${e}`);return t===`common.language_${e}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[e]||e.toUpperCase():t},dC=({value:e=[],onChange:t,placeholder:n="입력 후 Enter",disabled:r=!1,creatable:s=!0,delimiters:o=[","],className:i="",name:a,error:l,maxItems:c,modalId:d,statePath:u,__componentContext:f,id:g,editorAttrs:m})=>{const p=v.useMemo(()=>lC(),[]),y=v.useMemo(()=>cC(),[]),[b,_]=v.useState(()=>e||[]),[x,M]=v.useState(""),[C,k]=v.useState(!1),[N,S]=v.useState(null),[w,E]=v.useState({}),D=v.useRef(null),P=v.useRef(void 0);v.useEffect(()=>{const V=(K,q)=>!K&&!q?!0:!K||!q||K.length!==q.length?!1:JSON.stringify(K)===JSON.stringify(q),X=e||[];V(X,P.current)||(_(X),P.current=X)},[e]);const L=v.useCallback(V=>({target:{name:a,value:V},currentTarget:{name:a,value:V},preventDefault:()=>{},stopPropagation:()=>{}}),[a]),$=v.useCallback(V=>{const X=[];for(const K of p){const q=V[K];q?.trim()&&X.push(`${K.toUpperCase()}: ${q}`)}return X.length>0?X.join(" / "):""},[p]),A=v.useCallback(V=>{const X=b.filter((K,q)=>q!==V);_(X),t?.(L(X))},[b,t,L]),O=v.useCallback((V,X,K)=>{const q=window.G7Core;if(!q?.dispatch)return;const ne=p.map(ee=>({code:ee,label:Kl(ee)}));q.dispatch({handler:"setState",params:{target:"global",multilingualTagEdit:{fieldName:a,editingIndex:V,values:X,supportedLocales:ne,defaultLocale:y,statePath:u,currentTags:K}}}),q.dispatch({handler:"openModal",target:d},{componentContext:f})},[a,p,y,d,u,f]),T=v.useCallback(V=>{r||(d?O(V,{...b[V]},b):(S(V),E({...b[V]}),k(!0)))},[r,b,d,O]),z=v.useCallback((V="")=>{const X={};p.forEach(K=>{X[K]=K===y?V:""}),d?(O(null,X,b),M("")):(S(null),E(X),k(!0),M(""))},[p,y,d,O,b]),H=v.useCallback(()=>{if(!Object.values(w).some(K=>K.trim()!=="")){k(!1);return}let X;N!==null?X=b.map((K,q)=>q===N?w:K):X=[...b,w],_(X),t?.(L(X)),k(!1),E({}),S(null)},[w,N,b,t,L]),j=v.useCallback(()=>{k(!1),E({}),S(null)},[]),G=v.useCallback(V=>{const X=V.target.value;if(o.some(q=>X.includes(q))&&s){const q=o.map(de=>de.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),ne=new RegExp(`[${q.join("")}]`),ee=X.split(ne).map(de=>de.trim()).filter(Boolean);if(ee.length>0){z(ee[0]);return}}M(X)},[o,s,z]),U=v.useCallback(V=>{if(V.key==="Enter"&&x.trim()&&s){if(V.preventDefault(),c&&b.length>=c)return;z(x.trim())}},[x,s,c,b.length,z]),W=v.useCallback((V,X)=>{E(K=>({...K,[V]:X}))},[]),Y=!!l;return h.jsxs(I,{className:i,id:g,...m,children:[h.jsxs(I,{className:`flex flex-wrap gap-1.5 p-2 border rounded-lg bg-white dark:bg-gray-700 min-h-[42px] ${Y?"border-red-500 dark:border-red-400":"border-gray-300 dark:border-gray-600 focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500"} ${r?"opacity-60 bg-gray-100 dark:bg-gray-800 cursor-not-allowed":""}`,children:[b.map((V,X)=>h.jsxs(I,{className:"flex items-center gap-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded px-2 py-0.5 text-sm",children:[h.jsx(Q,{className:`cursor-pointer hover:underline ${r?"cursor-not-allowed":""}`,onClick:()=>T(X),children:$(V)}),!r&&h.jsx(ie,{type:"button",className:"p-0 h-4 w-4 min-w-0 border-0 bg-transparent text-blue-600 dark:text-blue-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-transparent",onClick:K=>{K.stopPropagation(),A(X)},children:h.jsx(se,{name:"times",className:"text-xs"})})]},X)),s&&!r&&(!c||b.lengthh.jsxs(I,{className:"space-y-1",children:[h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300 w-16",children:Kl(V)}),h.jsxs(Q,{className:"text-xs text-gray-400 dark:text-gray-500",children:["(",V.toUpperCase(),")"]})]}),h.jsx(_t,{type:"text",value:w[V]??"",onChange:X=>W(V,X.target.value),placeholder:`${Kl(V)} 값 입력`,className:"w-full",autoFocus:V===y})]},V)),h.jsxs(I,{className:"flex justify-end gap-2 pt-4 border-t border-gray-200 dark:border-gray-700",children:[h.jsx(ie,{type:"button",variant:"secondary",onClick:j,children:cs("common.cancel")}),h.jsx(ie,{type:"button",variant:"primary",onClick:H,children:cs(N!==null?"common.save":"common.add")})]})]})}),a&&h.jsx("input",{type:"hidden",name:a,value:JSON.stringify(b)})]})},hh=(e,t)=>window.G7Core?.t?.(e,t)??e,gh=({type:e,identifier:t,name:n,installedModules:r=[],installedPlugins:s=[],className:o="",style:i,id:a,editorAttrs:l})=>{const c={module:ce.Cubes,plugin:ce.PuzzlePiece},d={module:hh("common.module"),plugin:hh("common.plugin")},u=v.useMemo(()=>{if(n)return n;if(t){const p=(e==="module"?r:s)?.find(y=>y.identifier===t);if(p)return p.name}},[n,t,e,r,s]),f=c[e],g=u?`${u} ${d[e]}`:d[e];return h.jsxs(Q,{className:`inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium text-gray-400 dark:text-gray-500 ${o}`,style:i,id:a,...l,children:[h.jsx(se,{name:f,className:"w-3.5 h-3.5"}),h.jsx(Q,{children:g})]})},uC=({options:e=[],value:t,onChange:n,multiple:r=!1,searchPlaceholder:s="Search...",onSearch:o,noResultsText:i="No results found",initialText:a="Type to search",triggerLabel:l="Select",triggerIcon:c,disabled:d=!1,className:u="",id:f,editorAttrs:g})=>{const[m,p]=v.useState(!1),[y,b]=v.useState(""),_=v.useRef(null),x=v.useRef(null),M=v.useRef(`searchable-dropdown-input-${Math.random().toString(36).slice(2,9)}`).current,C=v.useMemo(()=>t==null?[]:Array.isArray(t)?t:[t],[t]),k=v.useMemo(()=>{if(!y.trim())return e;const A=y.toLowerCase();return e.filter(O=>O.label.toLowerCase().includes(A)||O.description?.toLowerCase().includes(A))},[e,y]),N=v.useMemo(()=>{if(C.length===0)return l;if(r){const O=C.length;return O===1?e.find(z=>String(z.value)===String(C[0]))?.label||l:`${O} selected`}return e.find(O=>String(O.value)===String(C[0]))?.label||l},[C,e,r,l]);v.useEffect(()=>{const A=O=>{_.current&&!_.current.contains(O.target)&&p(!1)};return m&&document.addEventListener("mousedown",A),()=>{document.removeEventListener("mousedown",A)}},[m]),v.useEffect(()=>{const A=O=>{O.key==="Escape"&&m&&(p(!1),x.current?.focus())};return document.addEventListener("keydown",A),()=>document.removeEventListener("keydown",A)},[m]),v.useEffect(()=>{if(!m){b("");return}const A=setTimeout(()=>{document.getElementById(M)?.focus()},50);return()=>{clearTimeout(A)}},[m,M]);const S=v.useCallback(()=>{d||p(A=>!A)},[d]),w=v.useCallback(A=>{const O=A.target.value;b(O),o?.(O)},[o]),E=v.useCallback(A=>{if(r){const T=C.some(z=>String(z)===String(A))?C.filter(z=>String(z)!==String(A)):[...C,A];n?.(T)}else n?.(A),p(!1),x.current?.focus()},[r,C,n]),D=A=>C.some(O=>String(O)===String(A)),P=y.length===0&&e.length===0,L=y.length>0&&k.length===0,$=k.length>0;return h.jsxs(I,{ref:_,className:`relative inline-block ${u}`,id:f,...g,children:[h.jsxs(ie,{ref:x,type:"button",onClick:S,disabled:d,className:` +`]))),m1=function(e,n){var r=e.isFocused,s=e.size,o=e.theme,i=o.colors,a=o.spacing.baseUnit;return Me({label:"loadingIndicator",display:"flex",transition:"color 150ms",alignSelf:"center",fontSize:s,lineHeight:1,marginRight:s,textAlign:"center",verticalAlign:"middle"},n?{}:{color:r?i.neutral60:i.neutral20,padding:a*2})},Tl=function(e){var n=e.delay,r=e.offset;return _e("span",{css:kl({animation:"".concat(p1," 1s ease-in-out ").concat(n,"ms infinite;"),backgroundColor:"currentColor",borderRadius:"1em",display:"inline-block",marginLeft:r?"1em":void 0,height:"1em",verticalAlign:"top",width:"1em"},"","")})},b1=function(e){var n=e.innerProps,r=e.isRtl,s=e.size,o=s===void 0?4:s,i=An(e,a1);return _e("div",Ae({},vt(Me(Me({},i),{},{innerProps:n,isRtl:r,size:o}),"loadingIndicator",{indicator:!0,"loading-indicator":!0}),n),_e(Tl,{delay:0,offset:r}),_e(Tl,{delay:160,offset:!0}),_e(Tl,{delay:320,offset:!r}))},y1=function(e,n){var r=e.isDisabled,s=e.isFocused,o=e.theme,i=o.colors,a=o.borderRadius,l=o.spacing;return Me({label:"control",alignItems:"center",cursor:"default",display:"flex",flexWrap:"wrap",justifyContent:"space-between",minHeight:l.controlHeight,outline:"0 !important",position:"relative",transition:"all 100ms"},n?{}:{backgroundColor:r?i.neutral5:i.neutral0,borderColor:r?i.neutral10:s?i.primary:i.neutral20,borderRadius:a,borderStyle:"solid",borderWidth:1,boxShadow:s?"0 0 0 1px ".concat(i.primary):void 0,"&:hover":{borderColor:s?i.primary:i.neutral30}})},v1=function(e){var n=e.children,r=e.isDisabled,s=e.isFocused,o=e.innerRef,i=e.innerProps,a=e.menuIsOpen;return _e("div",Ae({ref:o},vt(e,"control",{control:!0,"control--is-disabled":r,"control--is-focused":s,"control--menu-is-open":a}),i,{"aria-disabled":r||void 0}),n)},x1=v1,w1=["data"],k1=function(e,n){var r=e.theme.spacing;return n?{}:{paddingBottom:r.baseUnit*2,paddingTop:r.baseUnit*2}},C1=function(e){var n=e.children,r=e.cx,s=e.getStyles,o=e.getClassNames,i=e.Heading,a=e.headingProps,l=e.innerProps,c=e.label,d=e.theme,u=e.selectProps;return _e("div",Ae({},vt(e,"group",{group:!0}),l),_e(i,Ae({},a,{selectProps:u,theme:d,getStyles:s,getClassNames:o,cx:r}),c),_e("div",null,n))},S1=function(e,n){var r=e.theme,s=r.colors,o=r.spacing;return Me({label:"group",cursor:"default",display:"block"},n?{}:{color:s.neutral40,fontSize:"75%",fontWeight:500,marginBottom:"0.25em",paddingLeft:o.baseUnit*3,paddingRight:o.baseUnit*3,textTransform:"uppercase"})},_1=function(e){var n=uf(e);n.data;var r=An(n,w1);return _e("div",Ae({},vt(e,"groupHeading",{"group-heading":!0}),r))},E1=C1,M1=["innerRef","isDisabled","isHidden","inputClassName"],N1=function(e,n){var r=e.isDisabled,s=e.value,o=e.theme,i=o.spacing,a=o.colors;return Me(Me({visibility:r?"hidden":"visible",transform:s?"translateZ(0)":""},O1),n?{}:{margin:i.baseUnit/2,paddingBottom:i.baseUnit/2,paddingTop:i.baseUnit/2,color:a.neutral80})},Cf={gridArea:"1 / 2",font:"inherit",minWidth:"2px",border:0,margin:0,outline:0,padding:0},O1={flex:"1 1 auto",display:"inline-grid",gridArea:"1 / 1 / 2 / 3",gridTemplateColumns:"0 min-content","&:after":Me({content:'attr(data-value) " "',visibility:"hidden",whiteSpace:"pre"},Cf)},T1=function(e){return Me({label:"input",color:"inherit",background:0,opacity:e?0:1,width:"100%"},Cf)},A1=function(e){var n=e.cx,r=e.value,s=uf(e),o=s.innerRef,i=s.isDisabled,a=s.isHidden,l=s.inputClassName,c=An(s,M1);return _e("div",Ae({},vt(e,"input",{"input-container":!0}),{"data-value":r||""}),_e("input",Ae({className:n({input:!0},l),ref:o,style:T1(a),disabled:i},c)))},D1=A1,I1=function(e,n){var r=e.theme,s=r.spacing,o=r.borderRadius,i=r.colors;return Me({label:"multiValue",display:"flex",minWidth:0},n?{}:{backgroundColor:i.neutral10,borderRadius:o/2,margin:s.baseUnit/2})},L1=function(e,n){var r=e.theme,s=r.borderRadius,o=r.colors,i=e.cropWithEllipsis;return Me({overflow:"hidden",textOverflow:i||i===void 0?"ellipsis":void 0,whiteSpace:"nowrap"},n?{}:{borderRadius:s/2,color:o.neutral80,fontSize:"85%",padding:3,paddingLeft:6})},P1=function(e,n){var r=e.theme,s=r.spacing,o=r.borderRadius,i=r.colors,a=e.isFocused;return Me({alignItems:"center",display:"flex"},n?{}:{borderRadius:o/2,backgroundColor:a?i.dangerLight:void 0,paddingLeft:s.baseUnit,paddingRight:s.baseUnit,":hover":{backgroundColor:i.dangerLight,color:i.danger}})},Sf=function(e){var n=e.children,r=e.innerProps;return _e("div",r,n)},$1=Sf,F1=Sf;function z1(t){var e=t.children,n=t.innerProps;return _e("div",Ae({role:"button"},n),e||_e(Ol,{size:14}))}var V1=function(e){var n=e.children,r=e.components,s=e.data,o=e.innerProps,i=e.isDisabled,a=e.removeProps,l=e.selectProps,c=r.Container,d=r.Label,u=r.Remove;return _e(c,{data:s,innerProps:Me(Me({},vt(e,"multiValue",{"multi-value":!0,"multi-value--is-disabled":i})),o),selectProps:l},_e(d,{data:s,innerProps:Me({},vt(e,"multiValueLabel",{"multi-value__label":!0})),selectProps:l},n),_e(u,{data:s,innerProps:Me(Me({},vt(e,"multiValueRemove",{"multi-value__remove":!0})),{},{"aria-label":"Remove ".concat(n||"option")},a),selectProps:l}))},H1=V1,U1=function(e,n){var r=e.isDisabled,s=e.isFocused,o=e.isSelected,i=e.theme,a=i.spacing,l=i.colors;return Me({label:"option",cursor:"default",display:"block",fontSize:"inherit",width:"100%",userSelect:"none",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)"},n?{}:{backgroundColor:o?l.primary:s?l.primary25:"transparent",color:r?l.neutral20:o?l.neutral0:"inherit",padding:"".concat(a.baseUnit*2,"px ").concat(a.baseUnit*3,"px"),":active":{backgroundColor:r?void 0:o?l.primary:l.primary50}})},B1=function(e){var n=e.children,r=e.isDisabled,s=e.isFocused,o=e.isSelected,i=e.innerRef,a=e.innerProps;return _e("div",Ae({},vt(e,"option",{option:!0,"option--is-disabled":r,"option--is-focused":s,"option--is-selected":o}),{ref:i,"aria-disabled":r},a),n)},j1=B1,W1=function(e,n){var r=e.theme,s=r.spacing,o=r.colors;return Me({label:"placeholder",gridArea:"1 / 1 / 2 / 3"},n?{}:{color:o.neutral50,marginLeft:s.baseUnit/2,marginRight:s.baseUnit/2})},G1=function(e){var n=e.children,r=e.innerProps;return _e("div",Ae({},vt(e,"placeholder",{placeholder:!0}),r),n)},Y1=G1,X1=function(e,n){var r=e.isDisabled,s=e.theme,o=s.spacing,i=s.colors;return Me({label:"singleValue",gridArea:"1 / 1 / 2 / 3",maxWidth:"100%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},n?{}:{color:r?i.neutral40:i.neutral80,marginLeft:o.baseUnit/2,marginRight:o.baseUnit/2})},K1=function(e){var n=e.children,r=e.isDisabled,s=e.innerProps;return _e("div",Ae({},vt(e,"singleValue",{"single-value":!0,"single-value--is-disabled":r}),s),n)},q1=K1,gi={ClearIndicator:f1,Control:x1,DropdownIndicator:d1,DownChevron:wf,CrossIcon:Ol,Group:E1,GroupHeading:_1,IndicatorsContainer:o1,IndicatorSeparator:g1,Input:D1,LoadingIndicator:b1,Menu:Gw,MenuList:Xw,MenuPortal:Rw,LoadingMessage:Qw,NoOptionsMessage:Zw,MultiValue:H1,MultiValueContainer:$1,MultiValueLabel:F1,MultiValueRemove:z1,Option:j1,Placeholder:Y1,SelectContainer:t1,SingleValue:q1,ValueContainer:r1},Z1=function(e){return Me(Me({},gi),e.components)},_f=Number.isNaN||function(e){return typeof e=="number"&&e!==e};function Q1(t,e){return!!(t===e||_f(t)&&_f(e))}function J1(t,e){if(t.length!==e.length)return!1;for(var n=0;n1?"s":""," ").concat(o.join(","),", selected.");case"select-option":return i?"option ".concat(s," is disabled. Select another option."):"option ".concat(s,", selected.");default:return""}},onFocus:function(e){var n=e.context,r=e.focused,s=e.options,o=e.label,i=o===void 0?"":o,a=e.selectValue,l=e.isDisabled,c=e.isSelected,d=e.isAppleDevice,u=function(p,y){return p&&p.length?"".concat(p.indexOf(y)+1," of ").concat(p.length):""};if(n==="value"&&a)return"value ".concat(i," focused, ").concat(u(a,r),".");if(n==="menu"&&d){var f=l?" disabled":"",g="".concat(c?" selected":"").concat(f);return"".concat(i).concat(g,", ").concat(u(s,r),".")}return""},onFilter:function(e){var n=e.inputValue,r=e.resultsMessage;return"".concat(r).concat(n?" for search term "+n:"",".")}},rk=function(e){var n=e.ariaSelection,r=e.focusedOption,s=e.focusedValue,o=e.focusableOptions,i=e.isFocused,a=e.selectValue,l=e.selectProps,c=e.id,d=e.isAppleDevice,u=l.ariaLiveMessages,f=l.getOptionLabel,g=l.inputValue,m=l.isMulti,p=l.isOptionDisabled,y=l.isSearchable,b=l.menuIsOpen,_=l.options,x=l.screenReaderStatus,E=l.tabSelectsValue,C=l.isLoading,k=l["aria-label"],N=l["aria-live"],S=v.useMemo(function(){return Me(Me({},nk),u||{})},[u]),w=v.useMemo(function(){var A="";if(n&&S.onChange){var D=n.option,O=n.options,z=n.removedValue,H=n.removedValues,j=n.value,W=function(Z){return Array.isArray(Z)?null:Z},U=z||D||W(j),B=U?f(U):"",G=O||H||void 0,V=G?G.map(f):[],K=Me({isDisabled:U&&p(U,a),label:B,labels:V},n);A=S.onChange(K)}return A},[n,S,p,a,f]),M=v.useMemo(function(){var A="",D=r||s,O=!!(r&&a&&a.includes(r));if(D&&S.onFocus){var z={focused:D,label:f(D),isDisabled:p(D,a),isSelected:O,options:o,context:D===r?"menu":"value",selectValue:a,isAppleDevice:d};A=S.onFocus(z)}return A},[r,s,f,p,S,o,a,d]),T=v.useMemo(function(){var A="";if(b&&_.length&&!C&&S.onFilter){var D=x({count:o.length});A=S.onFilter({inputValue:g,resultsMessage:D})}return A},[o,g,b,S,_,x,C]),P=n?.action==="initial-input-focus",L=v.useMemo(function(){var A="";if(S.guidance){var D=s?"value":b?"menu":"input";A=S.guidance({"aria-label":k,context:D,isDisabled:r&&p(r,a),isMulti:m,isSearchable:y,tabSelectsValue:E,isInitialFocus:P})}return A},[k,r,s,m,p,y,b,S,a,E,P]),$=_e(v.Fragment,null,_e("span",{id:"aria-selection"},w),_e("span",{id:"aria-focused"},M),_e("span",{id:"aria-results"},T),_e("span",{id:"aria-guidance"},L));return _e(v.Fragment,null,_e(Ef,{id:c},P&&$),_e(Ef,{"aria-live":N,"aria-atomic":"false","aria-relevant":"additions text",role:"log"},i&&!P&&$))},sk=rk,Al=[{base:"A",letters:"AⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ"},{base:"AA",letters:"Ꜳ"},{base:"AE",letters:"ÆǼǢ"},{base:"AO",letters:"Ꜵ"},{base:"AU",letters:"Ꜷ"},{base:"AV",letters:"ꜸꜺ"},{base:"AY",letters:"Ꜽ"},{base:"B",letters:"BⒷBḂḄḆɃƂƁ"},{base:"C",letters:"CⒸCĆĈĊČÇḈƇȻꜾ"},{base:"D",letters:"DⒹDḊĎḌḐḒḎĐƋƊƉꝹ"},{base:"DZ",letters:"DZDŽ"},{base:"Dz",letters:"DzDž"},{base:"E",letters:"EⒺEÈÉÊỀẾỄỂẼĒḔḖĔĖËẺĚȄȆẸỆȨḜĘḘḚƐƎ"},{base:"F",letters:"FⒻFḞƑꝻ"},{base:"G",letters:"GⒼGǴĜḠĞĠǦĢǤƓꞠꝽꝾ"},{base:"H",letters:"HⒽHĤḢḦȞḤḨḪĦⱧⱵꞍ"},{base:"I",letters:"IⒾIÌÍÎĨĪĬİÏḮỈǏȈȊỊĮḬƗ"},{base:"J",letters:"JⒿJĴɈ"},{base:"K",letters:"KⓀKḰǨḲĶḴƘⱩꝀꝂꝄꞢ"},{base:"L",letters:"LⓁLĿĹĽḶḸĻḼḺŁȽⱢⱠꝈꝆꞀ"},{base:"LJ",letters:"LJ"},{base:"Lj",letters:"Lj"},{base:"M",letters:"MⓂMḾṀṂⱮƜ"},{base:"N",letters:"NⓃNǸŃÑṄŇṆŅṊṈȠƝꞐꞤ"},{base:"NJ",letters:"NJ"},{base:"Nj",letters:"Nj"},{base:"O",letters:"OⓄOÒÓÔỒỐỖỔÕṌȬṎŌṐṒŎȮȰÖȪỎŐǑȌȎƠỜỚỠỞỢỌỘǪǬØǾƆƟꝊꝌ"},{base:"OI",letters:"Ƣ"},{base:"OO",letters:"Ꝏ"},{base:"OU",letters:"Ȣ"},{base:"P",letters:"PⓅPṔṖƤⱣꝐꝒꝔ"},{base:"Q",letters:"QⓆQꝖꝘɊ"},{base:"R",letters:"RⓇRŔṘŘȐȒṚṜŖṞɌⱤꝚꞦꞂ"},{base:"S",letters:"SⓈSẞŚṤŜṠŠṦṢṨȘŞⱾꞨꞄ"},{base:"T",letters:"TⓉTṪŤṬȚŢṰṮŦƬƮȾꞆ"},{base:"TZ",letters:"Ꜩ"},{base:"U",letters:"UⓊUÙÚÛŨṸŪṺŬÜǛǗǕǙỦŮŰǓȔȖƯỪỨỮỬỰỤṲŲṶṴɄ"},{base:"V",letters:"VⓋVṼṾƲꝞɅ"},{base:"VY",letters:"Ꝡ"},{base:"W",letters:"WⓌWẀẂŴẆẄẈⱲ"},{base:"X",letters:"XⓍXẊẌ"},{base:"Y",letters:"YⓎYỲÝŶỸȲẎŸỶỴƳɎỾ"},{base:"Z",letters:"ZⓏZŹẐŻŽẒẔƵȤⱿⱫꝢ"},{base:"a",letters:"aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐ"},{base:"aa",letters:"ꜳ"},{base:"ae",letters:"æǽǣ"},{base:"ao",letters:"ꜵ"},{base:"au",letters:"ꜷ"},{base:"av",letters:"ꜹꜻ"},{base:"ay",letters:"ꜽ"},{base:"b",letters:"bⓑbḃḅḇƀƃɓ"},{base:"c",letters:"cⓒcćĉċčçḉƈȼꜿↄ"},{base:"d",letters:"dⓓdḋďḍḑḓḏđƌɖɗꝺ"},{base:"dz",letters:"dzdž"},{base:"e",letters:"eⓔeèéêềếễểẽēḕḗĕėëẻěȅȇẹệȩḝęḙḛɇɛǝ"},{base:"f",letters:"fⓕfḟƒꝼ"},{base:"g",letters:"gⓖgǵĝḡğġǧģǥɠꞡᵹꝿ"},{base:"h",letters:"hⓗhĥḣḧȟḥḩḫẖħⱨⱶɥ"},{base:"hv",letters:"ƕ"},{base:"i",letters:"iⓘiìíîĩīĭïḯỉǐȉȋịįḭɨı"},{base:"j",letters:"jⓙjĵǰɉ"},{base:"k",letters:"kⓚkḱǩḳķḵƙⱪꝁꝃꝅꞣ"},{base:"l",letters:"lⓛlŀĺľḷḹļḽḻſłƚɫⱡꝉꞁꝇ"},{base:"lj",letters:"lj"},{base:"m",letters:"mⓜmḿṁṃɱɯ"},{base:"n",letters:"nⓝnǹńñṅňṇņṋṉƞɲʼnꞑꞥ"},{base:"nj",letters:"nj"},{base:"o",letters:"oⓞoòóôồốỗổõṍȭṏōṑṓŏȯȱöȫỏőǒȍȏơờớỡởợọộǫǭøǿɔꝋꝍɵ"},{base:"oi",letters:"ƣ"},{base:"ou",letters:"ȣ"},{base:"oo",letters:"ꝏ"},{base:"p",letters:"pⓟpṕṗƥᵽꝑꝓꝕ"},{base:"q",letters:"qⓠqɋꝗꝙ"},{base:"r",letters:"rⓡrŕṙřȑȓṛṝŗṟɍɽꝛꞧꞃ"},{base:"s",letters:"sⓢsßśṥŝṡšṧṣṩșşȿꞩꞅẛ"},{base:"t",letters:"tⓣtṫẗťṭțţṱṯŧƭʈⱦꞇ"},{base:"tz",letters:"ꜩ"},{base:"u",letters:"uⓤuùúûũṹūṻŭüǜǘǖǚủůűǔȕȗưừứữửựụṳųṷṵʉ"},{base:"v",letters:"vⓥvṽṿʋꝟʌ"},{base:"vy",letters:"ꝡ"},{base:"w",letters:"wⓦwẁẃŵẇẅẘẉⱳ"},{base:"x",letters:"xⓧxẋẍ"},{base:"y",letters:"yⓨyỳýŷỹȳẏÿỷẙỵƴɏỿ"},{base:"z",letters:"zⓩzźẑżžẓẕƶȥɀⱬꝣ"}],ok=new RegExp("["+Al.map(function(t){return t.letters}).join("")+"]","g"),Mf={},Dl=0;Dl-1}},ck=["innerRef"];function dk(t){var e=t.innerRef,n=An(t,ck),r=Fw(n,"onExited","in","enter","exit","appear");return _e("input",Ae({ref:e},r,{css:kl({label:"dummyInput",background:0,border:0,caretColor:"transparent",fontSize:"inherit",gridArea:"1 / 1 / 2 / 3",outline:0,padding:0,width:1,color:"transparent",left:-100,opacity:0,position:"relative",transform:"scale(.01)"},"","")}))}var uk=function(e){e.cancelable&&e.preventDefault(),e.stopPropagation()};function fk(t){var e=t.isEnabled,n=t.onBottomArrive,r=t.onBottomLeave,s=t.onTopArrive,o=t.onTopLeave,i=v.useRef(!1),a=v.useRef(!1),l=v.useRef(0),c=v.useRef(null),d=v.useCallback(function(y,b){if(c.current!==null){var _=c.current,x=_.scrollTop,E=_.scrollHeight,C=_.clientHeight,k=c.current,N=b>0,S=E-C-x,w=!1;S>b&&i.current&&(r&&r(y),i.current=!1),N&&a.current&&(o&&o(y),a.current=!1),N&&b>S?(n&&!i.current&&n(y),k.scrollTop=E,w=!0,i.current=!0):!N&&-b>x&&(s&&!a.current&&s(y),k.scrollTop=0,w=!0,a.current=!0),w&&uk(y)}},[n,r,s,o]),u=v.useCallback(function(y){d(y,y.deltaY)},[d]),f=v.useCallback(function(y){l.current=y.changedTouches[0].clientY},[]),g=v.useCallback(function(y){var b=l.current-y.changedTouches[0].clientY;d(y,b)},[d]),m=v.useCallback(function(y){if(y){var b=Lw?{passive:!1}:!1;y.addEventListener("wheel",u,b),y.addEventListener("touchstart",f,b),y.addEventListener("touchmove",g,b)}},[g,f,u]),p=v.useCallback(function(y){y&&(y.removeEventListener("wheel",u,!1),y.removeEventListener("touchstart",f,!1),y.removeEventListener("touchmove",g,!1))},[g,f,u]);return v.useEffect(function(){if(e){var y=c.current;return m(y),function(){p(y)}}},[e,m,p]),function(y){c.current=y}}var Tf=["boxSizing","height","overflow","paddingRight","position"],Af={boxSizing:"border-box",overflow:"hidden",position:"relative",height:"100%"};function Df(t){t.cancelable&&t.preventDefault()}function If(t){t.stopPropagation()}function Lf(){var t=this.scrollTop,e=this.scrollHeight,n=t+this.offsetHeight;t===0?this.scrollTop=1:n===e&&(this.scrollTop=t-1)}function Pf(){return"ontouchstart"in window||navigator.maxTouchPoints}var $f=!!(typeof window<"u"&&window.document&&window.document.createElement),zs=0,Wr={capture:!1,passive:!1};function hk(t){var e=t.isEnabled,n=t.accountForScrollbars,r=n===void 0?!0:n,s=v.useRef({}),o=v.useRef(null),i=v.useCallback(function(l){if($f){var c=document.body,d=c&&c.style;if(r&&Tf.forEach(function(m){var p=d&&d[m];s.current[m]=p}),r&&zs<1){var u=parseInt(s.current.paddingRight,10)||0,f=document.body?document.body.clientWidth:0,g=window.innerWidth-f+u||0;Object.keys(Af).forEach(function(m){var p=Af[m];d&&(d[m]=p)}),d&&(d.paddingRight="".concat(g,"px"))}c&&Pf()&&(c.addEventListener("touchmove",Df,Wr),l&&(l.addEventListener("touchstart",Lf,Wr),l.addEventListener("touchmove",If,Wr))),zs+=1}},[r]),a=v.useCallback(function(l){if($f){var c=document.body,d=c&&c.style;zs=Math.max(zs-1,0),r&&zs<1&&Tf.forEach(function(u){var f=s.current[u];d&&(d[u]=f)}),c&&Pf()&&(c.removeEventListener("touchmove",Df,Wr),l&&(l.removeEventListener("touchstart",Lf,Wr),l.removeEventListener("touchmove",If,Wr)))}},[r]);return v.useEffect(function(){if(e){var l=o.current;return i(l),function(){a(l)}}},[e,i,a]),function(l){o.current=l}}var gk=function(e){var n=e.target;return n.ownerDocument.activeElement&&n.ownerDocument.activeElement.blur()},pk={name:"1kfdb0e",styles:"position:fixed;left:0;bottom:0;right:0;top:0"};function mk(t){var e=t.children,n=t.lockEnabled,r=t.captureEnabled,s=r===void 0?!0:r,o=t.onBottomArrive,i=t.onBottomLeave,a=t.onTopArrive,l=t.onTopLeave,c=fk({isEnabled:s,onBottomArrive:o,onBottomLeave:i,onTopArrive:a,onTopLeave:l}),d=hk({isEnabled:n}),u=function(g){c(g),d(g)};return _e(v.Fragment,null,n&&_e("div",{onClick:gk,css:pk}),e(u))}var bk={name:"1a0ro4n-requiredInput",styles:"label:requiredInput;opacity:0;pointer-events:none;position:absolute;bottom:0;left:0;right:0;width:100%"},yk=function(e){var n=e.name,r=e.onFocus;return _e("input",{required:!0,name:n,tabIndex:-1,"aria-hidden":"true",onFocus:r,css:bk,value:"",onChange:function(){}})},vk=yk;function Pl(t){var e;return typeof window<"u"&&window.navigator!=null?t.test(((e=window.navigator.userAgentData)===null||e===void 0?void 0:e.platform)||window.navigator.platform):!1}function xk(){return Pl(/^iPhone/i)}function Ff(){return Pl(/^Mac/i)}function wk(){return Pl(/^iPad/i)||Ff()&&navigator.maxTouchPoints>1}function kk(){return xk()||wk()}function Ck(){return Ff()||kk()}var Sk=function(e){return e.label},zf=function(e){return e.label},Vf=function(e){return e.value},_k=function(e){return!!e.isDisabled},Ek={clearIndicator:u1,container:e1,control:y1,dropdownIndicator:c1,group:k1,groupHeading:S1,indicatorsContainer:s1,indicatorSeparator:h1,input:N1,loadingIndicator:m1,loadingMessage:qw,menu:Bw,menuList:Yw,menuPortal:Jw,multiValue:I1,multiValueLabel:L1,multiValueRemove:P1,noOptionsMessage:Kw,option:U1,placeholder:W1,singleValue:X1,valueContainer:n1},Mk={primary:"#2684FF",primary75:"#4C9AFF",primary50:"#B2D4FF",primary25:"#DEEBFF",danger:"#DE350B",dangerLight:"#FFBDAD",neutral0:"hsl(0, 0%, 100%)",neutral5:"hsl(0, 0%, 95%)",neutral10:"hsl(0, 0%, 90%)",neutral20:"hsl(0, 0%, 80%)",neutral30:"hsl(0, 0%, 70%)",neutral40:"hsl(0, 0%, 60%)",neutral50:"hsl(0, 0%, 50%)",neutral60:"hsl(0, 0%, 40%)",neutral70:"hsl(0, 0%, 30%)",neutral80:"hsl(0, 0%, 20%)",neutral90:"hsl(0, 0%, 10%)"},Nk=4,Hf=4,Ok=38,Tk=Hf*2,Ak={baseUnit:Hf,controlHeight:Ok,menuGutter:Tk},$l={borderRadius:Nk,colors:Mk,spacing:Ak},Dk={"aria-live":"polite",backspaceRemovesValue:!0,blurInputOnSelect:gf(),captureMenuScroll:!gf(),classNames:{},closeMenuOnSelect:!0,closeMenuOnScroll:!1,components:{},controlShouldRenderValue:!0,escapeClearsValue:!1,filterOption:lk(),formatGroupLabel:Sk,getOptionLabel:zf,getOptionValue:Vf,isDisabled:!1,isLoading:!1,isMulti:!1,isRtl:!1,isSearchable:!0,isOptionDisabled:_k,loadingMessage:function(){return"Loading..."},maxMenuHeight:300,minMenuHeight:140,menuIsOpen:!1,menuPlacement:"bottom",menuPosition:"absolute",menuShouldBlockScroll:!1,menuShouldScrollIntoView:!Dw(),noOptionsMessage:function(){return"No options"},openMenuOnFocus:!1,openMenuOnClick:!0,options:[],pageSize:5,placeholder:"Select...",screenReaderStatus:function(e){var n=e.count;return"".concat(n," result").concat(n!==1?"s":""," available")},styles:{},tabIndex:0,tabSelectsValue:!0,unstyled:!1};function Uf(t,e,n,r){var s=Yf(t,e,n),o=Xf(t,e,n),i=Gf(t,e),a=mi(t,e);return{type:"option",data:e,isDisabled:s,isSelected:o,label:i,value:a,index:r}}function pi(t,e){return t.options.map(function(n,r){if("options"in n){var s=n.options.map(function(i,a){return Uf(t,i,e,a)}).filter(function(i){return Wf(t,i)});return s.length>0?{type:"group",data:n,options:s,index:r}:void 0}var o=Uf(t,n,e,r);return Wf(t,o)?o:void 0}).filter(Pw)}function Bf(t){return t.reduce(function(e,n){return n.type==="group"?e.push.apply(e,Hr(n.options.map(function(r){return r.data}))):e.push(n.data),e},[])}function jf(t,e){return t.reduce(function(n,r){return r.type==="group"?n.push.apply(n,Hr(r.options.map(function(s){return{data:s.data,id:"".concat(e,"-").concat(r.index,"-").concat(s.index)}}))):n.push({data:r.data,id:"".concat(e,"-").concat(r.index)}),n},[])}function Ik(t,e){return Bf(pi(t,e))}function Wf(t,e){var n=t.inputValue,r=n===void 0?"":n,s=e.data,o=e.isSelected,i=e.label,a=e.value;return(!qf(t)||!o)&&Kf(t,{label:i,value:a,data:s},r)}function Lk(t,e){var n=t.focusedValue,r=t.selectValue,s=r.indexOf(n);if(s>-1){var o=e.indexOf(n);if(o>-1)return n;if(s-1?n:e[0]}var Fl=function(e,n){var r,s=(r=e.find(function(o){return o.data===n}))===null||r===void 0?void 0:r.id;return s||null},Gf=function(e,n){return e.getOptionLabel(n)},mi=function(e,n){return e.getOptionValue(n)};function Yf(t,e,n){return typeof t.isOptionDisabled=="function"?t.isOptionDisabled(e,n):!1}function Xf(t,e,n){if(n.indexOf(e)>-1)return!0;if(typeof t.isOptionSelected=="function")return t.isOptionSelected(e,n);var r=mi(t,e);return n.some(function(s){return mi(t,s)===r})}function Kf(t,e,n){return t.filterOption?t.filterOption(e,n):!0}var qf=function(e){var n=e.hideSelectedOptions,r=e.isMulti;return n===void 0?r:n},$k=1,zl=(function(t){ax(n,t);var e=dx(n);function n(r){var s;if(ox(this,n),s=e.call(this,r),s.state={ariaSelection:null,focusedOption:null,focusedOptionId:null,focusableOptionsWithIds:[],focusedValue:null,inputIsHidden:!1,isFocused:!1,selectValue:[],clearFocusValueOnUpdate:!1,prevWasFocused:!1,inputIsHiddenAfterUpdate:void 0,prevProps:void 0,instancePrefix:"",isAppleDevice:!1},s.blockOptionHover=!1,s.isComposing=!1,s.commonProps=void 0,s.initialTouchX=0,s.initialTouchY=0,s.openAfterFocus=!1,s.scrollToFocusedOptionOnUpdate=!1,s.userIsDragging=void 0,s.controlRef=null,s.getControlRef=function(l){s.controlRef=l},s.focusedOptionRef=null,s.getFocusedOptionRef=function(l){s.focusedOptionRef=l},s.menuListRef=null,s.getMenuListRef=function(l){s.menuListRef=l},s.inputRef=null,s.getInputRef=function(l){s.inputRef=l},s.focus=s.focusInput,s.blur=s.blurInput,s.onChange=function(l,c){var d=s.props,u=d.onChange,f=d.name;c.name=f,s.ariaOnChange(l,c),u(l,c)},s.setValue=function(l,c,d){var u=s.props,f=u.closeMenuOnSelect,g=u.isMulti,m=u.inputValue;s.onInputChange("",{action:"set-value",prevInputValue:m}),f&&(s.setState({inputIsHiddenAfterUpdate:!g}),s.onMenuClose()),s.setState({clearFocusValueOnUpdate:!0}),s.onChange(l,{action:c,option:d})},s.selectOption=function(l){var c=s.props,d=c.blurInputOnSelect,u=c.isMulti,f=c.name,g=s.state.selectValue,m=u&&s.isOptionSelected(l,g),p=s.isOptionDisabled(l,g);if(m){var y=s.getOptionValue(l);s.setValue(g.filter(function(b){return s.getOptionValue(b)!==y}),"deselect-option",l)}else if(!p)u?s.setValue([].concat(Hr(g),[l]),"select-option",l):s.setValue(l,"select-option");else{s.ariaOnChange(l,{action:"select-option",option:l,name:f});return}d&&s.blurInput()},s.removeValue=function(l){var c=s.props.isMulti,d=s.state.selectValue,u=s.getOptionValue(l),f=d.filter(function(m){return s.getOptionValue(m)!==u}),g=Fs(c,f,f[0]||null);s.onChange(g,{action:"remove-value",removedValue:l}),s.focusInput()},s.clearValue=function(){var l=s.state.selectValue;s.onChange(Fs(s.props.isMulti,[],null),{action:"clear",removedValues:l})},s.popValue=function(){var l=s.props.isMulti,c=s.state.selectValue,d=c[c.length-1],u=c.slice(0,c.length-1),f=Fs(l,u,u[0]||null);d&&s.onChange(f,{action:"pop-value",removedValue:d})},s.getFocusedOptionId=function(l){return Fl(s.state.focusableOptionsWithIds,l)},s.getFocusableOptionsWithIds=function(){return jf(pi(s.props,s.state.selectValue),s.getElementId("option"))},s.getValue=function(){return s.state.selectValue},s.cx=function(){for(var l=arguments.length,c=new Array(l),d=0;dg||f>g}},s.onTouchEnd=function(l){s.userIsDragging||(s.controlRef&&!s.controlRef.contains(l.target)&&s.menuListRef&&!s.menuListRef.contains(l.target)&&s.blurInput(),s.initialTouchX=0,s.initialTouchY=0)},s.onControlTouchEnd=function(l){s.userIsDragging||s.onControlMouseDown(l)},s.onClearIndicatorTouchEnd=function(l){s.userIsDragging||s.onClearIndicatorMouseDown(l)},s.onDropdownIndicatorTouchEnd=function(l){s.userIsDragging||s.onDropdownIndicatorMouseDown(l)},s.handleInputChange=function(l){var c=s.props.inputValue,d=l.currentTarget.value;s.setState({inputIsHiddenAfterUpdate:!1}),s.onInputChange(d,{action:"input-change",prevInputValue:c}),s.props.menuIsOpen||s.onMenuOpen()},s.onInputFocus=function(l){s.props.onFocus&&s.props.onFocus(l),s.setState({inputIsHiddenAfterUpdate:!1,isFocused:!0}),(s.openAfterFocus||s.props.openMenuOnFocus)&&s.openMenu("first"),s.openAfterFocus=!1},s.onInputBlur=function(l){var c=s.props.inputValue;if(s.menuListRef&&s.menuListRef.contains(document.activeElement)){s.inputRef.focus();return}s.props.onBlur&&s.props.onBlur(l),s.onInputChange("",{action:"input-blur",prevInputValue:c}),s.onMenuClose(),s.setState({focusedValue:null,isFocused:!1})},s.onOptionHover=function(l){if(!(s.blockOptionHover||s.state.focusedOption===l)){var c=s.getFocusableOptions(),d=c.indexOf(l);s.setState({focusedOption:l,focusedOptionId:d>-1?s.getFocusedOptionId(l):null})}},s.shouldHideSelectedOptions=function(){return qf(s.props)},s.onValueInputFocus=function(l){l.preventDefault(),l.stopPropagation(),s.focus()},s.onKeyDown=function(l){var c=s.props,d=c.isMulti,u=c.backspaceRemovesValue,f=c.escapeClearsValue,g=c.inputValue,m=c.isClearable,p=c.isDisabled,y=c.menuIsOpen,b=c.onKeyDown,_=c.tabSelectsValue,x=c.openMenuOnFocus,E=s.state,C=E.focusedOption,k=E.focusedValue,N=E.selectValue;if(!p&&!(typeof b=="function"&&(b(l),l.defaultPrevented))){switch(s.blockOptionHover=!0,l.key){case"ArrowLeft":if(!d||g)return;s.focusValue("previous");break;case"ArrowRight":if(!d||g)return;s.focusValue("next");break;case"Delete":case"Backspace":if(g)return;if(k)s.removeValue(k);else{if(!u)return;d?s.popValue():m&&s.clearValue()}break;case"Tab":if(s.isComposing||l.shiftKey||!y||!_||!C||x&&s.isOptionSelected(C,N))return;s.selectOption(C);break;case"Enter":if(l.keyCode===229)break;if(y){if(!C||s.isComposing)return;s.selectOption(C);break}return;case"Escape":y?(s.setState({inputIsHiddenAfterUpdate:!1}),s.onInputChange("",{action:"menu-close",prevInputValue:g}),s.onMenuClose()):m&&f&&s.clearValue();break;case" ":if(g)return;if(!y){s.openMenu("first");break}if(!C)return;s.selectOption(C);break;case"ArrowUp":y?s.focusOption("up"):s.openMenu("last");break;case"ArrowDown":y?s.focusOption("down"):s.openMenu("first");break;case"PageUp":if(!y)return;s.focusOption("pageup");break;case"PageDown":if(!y)return;s.focusOption("pagedown");break;case"Home":if(!y)return;s.focusOption("first");break;case"End":if(!y)return;s.focusOption("last");break;default:return}l.preventDefault()}},s.state.instancePrefix="react-select-"+(s.props.instanceId||++$k),s.state.selectValue=ci(r.value),r.menuIsOpen&&s.state.selectValue.length){var o=s.getFocusableOptionsWithIds(),i=s.buildFocusableOptions(),a=i.indexOf(s.state.selectValue[0]);s.state.focusableOptionsWithIds=o,s.state.focusedOption=i[a],s.state.focusedOptionId=Fl(o,i[a])}return s}return ix(n,[{key:"componentDidMount",value:function(){this.startListeningComposition(),this.startListeningToTouch(),this.props.closeMenuOnScroll&&document&&document.addEventListener&&document.addEventListener("scroll",this.onScroll,!0),this.props.autoFocus&&this.focusInput(),this.props.menuIsOpen&&this.state.focusedOption&&this.menuListRef&&this.focusedOptionRef&&hf(this.menuListRef,this.focusedOptionRef),Ck()&&this.setState({isAppleDevice:!0})}},{key:"componentDidUpdate",value:function(s){var o=this.props,i=o.isDisabled,a=o.menuIsOpen,l=this.state.isFocused;(l&&!i&&s.isDisabled||l&&a&&!s.menuIsOpen)&&this.focusInput(),l&&i&&!s.isDisabled?this.setState({isFocused:!1},this.onMenuClose):!l&&!i&&s.isDisabled&&this.inputRef===document.activeElement&&this.setState({isFocused:!0}),this.menuListRef&&this.focusedOptionRef&&this.scrollToFocusedOptionOnUpdate&&(hf(this.menuListRef,this.focusedOptionRef),this.scrollToFocusedOptionOnUpdate=!1)}},{key:"componentWillUnmount",value:function(){this.stopListeningComposition(),this.stopListeningToTouch(),document.removeEventListener("scroll",this.onScroll,!0)}},{key:"onMenuOpen",value:function(){this.props.onMenuOpen()}},{key:"onMenuClose",value:function(){this.onInputChange("",{action:"menu-close",prevInputValue:this.props.inputValue}),this.props.onMenuClose()}},{key:"onInputChange",value:function(s,o){this.props.onInputChange(s,o)}},{key:"focusInput",value:function(){this.inputRef&&this.inputRef.focus()}},{key:"blurInput",value:function(){this.inputRef&&this.inputRef.blur()}},{key:"openMenu",value:function(s){var o=this,i=this.state,a=i.selectValue,l=i.isFocused,c=this.buildFocusableOptions(),d=s==="first"?0:c.length-1;if(!this.props.isMulti){var u=c.indexOf(a[0]);u>-1&&(d=u)}this.scrollToFocusedOptionOnUpdate=!(l&&this.menuListRef),this.setState({inputIsHiddenAfterUpdate:!1,focusedValue:null,focusedOption:c[d],focusedOptionId:this.getFocusedOptionId(c[d])},function(){return o.onMenuOpen()})}},{key:"focusValue",value:function(s){var o=this.state,i=o.selectValue,a=o.focusedValue;if(this.props.isMulti){this.setState({focusedOption:null});var l=i.indexOf(a);a||(l=-1);var c=i.length-1,d=-1;if(i.length){switch(s){case"previous":l===0?d=0:l===-1?d=c:d=l-1;break;case"next":l>-1&&l0&&arguments[0]!==void 0?arguments[0]:"first",o=this.props.pageSize,i=this.state.focusedOption,a=this.getFocusableOptions();if(a.length){var l=0,c=a.indexOf(i);i||(c=-1),s==="up"?l=c>0?c-1:a.length-1:s==="down"?l=(c+1)%a.length:s==="pageup"?(l=c-o,l<0&&(l=0)):s==="pagedown"?(l=c+o,l>a.length-1&&(l=a.length-1)):s==="last"&&(l=a.length-1),this.scrollToFocusedOptionOnUpdate=!0,this.setState({focusedOption:a[l],focusedValue:null,focusedOptionId:this.getFocusedOptionId(a[l])})}}},{key:"getTheme",value:(function(){return this.props.theme?typeof this.props.theme=="function"?this.props.theme($l):Me(Me({},$l),this.props.theme):$l})},{key:"getCommonProps",value:function(){var s=this.clearValue,o=this.cx,i=this.getStyles,a=this.getClassNames,l=this.getValue,c=this.selectOption,d=this.setValue,u=this.props,f=u.isMulti,g=u.isRtl,m=u.options,p=this.hasValue();return{clearValue:s,cx:o,getStyles:i,getClassNames:a,getValue:l,hasValue:p,isMulti:f,isRtl:g,options:m,selectOption:c,selectProps:u,setValue:d,theme:this.getTheme()}}},{key:"hasValue",value:function(){var s=this.state.selectValue;return s.length>0}},{key:"hasOptions",value:function(){return!!this.getFocusableOptions().length}},{key:"isClearable",value:function(){var s=this.props,o=s.isClearable,i=s.isMulti;return o===void 0?i:o}},{key:"isOptionDisabled",value:function(s,o){return Yf(this.props,s,o)}},{key:"isOptionSelected",value:function(s,o){return Xf(this.props,s,o)}},{key:"filterOption",value:function(s,o){return Kf(this.props,s,o)}},{key:"formatOptionLabel",value:function(s,o){if(typeof this.props.formatOptionLabel=="function"){var i=this.props.inputValue,a=this.state.selectValue;return this.props.formatOptionLabel(s,{context:o,inputValue:i,selectValue:a})}else return this.getOptionLabel(s)}},{key:"formatGroupLabel",value:function(s){return this.props.formatGroupLabel(s)}},{key:"startListeningComposition",value:(function(){document&&document.addEventListener&&(document.addEventListener("compositionstart",this.onCompositionStart,!1),document.addEventListener("compositionend",this.onCompositionEnd,!1))})},{key:"stopListeningComposition",value:function(){document&&document.removeEventListener&&(document.removeEventListener("compositionstart",this.onCompositionStart),document.removeEventListener("compositionend",this.onCompositionEnd))}},{key:"startListeningToTouch",value:(function(){document&&document.addEventListener&&(document.addEventListener("touchstart",this.onTouchStart,!1),document.addEventListener("touchmove",this.onTouchMove,!1),document.addEventListener("touchend",this.onTouchEnd,!1))})},{key:"stopListeningToTouch",value:function(){document&&document.removeEventListener&&(document.removeEventListener("touchstart",this.onTouchStart),document.removeEventListener("touchmove",this.onTouchMove),document.removeEventListener("touchend",this.onTouchEnd))}},{key:"renderInput",value:(function(){var s=this.props,o=s.isDisabled,i=s.isSearchable,a=s.inputId,l=s.inputValue,c=s.tabIndex,d=s.form,u=s.menuIsOpen,f=s.required,g=this.getComponents(),m=g.Input,p=this.state,y=p.inputIsHidden,b=p.ariaSelection,_=this.commonProps,x=a||this.getElementId("input"),E=Me(Me(Me({"aria-autocomplete":"list","aria-expanded":u,"aria-haspopup":!0,"aria-errormessage":this.props["aria-errormessage"],"aria-invalid":this.props["aria-invalid"],"aria-label":this.props["aria-label"],"aria-labelledby":this.props["aria-labelledby"],"aria-required":f,role:"combobox","aria-activedescendant":this.state.isAppleDevice?void 0:this.state.focusedOptionId||""},u&&{"aria-controls":this.getElementId("listbox")}),!i&&{"aria-readonly":!0}),this.hasValue()?b?.action==="initial-input-focus"&&{"aria-describedby":this.getElementId("live-region")}:{"aria-describedby":this.getElementId("placeholder")});return i?F.createElement(m,Ae({},_,{autoCapitalize:"none",autoComplete:"off",autoCorrect:"off",id:x,innerRef:this.getInputRef,isDisabled:o,isHidden:y,onBlur:this.onInputBlur,onChange:this.handleInputChange,onFocus:this.onInputFocus,spellCheck:"false",tabIndex:c,form:d,type:"text",value:l},E)):F.createElement(dk,Ae({id:x,innerRef:this.getInputRef,onBlur:this.onInputBlur,onChange:li,onFocus:this.onInputFocus,disabled:o,tabIndex:c,inputMode:"none",form:d,value:""},E))})},{key:"renderPlaceholderOrValue",value:function(){var s=this,o=this.getComponents(),i=o.MultiValue,a=o.MultiValueContainer,l=o.MultiValueLabel,c=o.MultiValueRemove,d=o.SingleValue,u=o.Placeholder,f=this.commonProps,g=this.props,m=g.controlShouldRenderValue,p=g.isDisabled,y=g.isMulti,b=g.inputValue,_=g.placeholder,x=this.state,E=x.selectValue,C=x.focusedValue,k=x.isFocused;if(!this.hasValue()||!m)return b?null:F.createElement(u,Ae({},f,{key:"placeholder",isDisabled:p,isFocused:k,innerProps:{id:this.getElementId("placeholder")}}),_);if(y)return E.map(function(S,w){var M=S===C,T="".concat(s.getOptionLabel(S),"-").concat(s.getOptionValue(S));return F.createElement(i,Ae({},f,{components:{Container:a,Label:l,Remove:c},isFocused:M,isDisabled:p,key:T,index:w,removeProps:{onClick:function(){return s.removeValue(S)},onTouchEnd:function(){return s.removeValue(S)},onMouseDown:function(L){L.preventDefault()}},data:S}),s.formatOptionLabel(S,"value"))});if(b)return null;var N=E[0];return F.createElement(d,Ae({},f,{data:N,isDisabled:p}),this.formatOptionLabel(N,"value"))}},{key:"renderClearIndicator",value:function(){var s=this.getComponents(),o=s.ClearIndicator,i=this.commonProps,a=this.props,l=a.isDisabled,c=a.isLoading,d=this.state.isFocused;if(!this.isClearable()||!o||l||!this.hasValue()||c)return null;var u={onMouseDown:this.onClearIndicatorMouseDown,onTouchEnd:this.onClearIndicatorTouchEnd,"aria-hidden":"true"};return F.createElement(o,Ae({},i,{innerProps:u,isFocused:d}))}},{key:"renderLoadingIndicator",value:function(){var s=this.getComponents(),o=s.LoadingIndicator,i=this.commonProps,a=this.props,l=a.isDisabled,c=a.isLoading,d=this.state.isFocused;if(!o||!c)return null;var u={"aria-hidden":"true"};return F.createElement(o,Ae({},i,{innerProps:u,isDisabled:l,isFocused:d}))}},{key:"renderIndicatorSeparator",value:function(){var s=this.getComponents(),o=s.DropdownIndicator,i=s.IndicatorSeparator;if(!o||!i)return null;var a=this.commonProps,l=this.props.isDisabled,c=this.state.isFocused;return F.createElement(i,Ae({},a,{isDisabled:l,isFocused:c}))}},{key:"renderDropdownIndicator",value:function(){var s=this.getComponents(),o=s.DropdownIndicator;if(!o)return null;var i=this.commonProps,a=this.props.isDisabled,l=this.state.isFocused,c={onMouseDown:this.onDropdownIndicatorMouseDown,onTouchEnd:this.onDropdownIndicatorTouchEnd,"aria-hidden":"true"};return F.createElement(o,Ae({},i,{innerProps:c,isDisabled:a,isFocused:l}))}},{key:"renderMenu",value:function(){var s=this,o=this.getComponents(),i=o.Group,a=o.GroupHeading,l=o.Menu,c=o.MenuList,d=o.MenuPortal,u=o.LoadingMessage,f=o.NoOptionsMessage,g=o.Option,m=this.commonProps,p=this.state.focusedOption,y=this.props,b=y.captureMenuScroll,_=y.inputValue,x=y.isLoading,E=y.loadingMessage,C=y.minMenuHeight,k=y.maxMenuHeight,N=y.menuIsOpen,S=y.menuPlacement,w=y.menuPosition,M=y.menuPortalTarget,T=y.menuShouldBlockScroll,P=y.menuShouldScrollIntoView,L=y.noOptionsMessage,$=y.onMenuScrollToTop,A=y.onMenuScrollToBottom;if(!N)return null;var D=function(B,G){var V=B.type,K=B.data,X=B.isDisabled,Z=B.isSelected,ee=B.label,ne=B.value,he=p===K,J=X?void 0:function(){return s.onOptionHover(K)},le=X?void 0:function(){return s.selectOption(K)},ue="".concat(s.getElementId("option"),"-").concat(G),ae={id:ue,onClick:le,onMouseMove:J,onMouseOver:J,tabIndex:-1,role:"option","aria-selected":s.state.isAppleDevice?void 0:Z};return F.createElement(g,Ae({},m,{innerProps:ae,data:K,isDisabled:X,isSelected:Z,key:ue,label:ee,type:V,value:ne,isFocused:he,innerRef:he?s.getFocusedOptionRef:void 0}),s.formatOptionLabel(B.data,"menu"))},O;if(this.hasOptions())O=this.getCategorizedOptions().map(function(U){if(U.type==="group"){var B=U.data,G=U.options,V=U.index,K="".concat(s.getElementId("group"),"-").concat(V),X="".concat(K,"-heading");return F.createElement(i,Ae({},m,{key:K,data:B,options:G,Heading:a,headingProps:{id:X,data:U.data},label:s.formatGroupLabel(U.data)}),U.options.map(function(Z){return D(Z,"".concat(V,"-").concat(Z.index))}))}else if(U.type==="option")return D(U,"".concat(U.index))});else if(x){var z=E({inputValue:_});if(z===null)return null;O=F.createElement(u,m,z)}else{var H=L({inputValue:_});if(H===null)return null;O=F.createElement(f,m,H)}var j={minMenuHeight:C,maxMenuHeight:k,menuPlacement:S,menuPosition:w,menuShouldScrollIntoView:P},W=F.createElement(jw,Ae({},m,j),function(U){var B=U.ref,G=U.placerProps,V=G.placement,K=G.maxHeight;return F.createElement(l,Ae({},m,j,{innerRef:B,innerProps:{onMouseDown:s.onMenuMouseDown,onMouseMove:s.onMenuMouseMove},isLoading:x,placement:V}),F.createElement(mk,{captureEnabled:b,onTopArrive:$,onBottomArrive:A,lockEnabled:T},function(X){return F.createElement(c,Ae({},m,{innerRef:function(ee){s.getMenuListRef(ee),X(ee)},innerProps:{role:"listbox","aria-multiselectable":m.isMulti,id:s.getElementId("listbox")},isLoading:x,maxHeight:K,focusedOption:p}),O)}))});return M||w==="fixed"?F.createElement(d,Ae({},m,{appendTo:M,controlElement:this.controlRef,menuPlacement:S,menuPosition:w}),W):W}},{key:"renderFormField",value:function(){var s=this,o=this.props,i=o.delimiter,a=o.isDisabled,l=o.isMulti,c=o.name,d=o.required,u=this.state.selectValue;if(d&&!this.hasValue()&&!a)return F.createElement(vk,{name:c,onFocus:this.onValueInputFocus});if(!(!c||a))if(l)if(i){var f=u.map(function(p){return s.getOptionValue(p)}).join(i);return F.createElement("input",{name:c,type:"hidden",value:f})}else{var g=u.length>0?u.map(function(p,y){return F.createElement("input",{key:"i-".concat(y),name:c,type:"hidden",value:s.getOptionValue(p)})}):F.createElement("input",{name:c,type:"hidden",value:""});return F.createElement("div",null,g)}else{var m=u[0]?this.getOptionValue(u[0]):"";return F.createElement("input",{name:c,type:"hidden",value:m})}}},{key:"renderLiveRegion",value:function(){var s=this.commonProps,o=this.state,i=o.ariaSelection,a=o.focusedOption,l=o.focusedValue,c=o.isFocused,d=o.selectValue,u=this.getFocusableOptions();return F.createElement(sk,Ae({},s,{id:this.getElementId("live-region"),ariaSelection:i,focusedOption:a,focusedValue:l,isFocused:c,selectValue:d,focusableOptions:u,isAppleDevice:this.state.isAppleDevice}))}},{key:"render",value:function(){var s=this.getComponents(),o=s.Control,i=s.IndicatorsContainer,a=s.SelectContainer,l=s.ValueContainer,c=this.props,d=c.className,u=c.id,f=c.isDisabled,g=c.menuIsOpen,m=this.state.isFocused,p=this.commonProps=this.getCommonProps();return F.createElement(a,Ae({},p,{className:d,innerProps:{id:u,onKeyDown:this.onKeyDown},isDisabled:f,isFocused:m}),this.renderLiveRegion(),F.createElement(o,Ae({},p,{innerRef:this.getControlRef,innerProps:{onMouseDown:this.onControlMouseDown,onTouchEnd:this.onControlTouchEnd},isDisabled:f,isFocused:m,menuIsOpen:g}),F.createElement(l,Ae({},p,{isDisabled:f}),this.renderPlaceholderOrValue(),this.renderInput()),F.createElement(i,Ae({},p,{isDisabled:f}),this.renderClearIndicator(),this.renderLoadingIndicator(),this.renderIndicatorSeparator(),this.renderDropdownIndicator())),this.renderMenu(),this.renderFormField())}}],[{key:"getDerivedStateFromProps",value:function(s,o){var i=o.prevProps,a=o.clearFocusValueOnUpdate,l=o.inputIsHiddenAfterUpdate,c=o.ariaSelection,d=o.isFocused,u=o.prevWasFocused,f=o.instancePrefix,g=s.options,m=s.value,p=s.menuIsOpen,y=s.inputValue,b=s.isMulti,_=ci(m),x={};if(i&&(m!==i.value||g!==i.options||p!==i.menuIsOpen||y!==i.inputValue)){var E=p?Ik(s,_):[],C=p?jf(pi(s,_),"".concat(f,"-option")):[],k=a?Lk(o,_):null,N=Pk(o,E),S=Fl(C,N);x={selectValue:_,focusedOption:N,focusedOptionId:S,focusableOptionsWithIds:C,focusedValue:k,clearFocusValueOnUpdate:!1}}var w=l!=null&&s!==i?{inputIsHidden:l,inputIsHiddenAfterUpdate:void 0}:{},M=c,T=d&&u;return d&&!T&&(M={value:Fs(b,_,_[0]||null),options:_,action:"initial-input-focus"},T=!u),c?.action==="initial-input-focus"&&(M=null),Me(Me(Me({},x),w),{},{prevProps:s,ariaSelection:M,prevWasFocused:T})}}]),n})(v.Component);zl.defaultProps=Dk;var Fk=v.forwardRef(function(t,e){var n=Du(t);return F.createElement(zl,Ae({ref:e},n))}),Zf=Fk,zk=["allowCreateWhileLoading","createOptionPosition","formatCreateLabel","isValidNewOption","getNewOptionData","onCreateOption","options","onChange"],Qf=function(){var e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:"",n=arguments.length>1?arguments[1]:void 0,r=arguments.length>2?arguments[2]:void 0,s=String(e).toLowerCase(),o=String(r.getOptionValue(n)).toLowerCase(),i=String(r.getOptionLabel(n)).toLowerCase();return o===s||i===s},Vl={formatCreateLabel:function(e){return'Create "'.concat(e,'"')},isValidNewOption:function(e,n,r,s){return!(!e||n.some(function(o){return Qf(e,o,s)})||r.some(function(o){return Qf(e,o,s)}))},getNewOptionData:function(e,n){return{label:n,value:e,__isNew__:!0}}};function Vk(t){var e=t.allowCreateWhileLoading,n=e===void 0?!1:e,r=t.createOptionPosition,s=r===void 0?"last":r,o=t.formatCreateLabel,i=o===void 0?Vl.formatCreateLabel:o,a=t.isValidNewOption,l=a===void 0?Vl.isValidNewOption:a,c=t.getNewOptionData,d=c===void 0?Vl.getNewOptionData:c,u=t.onCreateOption,f=t.options,g=f===void 0?[]:f,m=t.onChange,p=An(t,zk),y=p.getOptionValue,b=y===void 0?Vf:y,_=p.getOptionLabel,x=_===void 0?zf:_,E=p.inputValue,C=p.isLoading,k=p.isMulti,N=p.value,S=p.name,w=v.useMemo(function(){return l(E,ci(N),g,{getOptionValue:b,getOptionLabel:x})?d(E,i(E)):void 0},[i,d,x,b,E,l,g,N]),M=v.useMemo(function(){return(n||!C)&&w?s==="first"?[w].concat(Hr(g)):[].concat(Hr(g),[w]):g},[n,s,C,w,g]),T=v.useCallback(function(P,L){if(L.action!=="select-option")return m(P,L);var $=Array.isArray(P)?P:[P];if($[$.length-1]===w){if(u)u(E);else{var A=d(E,E),D={action:"create-option",name:S,option:A};m(Fs(k,[].concat(Hr(ci(N)),[A]),A),D)}return}m(P,L)},[d,E,k,S,w,u,m,N]);return Me(Me({},p),{},{options:M,onChange:T})}var Hk=v.forwardRef(function(t,e){var n=Du(t),r=Vk(n);return F.createElement(zl,Ae({ref:e},r))}),Jf=Hk;const $t={control:"input-tag",valueContainer:"gap-1 flex flex-wrap",multiValue:{blue:"bg-blue-100 dark:bg-blue-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",green:"bg-green-100 dark:bg-green-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",amber:"bg-amber-100 dark:bg-amber-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",red:"bg-red-100 dark:bg-red-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",purple:"bg-purple-100 dark:bg-purple-900/20 rounded px-1.5 py-0.5 flex items-center gap-1",gray:"bg-gray-100 dark:bg-gray-700/50 rounded px-1.5 py-0.5 flex items-center gap-1"},multiValueLabel:{blue:"text-blue-700 dark:text-blue-300 text-xs",green:"text-green-700 dark:text-green-300 text-xs font-semibold",amber:"text-amber-700 dark:text-amber-300 text-xs font-semibold",red:"text-red-700 dark:text-red-300 text-xs font-semibold",purple:"text-purple-700 dark:text-purple-300 text-xs font-semibold",gray:"text-gray-700 dark:text-gray-300 text-xs"},multiValueRemove:{blue:"text-blue-700 dark:text-blue-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",green:"text-green-700 dark:text-green-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",amber:"text-amber-700 dark:text-amber-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",red:"text-red-700 dark:text-red-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",purple:"text-purple-700 dark:text-purple-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1",gray:"text-gray-700 dark:text-gray-300 hover:bg-red-100 dark:hover:bg-red-900/30 hover:text-red-600 dark:hover:text-red-400 rounded px-1"},input:"text-gray-900 dark:text-white text-sm",placeholder:"text-gray-400 dark:text-gray-500 text-sm",menu:"mt-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg",menuList:"py-1",option:{base:"px-3 py-2 cursor-pointer text-gray-900 dark:text-white text-[13px]",selected:"bg-blue-100 dark:bg-blue-900/20",focused:"bg-gray-100 dark:bg-gray-700",default:"bg-white dark:bg-gray-800"},noOptionsMessage:"px-3 py-2 text-gray-500 dark:text-gray-400 text-[13px]",checkbox:{base:"w-4 h-4 rounded border flex items-center justify-center text-white text-[10px] shrink-0",selected:"bg-blue-500 border-blue-500",unselected:"border-gray-400 dark:border-gray-500"}},Rf=t=>{const{data:e,isSelected:n}=t;return h.jsx(gi.Option,{...t,children:h.jsxs("div",{className:"flex items-center gap-2",children:[h.jsx("span",{className:`${$t.checkbox.base} ${n?$t.checkbox.selected:$t.checkbox.unselected}`,children:n?"✓":""}),h.jsxs("div",{className:"flex-1 min-w-0",children:[h.jsxs("div",{className:"flex items-center gap-1",children:[h.jsx("span",{className:"text-[13px]",children:e.label}),e.count!==void 0&&e.count>0&&h.jsxs("span",{className:"text-gray-500 dark:text-gray-400 text-[10px]",children:["(",e.count,")"]})]}),e.description&&h.jsx("p",{className:"text-xs text-gray-500 dark:text-gray-400 truncate",children:e.description})]})]})})},eh=t=>h.jsx(gi.GroupHeading,{...t,children:h.jsx("div",{className:"px-3 py-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 bg-gray-50 dark:bg-gray-800/50 border-b border-gray-200 dark:border-gray-700",children:t.data.label})}),Uk=t=>{const{data:e}=t;return h.jsxs(gi.MultiValueLabel,{...t,children:[h.jsx("span",{children:e.label}),e.count!==void 0&&e.count>0&&h.jsxs("span",{className:"text-xs opacity-60 ml-1",children:["(",e.count,")"]})]})},Bk=({value:t,options:e=[],onChange:n,creatable:r=!1,placeholder:s="검색 또는 입력...",noOptionsMessage:o="검색 결과가 없습니다",formatCreateLabel:i,createLabelSuffix:a="추가",maxItems:l,disabled:c=!1,onBeforeRemove:d,onCreateOption:u,className:f="",tagVariants:g,defaultVariant:m="blue",name:p,isMulti:y=!0,isClearable:b=!0,isSearchable:_=!0,delimiters:x=[","],splitOnPaste:E=!0,onInputChange:C,id:k,editorAttrs:N})=>{const S=c===!0||c==="true",w=v.useCallback(J=>J==null?[]:Array.isArray(J)?J:[J],[]),[M,T]=v.useState(()=>w(t)),[P,L]=v.useState(""),$=v.useRef(void 0);v.useEffect(()=>{const J=(ue,ae)=>!ue&&!ae?!0:!ue||!ae||ue.length!==ae.length?!1:ue.every((ve,fe)=>ve===ae[fe]),le=w(t);J(le,$.current)||(T(le),$.current=le)},[t,w]);const A=v.useMemo(()=>i?typeof i=="string"?J=>`${i} "${J}"`:i:J=>h.jsxs("span",{className:"text-[13px]",children:[h.jsx("strong",{children:J})," ",a]}),[i,a]),D=v.useMemo(()=>!M||!Array.isArray(M)?[]:M.map(J=>e.find(ue=>ue.value===J)||{value:J,label:String(J)}).filter(J=>J!==void 0),[M,e]),O=v.useMemo(()=>{const J=new Set(e.map(we=>we.value)),le=M?M.filter(we=>!J.has(we)).map(we=>({value:we,label:String(we)})):[],ue=[...e,...le];if(!ue.some(we=>we.group))return ue;const ve=new Map,fe=[];ue.forEach(we=>{if(we.group){const He=ve.get(we.group)||[];He.push(we),ve.set(we.group,He)}else fe.push(we)});const ye=[];return ve.forEach((we,He)=>{ye.push({label:He,options:we})}),fe.length>0&&ye.push({label:"",options:fe}),ye},[e,M]),z=v.useCallback(J=>({target:{value:J},currentTarget:{value:J},preventDefault:()=>{},stopPropagation:()=>{}}),[]),H=v.useCallback(J=>{if(!x?.length)return[J.trim()].filter(Boolean);const le=x.map(ae=>ae.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),ue=new RegExp(`[${le.join("")}]`);return J.split(ue).map(ae=>ae.trim()).filter(Boolean)},[x]),j=v.useCallback((J,le)=>{if(le.action!=="input-change"){L(J);return}if(C&&C(z(J)),!x?.some(fe=>J.includes(fe))||!r||!y){L(J);return}const ve=H(J).filter(fe=>fe&&!M.includes(fe));if(ve.length>0){const fe=l?l-M.length:1/0,ye=ve.slice(0,fe);if(ye.length>0){const we=[...M,...ye];T(we),n(z(we))}}L("")},[x,r,y,H,M,l,n,z,C]),W=v.useCallback(J=>{if(!E||!r||!y)return;const le=J.clipboardData.getData("text"),ue=H(le);if(ue.length<=1)return;J.preventDefault();const ae=ue.filter(ve=>ve&&!M.includes(ve));if(ae.length>0){const ve=l?l-M.length:1/0,fe=ae.slice(0,ve);if(fe.length>0){const ye=[...M,...fe];T(ye),n(z(ye))}}},[E,r,y,H,M,l,n,z]),U=v.useCallback((J,le)=>{if((le.action==="remove-value"||le.action==="pop-value")&&le.removedValue&&d){const ae=d(le.removedValue);if(ae!==!0){const ve=typeof ae=="string"?ae:`"${le.removedValue.label}"은(는) 제거할 수 없습니다.`;alert(ve);return}}if(l&&J.length>l)return;const ue=J.map(ae=>ae.value);T(ue),n(z(ue))},[n,d,l,z]),B=v.useCallback((J,le)=>{if(le.action==="clear"&&M.length>0&&d){const ve=e.find(ye=>ye.value===M[0])||{value:M[0],label:String(M[0])},fe=d(ve);if(fe!==!0){const ye=typeof fe=="string"?fe:`"${ve.label}"은(는) 제거할 수 없습니다.`;alert(ye);return}}const ue=J?J.value:null,ae=J?[J.value]:[];T(ae),n(z(ue))},[n,d,M,e,z]),G=v.useCallback(J=>{u&&u(J);const le=[...M||[],J];T(le),n(z(le))},[n,u,M,z]),V=v.useCallback(J=>!J||!J.trim()?!1:!M?.includes(J.trim()),[M]),K=v.useCallback(J=>{const le=P.trim();if(J.key==="Tab"&&r&&y&&le){if(M.includes(le))J.preventDefault(),L("");else if(!l||M.lengthg&&g[J]?g[J]:m,[g,m]),Z={control:()=>`${$t.control} ${f} ${S?"opacity-60 cursor-not-allowed":""}`,valueContainer:()=>$t.valueContainer,multiValue:J=>{const le=X(J.data.value);return $t.multiValue[le]},multiValueLabel:J=>{const le=X(J.data.value);return $t.multiValueLabel[le]},multiValueRemove:J=>{const le=X(J.data.value);return`${$t.multiValueRemove[le]} ${S?"pointer-events-none":""}`},singleValue:()=>"text-gray-900 dark:text-white text-sm",input:()=>$t.input,placeholder:()=>$t.placeholder,menu:()=>$t.menu,menuList:()=>$t.menuList,menuPortal:()=>"z-[9999]",option:J=>`${$t.option.base} ${J.isSelected?$t.option.selected:J.isFocused?$t.option.focused:$t.option.default}`,noOptionsMessage:()=>$t.noOptionsMessage,clearIndicator:()=>"text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 cursor-pointer px-1",dropdownIndicator:()=>"text-gray-400 dark:text-gray-500 px-1"},ee={options:O,onKeyDown:K,placeholder:s,noOptionsMessage:()=>o,isDisabled:S,isSearchable:_,menuIsOpen:void 0,openMenuOnClick:!0,openMenuOnFocus:!0,menuPortalTarget:typeof document<"u"?document.body:null,menuPosition:"fixed",classNamePrefix:"tag-input",unstyled:!0,classNames:Z,...r&&y||C?{inputValue:P,onInputChange:j}:{}},ne=y?M.join(","):M[0]??"";if(!y){const J={...ee,isMulti:!1,value:D[0]||null,onChange:B,isClearable:b,closeMenuOnSelect:!0,components:{Option:Rf,GroupHeading:eh,IndicatorSeparator:()=>null,...b?{}:{ClearIndicator:()=>null}}};return r?h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ne}),h.jsx(Jf,{...J,onCreateOption:G,formatCreateLabel:A,isValidNewOption:V,createOptionPosition:"first"})]}):h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ne}),h.jsx(Zf,{...J})]})}const he={...ee,isMulti:!0,value:D,onChange:U,closeMenuOnSelect:!1,hideSelectedOptions:!1,backspaceRemovesValue:!1,isClearable:!1,components:{Option:Rf,GroupHeading:eh,MultiValueLabel:Uk,ClearIndicator:()=>null,DropdownIndicator:()=>null,IndicatorSeparator:()=>null}};return r?h.jsxs(I,{id:k,...N,onPaste:W,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ne}),h.jsx(Jf,{...he,onCreateOption:G,formatCreateLabel:A,isValidNewOption:V,createOptionPosition:"first"})]}):h.jsxs(I,{id:k,...N,children:[p&&h.jsx("input",{type:"hidden",name:p,value:ne}),h.jsx(Zf,{...he})]})},Gr=(t,e)=>window.G7Core?.t?.(t,e)??t,jk=()=>window.G7Core?.locale?.supported?.()??["ko","en"],Wk=()=>window.G7Core?.locale?.current?.()??"ko",Hl=t=>{const e=Gr(`common.language_${t}`);return e===`common.language_${t}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[t]||t.toUpperCase():e},Gk=({value:t=[],onChange:e,placeholder:n="입력 후 Enter",disabled:r=!1,creatable:s=!0,delimiters:o=[","],className:i="",name:a,error:l,maxItems:c,modalId:d,statePath:u,__componentContext:f,id:g,editorAttrs:m})=>{const p=v.useMemo(()=>jk(),[]),y=v.useMemo(()=>Wk(),[]),[b,_]=v.useState(()=>t||[]),[x,E]=v.useState(""),[C,k]=v.useState(!1),[N,S]=v.useState(null),[w,M]=v.useState({}),T=v.useRef(null),P=v.useRef(void 0);v.useEffect(()=>{const V=(X,Z)=>!X&&!Z?!0:!X||!Z||X.length!==Z.length?!1:JSON.stringify(X)===JSON.stringify(Z),K=t||[];V(K,P.current)||(_(K),P.current=K)},[t]);const L=v.useCallback(V=>({target:{name:a,value:V},currentTarget:{name:a,value:V},preventDefault:()=>{},stopPropagation:()=>{}}),[a]),$=v.useCallback(V=>{const K=[];for(const X of p){const Z=V[X];Z?.trim()&&K.push(`${X.toUpperCase()}: ${Z}`)}return K.length>0?K.join(" / "):""},[p]),A=v.useCallback(V=>{const K=b.filter((X,Z)=>Z!==V);_(K),e?.(L(K))},[b,e,L]),D=v.useCallback((V,K,X)=>{const Z=window.G7Core;if(!Z?.dispatch)return;const ee=p.map(ne=>({code:ne,label:Hl(ne)}));Z.dispatch({handler:"setState",params:{target:"global",multilingualTagEdit:{fieldName:a,editingIndex:V,values:K,supportedLocales:ee,defaultLocale:y,statePath:u,currentTags:X}}}),Z.dispatch({handler:"openModal",target:d},{componentContext:f})},[a,p,y,d,u,f]),O=v.useCallback(V=>{r||(d?D(V,{...b[V]},b):(S(V),M({...b[V]}),k(!0)))},[r,b,d,D]),z=v.useCallback((V="")=>{const K={};p.forEach(X=>{K[X]=X===y?V:""}),d?(D(null,K,b),E("")):(S(null),M(K),k(!0),E(""))},[p,y,d,D,b]),H=v.useCallback(()=>{if(!Object.values(w).some(X=>X.trim()!=="")){k(!1);return}let K;N!==null?K=b.map((X,Z)=>Z===N?w:X):K=[...b,w],_(K),e?.(L(K)),k(!1),M({}),S(null)},[w,N,b,e,L]),j=v.useCallback(()=>{k(!1),M({}),S(null)},[]),W=v.useCallback(V=>{const K=V.target.value;if(o.some(Z=>K.includes(Z))&&s){const Z=o.map(he=>he.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")),ee=new RegExp(`[${Z.join("")}]`),ne=K.split(ee).map(he=>he.trim()).filter(Boolean);if(ne.length>0){z(ne[0]);return}}E(K)},[o,s,z]),U=v.useCallback(V=>{if(V.key==="Enter"&&x.trim()&&s){if(V.preventDefault(),c&&b.length>=c)return;z(x.trim())}},[x,s,c,b.length,z]),B=v.useCallback((V,K)=>{M(X=>({...X,[V]:K}))},[]),G=!!l;return h.jsxs(I,{className:i,id:g,...m,children:[h.jsxs(I,{className:`flex flex-wrap gap-1.5 p-2 border rounded-lg bg-white dark:bg-gray-700 min-h-[42px] ${G?"border-red-500 dark:border-red-400":"border-gray-300 dark:border-gray-600 focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500"} ${r?"opacity-60 bg-gray-100 dark:bg-gray-800 cursor-not-allowed":""}`,children:[b.map((V,K)=>h.jsxs(I,{className:"flex items-center gap-1 bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded px-2 py-0.5 text-sm",children:[h.jsx(Q,{className:`cursor-pointer hover:underline ${r?"cursor-not-allowed":""}`,onClick:()=>O(K),children:$(V)}),!r&&h.jsx(oe,{type:"button",className:"p-0 h-4 w-4 min-w-0 border-0 bg-transparent text-blue-600 dark:text-blue-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-transparent",onClick:X=>{X.stopPropagation(),A(K)},children:h.jsx(re,{name:"times",className:"text-xs"})})]},K)),s&&!r&&(!c||b.lengthh.jsxs(I,{className:"space-y-1",children:[h.jsxs(I,{className:"flex items-center gap-2",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300 w-16",children:Hl(V)}),h.jsxs(Q,{className:"text-xs text-gray-400 dark:text-gray-500",children:["(",V.toUpperCase(),")"]})]}),h.jsx(kt,{type:"text",value:w[V]??"",onChange:K=>B(V,K.target.value),placeholder:`${Hl(V)} 값 입력`,className:"w-full",autoFocus:V===y})]},V)),h.jsxs(I,{className:"flex justify-end gap-2 pt-4 border-t border-gray-200 dark:border-gray-700",children:[h.jsx(oe,{type:"button",variant:"secondary",onClick:j,children:Gr("common.cancel")}),h.jsx(oe,{type:"button",variant:"primary",onClick:H,children:Gr(N!==null?"common.save":"common.add")})]})]})}),a&&h.jsx("input",{type:"hidden",name:a,value:JSON.stringify(b)})]})},th=(t,e)=>window.G7Core?.t?.(t,e)??t,nh=({type:t,identifier:e,name:n,installedModules:r=[],installedPlugins:s=[],className:o="",style:i,id:a,editorAttrs:l})=>{const c={module:ce.Cubes,plugin:ce.PuzzlePiece},d={module:th("common.module"),plugin:th("common.plugin")},u=v.useMemo(()=>{if(n)return n;if(e){const p=(t==="module"?r:s)?.find(y=>y.identifier===e);if(p)return p.name}},[n,e,t,r,s]),f=c[t],g=u?`${u} ${d[t]}`:d[t];return h.jsxs(Q,{className:`inline-flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium text-gray-400 dark:text-gray-500 ${o}`,style:i,id:a,...l,children:[h.jsx(re,{name:f,className:"w-3.5 h-3.5"}),h.jsx(Q,{children:g})]})},Yk=({options:t=[],value:e,onChange:n,multiple:r=!1,searchPlaceholder:s="Search...",onSearch:o,noResultsText:i="No results found",initialText:a="Type to search",triggerLabel:l="Select",triggerIcon:c,disabled:d=!1,className:u="",id:f,editorAttrs:g})=>{const[m,p]=v.useState(!1),[y,b]=v.useState(""),_=v.useRef(null),x=v.useRef(null),E=v.useRef(`searchable-dropdown-input-${Math.random().toString(36).slice(2,9)}`).current,C=v.useMemo(()=>e==null?[]:Array.isArray(e)?e:[e],[e]),k=v.useMemo(()=>{if(!y.trim())return t;const A=y.toLowerCase();return t.filter(D=>D.label.toLowerCase().includes(A)||D.description?.toLowerCase().includes(A))},[t,y]),N=v.useMemo(()=>{if(C.length===0)return l;if(r){const D=C.length;return D===1?t.find(z=>String(z.value)===String(C[0]))?.label||l:`${D} selected`}return t.find(D=>String(D.value)===String(C[0]))?.label||l},[C,t,r,l]);v.useEffect(()=>{const A=D=>{_.current&&!_.current.contains(D.target)&&p(!1)};return m&&document.addEventListener("mousedown",A),()=>{document.removeEventListener("mousedown",A)}},[m]),v.useEffect(()=>{const A=D=>{D.key==="Escape"&&m&&(p(!1),x.current?.focus())};return document.addEventListener("keydown",A),()=>document.removeEventListener("keydown",A)},[m]),v.useEffect(()=>{if(!m){b("");return}const A=setTimeout(()=>{document.getElementById(E)?.focus()},50);return()=>{clearTimeout(A)}},[m,E]);const S=v.useCallback(()=>{d||p(A=>!A)},[d]),w=v.useCallback(A=>{const D=A.target.value;b(D),o?.(D)},[o]),M=v.useCallback(A=>{if(r){const O=C.some(z=>String(z)===String(A))?C.filter(z=>String(z)!==String(A)):[...C,A];n?.(O)}else n?.(A),p(!1),x.current?.focus()},[r,C,n]),T=A=>C.some(D=>String(D)===String(A)),P=y.length===0&&t.length===0,L=y.length>0&&k.length===0,$=k.length>0;return h.jsxs(I,{ref:_,className:`relative inline-block ${u}`,id:f,...g,children:[h.jsxs(oe,{ref:x,type:"button",onClick:S,disabled:d,className:` px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl text-gray-700 dark:text-gray-200 font-medium @@ -182,27 +181,27 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed min-w-[200px] - `,"aria-haspopup":"listbox","aria-expanded":m,children:[h.jsxs(I,{className:"flex items-center gap-2",children:[c,h.jsx(Q,{className:"truncate",children:N})]}),h.jsx(Qn,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${m?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),m&&h.jsxs(I,{className:"absolute z-50 w-full mt-2 bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden min-w-[280px]",role:"listbox",children:[h.jsx(I,{className:"p-3 border-b border-gray-200 dark:border-gray-700",children:h.jsx("input",{id:M,type:"text",value:y,onChange:w,placeholder:s,className:"w-full px-3 py-2 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent focus:outline-none"})}),h.jsxs(I,{className:"max-h-80 overflow-auto",children:[P&&h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:a}),L&&h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:i}),$&&h.jsx(I,{className:"py-2",children:k.map(A=>{const O=D(A.value);return h.jsxs(ie,{type:"button",onClick:()=>!A.disabled&&E(A.value),disabled:A.disabled,className:` + `,"aria-haspopup":"listbox","aria-expanded":m,children:[h.jsxs(I,{className:"flex items-center gap-2",children:[c,h.jsx(Q,{className:"truncate",children:N})]}),h.jsx(Un,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${m?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),m&&h.jsxs(I,{className:"absolute z-50 w-full mt-2 bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden min-w-[280px]",role:"listbox",children:[h.jsx(I,{className:"p-3 border-b border-gray-200 dark:border-gray-700",children:h.jsx("input",{id:E,type:"text",value:y,onChange:w,placeholder:s,className:"w-full px-3 py-2 bg-gray-50 dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent focus:outline-none"})}),h.jsxs(I,{className:"max-h-80 overflow-auto",children:[P&&h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:a}),L&&h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:i}),$&&h.jsx(I,{className:"py-2",children:k.map(A=>{const D=T(A.value);return h.jsxs(oe,{type:"button",onClick:()=>!A.disabled&&M(A.value),disabled:A.disabled,className:` w-full px-4 py-3 text-left flex items-start gap-3 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${A.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"} - `,role:"option","aria-selected":O,children:[h.jsx(I,{className:"w-5 h-5 flex-shrink-0 mt-0.5",children:O&&h.jsx(Qn,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsx(I,{className:`font-medium ${O?"text-blue-600 dark:text-blue-400":"text-gray-900 dark:text-white"}`,children:A.label}),A.description&&h.jsx(I,{className:"text-sm text-gray-500 dark:text-gray-400 mt-0.5",children:A.description})]})]},A.value)})})]})]})]})},Rs={container:{base:"tag-select",disabled:"opacity-50 cursor-not-allowed"},tag:"tag",tagRemove:"tag-remove",placeholder:"tag-select-placeholder"},fC=({options:e=[],value:t=[],onChange:n,placeholder:r="No items selected",disabled:s=!1,className:o="",id:i,editorAttrs:a})=>{const l=v.useCallback(f=>e.find(m=>String(m.value)===String(f))?.label||String(f),[e]),c=v.useCallback(f=>{if(s)return;const g=t.filter(m=>String(m)!==String(f));n?.(g)},[t,n,s]),d=v.useMemo(()=>t.map(f=>({value:f,label:l(f)})),[t,l]),u=`${Rs.container.base} ${s?Rs.container.disabled:""} ${o}`.trim();return h.jsx(I,{className:u,id:i,...a,children:d.length===0?h.jsx(Q,{className:Rs.placeholder,children:r}):d.map(f=>h.jsxs(Q,{className:Rs.tag,children:[f.label,!s&&h.jsx(Q,{className:Rs.tagRemove,onClick:()=>c(f.value),role:"button","aria-label":`Remove ${f.label}`,children:h.jsx(Qn,{className:"w-3 h-3",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]},f.value))})};function hC(){for(var e=arguments.length,t=new Array(e),n=0;nr=>{t.forEach(s=>s(r))},t)}const Ei=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function ds(e){const t=Object.prototype.toString.call(e);return t==="[object Window]"||t==="[object global]"}function ql(e){return"nodeType"in e}function Qt(e){var t,n;return e?ds(e)?e:ql(e)&&(t=(n=e.ownerDocument)==null?void 0:n.defaultView)!=null?t:window:window}function Zl(e){const{Document:t}=Qt(e);return e instanceof t}function eo(e){return ds(e)?!1:e instanceof Qt(e).HTMLElement}function ph(e){return e instanceof Qt(e).SVGElement}function us(e){return e?ds(e)?e.document:ql(e)?Zl(e)?e:eo(e)||ph(e)?e.ownerDocument:document:document:document}const En=Ei?v.useLayoutEffect:v.useEffect;function Mi(e){const t=v.useRef(e);return En(()=>{t.current=e}),v.useCallback(function(){for(var n=arguments.length,r=new Array(n),s=0;s{e.current=setInterval(r,s)},[]),n=v.useCallback(()=>{e.current!==null&&(clearInterval(e.current),e.current=null)},[]);return[t,n]}function to(e,t){t===void 0&&(t=[e]);const n=v.useRef(e);return En(()=>{n.current!==e&&(n.current=e)},t),n}function no(e,t){const n=v.useRef();return v.useMemo(()=>{const r=e(n.current);return n.current=r,r},[...t])}function Ni(e){const t=Mi(e),n=v.useRef(null),r=v.useCallback(s=>{s!==n.current&&t?.(s,n.current),n.current=s},[]);return[n,r]}function Ti(e){const t=v.useRef();return v.useEffect(()=>{t.current=e},[e]),t.current}let Ql={};function ro(e,t){return v.useMemo(()=>{if(t)return t;const n=Ql[e]==null?0:Ql[e]+1;return Ql[e]=n,e+"-"+n},[e,t])}function mh(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),s=1;s{const a=Object.entries(i);for(const[l,c]of a){const d=o[l];d!=null&&(o[l]=d+e*c)}return o},{...t})}}const fs=mh(1),so=mh(-1);function pC(e){return"clientX"in e&&"clientY"in e}function Oi(e){if(!e)return!1;const{KeyboardEvent:t}=Qt(e.target);return t&&e instanceof t}function mC(e){if(!e)return!1;const{TouchEvent:t}=Qt(e.target);return t&&e instanceof t}function Ai(e){if(mC(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}else if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return pC(e)?{x:e.clientX,y:e.clientY}:null}const jn=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[jn.Translate.toString(e),jn.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),bh="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function bC(e){return e.matches(bh)?e:e.querySelector(bh)}const yC={display:"none"};function vC(e){let{id:t,value:n}=e;return v.createElement("div",{id:t,style:yC},n)}function xC(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;const s={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return v.createElement("div",{id:t,style:s,role:"status","aria-live":r,"aria-atomic":!0},n)}function wC(){const[e,t]=v.useState("");return{announce:v.useCallback(r=>{r!=null&&t(r)},[]),announcement:e}}const yh=v.createContext(null);function kC(e){const t=v.useContext(yh);v.useEffect(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}function CC(){const[e]=v.useState(()=>new Set),t=v.useCallback(r=>(e.add(r),()=>e.delete(r)),[e]);return[v.useCallback(r=>{let{type:s,event:o}=r;e.forEach(i=>{var a;return(a=i[s])==null?void 0:a.call(i,o)})},[e]),t]}const SC={draggable:` + `,role:"option","aria-selected":D,children:[h.jsx(I,{className:"w-5 h-5 flex-shrink-0 mt-0.5",children:D&&h.jsx(Un,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsx(I,{className:`font-medium ${D?"text-blue-600 dark:text-blue-400":"text-gray-900 dark:text-white"}`,children:A.label}),A.description&&h.jsx(I,{className:"text-sm text-gray-500 dark:text-gray-400 mt-0.5",children:A.description})]})]},A.value)})})]})]})]})},Vs={container:{base:"tag-select",disabled:"opacity-50 cursor-not-allowed"},tag:"tag",tagRemove:"tag-remove",placeholder:"tag-select-placeholder"},Xk=({options:t=[],value:e=[],onChange:n,placeholder:r="No items selected",disabled:s=!1,className:o="",id:i,editorAttrs:a})=>{const l=v.useCallback(f=>t.find(m=>String(m.value)===String(f))?.label||String(f),[t]),c=v.useCallback(f=>{if(s)return;const g=e.filter(m=>String(m)!==String(f));n?.(g)},[e,n,s]),d=v.useMemo(()=>e.map(f=>({value:f,label:l(f)})),[e,l]),u=`${Vs.container.base} ${s?Vs.container.disabled:""} ${o}`.trim();return h.jsx(I,{className:u,id:i,...a,children:d.length===0?h.jsx(Q,{className:Vs.placeholder,children:r}):d.map(f=>h.jsxs(Q,{className:Vs.tag,children:[f.label,!s&&h.jsx(Q,{className:Vs.tagRemove,onClick:()=>c(f.value),role:"button","aria-label":`Remove ${f.label}`,children:h.jsx(Un,{className:"w-3 h-3",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]},f.value))})};function Kk(){for(var t=arguments.length,e=new Array(t),n=0;nr=>{e.forEach(s=>s(r))},e)}const bi=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Yr(t){const e=Object.prototype.toString.call(t);return e==="[object Window]"||e==="[object global]"}function Ul(t){return"nodeType"in t}function Kt(t){var e,n;return t?Yr(t)?t:Ul(t)&&(e=(n=t.ownerDocument)==null?void 0:n.defaultView)!=null?e:window:window}function Bl(t){const{Document:e}=Kt(t);return t instanceof e}function Hs(t){return Yr(t)?!1:t instanceof Kt(t).HTMLElement}function rh(t){return t instanceof Kt(t).SVGElement}function Xr(t){return t?Yr(t)?t.document:Ul(t)?Bl(t)?t:Hs(t)||rh(t)?t.ownerDocument:document:document:document}const wn=bi?v.useLayoutEffect:v.useEffect;function yi(t){const e=v.useRef(t);return wn(()=>{e.current=t}),v.useCallback(function(){for(var n=arguments.length,r=new Array(n),s=0;s{t.current=setInterval(r,s)},[]),n=v.useCallback(()=>{t.current!==null&&(clearInterval(t.current),t.current=null)},[]);return[e,n]}function Us(t,e){e===void 0&&(e=[t]);const n=v.useRef(t);return wn(()=>{n.current!==t&&(n.current=t)},e),n}function Bs(t,e){const n=v.useRef();return v.useMemo(()=>{const r=t(n.current);return n.current=r,r},[...e])}function vi(t){const e=yi(t),n=v.useRef(null),r=v.useCallback(s=>{s!==n.current&&e?.(s,n.current),n.current=s},[]);return[n,r]}function xi(t){const e=v.useRef();return v.useEffect(()=>{e.current=t},[t]),e.current}let jl={};function js(t,e){return v.useMemo(()=>{if(e)return e;const n=jl[t]==null?0:jl[t]+1;return jl[t]=n,t+"-"+n},[t,e])}function sh(t){return function(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),s=1;s{const a=Object.entries(i);for(const[l,c]of a){const d=o[l];d!=null&&(o[l]=d+t*c)}return o},{...e})}}const Kr=sh(1),Ws=sh(-1);function Zk(t){return"clientX"in t&&"clientY"in t}function wi(t){if(!t)return!1;const{KeyboardEvent:e}=Kt(t.target);return e&&t instanceof e}function Qk(t){if(!t)return!1;const{TouchEvent:e}=Kt(t.target);return e&&t instanceof e}function ki(t){if(Qk(t)){if(t.touches&&t.touches.length){const{clientX:e,clientY:n}=t.touches[0];return{x:e,y:n}}else if(t.changedTouches&&t.changedTouches.length){const{clientX:e,clientY:n}=t.changedTouches[0];return{x:e,y:n}}}return Zk(t)?{x:t.clientX,y:t.clientY}:null}const Pn=Object.freeze({Translate:{toString(t){if(!t)return;const{x:e,y:n}=t;return"translate3d("+(e?Math.round(e):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(t){if(!t)return;const{scaleX:e,scaleY:n}=t;return"scaleX("+e+") scaleY("+n+")"}},Transform:{toString(t){if(t)return[Pn.Translate.toString(t),Pn.Scale.toString(t)].join(" ")}},Transition:{toString(t){let{property:e,duration:n,easing:r}=t;return e+" "+n+"ms "+r}}}),oh="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function Jk(t){return t.matches(oh)?t:t.querySelector(oh)}const Rk={display:"none"};function eC(t){let{id:e,value:n}=t;return v.createElement("div",{id:e,style:Rk},n)}function tC(t){let{id:e,announcement:n,ariaLiveType:r="assertive"}=t;const s={position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};return v.createElement("div",{id:e,style:s,role:"status","aria-live":r,"aria-atomic":!0},n)}function nC(){const[t,e]=v.useState("");return{announce:v.useCallback(r=>{r!=null&&e(r)},[]),announcement:t}}const ih=v.createContext(null);function rC(t){const e=v.useContext(ih);v.useEffect(()=>{if(!e)throw new Error("useDndMonitor must be used within a children of ");return e(t)},[t,e])}function sC(){const[t]=v.useState(()=>new Set),e=v.useCallback(r=>(t.add(r),()=>t.delete(r)),[t]);return[v.useCallback(r=>{let{type:s,event:o}=r;t.forEach(i=>{var a;return(a=i[s])==null?void 0:a.call(i,o)})},[t]),e]}const oC={draggable:` To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel. - `},_C={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function EC(e){let{announcements:t=_C,container:n,hiddenTextDescribedById:r,screenReaderInstructions:s=SC}=e;const{announce:o,announcement:i}=wC(),a=ro("DndLiveRegion"),[l,c]=v.useState(!1);if(v.useEffect(()=>{c(!0)},[]),kC(v.useMemo(()=>({onDragStart(u){let{active:f}=u;o(t.onDragStart({active:f}))},onDragMove(u){let{active:f,over:g}=u;t.onDragMove&&o(t.onDragMove({active:f,over:g}))},onDragOver(u){let{active:f,over:g}=u;o(t.onDragOver({active:f,over:g}))},onDragEnd(u){let{active:f,over:g}=u;o(t.onDragEnd({active:f,over:g}))},onDragCancel(u){let{active:f,over:g}=u;o(t.onDragCancel({active:f,over:g}))}}),[o,t])),!l)return null;const d=v.createElement(v.Fragment,null,v.createElement(vC,{id:r,value:s.draggable}),v.createElement(xC,{id:a,announcement:i}));return n?$n.createPortal(d,n):d}var Tt;(function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"})(Tt||(Tt={}));function Di(){}function hs(e,t){return v.useMemo(()=>({sensor:e,options:t??{}}),[e,t])}function Jl(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(r=>r!=null),[...t])}const Mn=Object.freeze({x:0,y:0});function vh(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function MC(e,t){const n=Ai(e);if(!n)return"0 0";const r={x:(n.x-t.left)/t.width*100,y:(n.y-t.top)/t.height*100};return r.x+"% "+r.y+"%"}function xh(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return n-r}function NC(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return r-n}function wh(e){let{left:t,top:n,height:r,width:s}=e;return[{x:t,y:n},{x:t+s,y:n},{x:t,y:n+r},{x:t+s,y:n+r}]}function kh(e,t){if(!e||e.length===0)return null;const[n]=e;return n[t]}function Ch(e,t,n){return t===void 0&&(t=e.left),n===void 0&&(n=e.top),{x:t+e.width*.5,y:n+e.height*.5}}const Rl=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const s=Ch(t,t.left,t.top),o=[];for(const i of r){const{id:a}=i,l=n.get(a);if(l){const c=vh(Ch(l),s);o.push({id:a,data:{droppableContainer:i,value:c}})}}return o.sort(xh)},TC=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const s=wh(t),o=[];for(const i of r){const{id:a}=i,l=n.get(a);if(l){const c=wh(l),d=s.reduce((f,g,m)=>f+vh(c[m],g),0),u=Number((d/4).toFixed(4));o.push({id:a,data:{droppableContainer:i,value:u}})}}return o.sort(xh)};function OC(e,t){const n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),s=Math.min(t.left+t.width,e.left+e.width),o=Math.min(t.top+t.height,e.top+e.height),i=s-r,a=o-n;if(r{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const s=[];for(const o of r){const{id:i}=o,a=n.get(i);if(a){const l=OC(a,t);l>0&&s.push({id:i,data:{droppableContainer:o,value:l}})}}return s.sort(NC)};function DC(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}function Sh(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:Mn}function IC(e){return function(n){for(var r=arguments.length,s=new Array(r>1?r-1:0),o=1;o({...i,top:i.top+e*a.y,bottom:i.bottom+e*a.y,left:i.left+e*a.x,right:i.right+e*a.x}),{...n})}}const LC=IC(1);function _h(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}else if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}function PC(e,t,n){const r=_h(t);if(!r)return e;const{scaleX:s,scaleY:o,x:i,y:a}=r,l=e.left-i-(1-s)*parseFloat(n),c=e.top-a-(1-o)*parseFloat(n.slice(n.indexOf(" ")+1)),d=s?e.width/s:e.width,u=o?e.height/o:e.height;return{width:d,height:u,top:c,right:l+d,bottom:c+u,left:l}}const $C={ignoreTransform:!1};function gs(e,t){t===void 0&&(t=$C);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:c,transformOrigin:d}=Qt(e).getComputedStyle(e);c&&(n=PC(n,c,d))}const{top:r,left:s,width:o,height:i,bottom:a,right:l}=n;return{top:r,left:s,width:o,height:i,bottom:a,right:l}}function Eh(e){return gs(e,{ignoreTransform:!0})}function FC(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}function zC(e,t){return t===void 0&&(t=Qt(e).getComputedStyle(e)),t.position==="fixed"}function VC(e,t){t===void 0&&(t=Qt(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(s=>{const o=t[s];return typeof o=="string"?n.test(o):!1})}function Ii(e,t){const n=[];function r(s){if(t!=null&&n.length>=t||!s)return n;if(Zl(s)&&s.scrollingElement!=null&&!n.includes(s.scrollingElement))return n.push(s.scrollingElement),n;if(!eo(s)||ph(s)||n.includes(s))return n;const o=Qt(e).getComputedStyle(s);return s!==e&&VC(s,o)&&n.push(s),zC(s,o)?n:r(s.parentNode)}return e?r(e):n}function Mh(e){const[t]=Ii(e,1);return t??null}function ec(e){return!Ei||!e?null:ds(e)?e:ql(e)?Zl(e)||e===us(e).scrollingElement?window:eo(e)?e:null:null}function Nh(e){return ds(e)?e.scrollX:e.scrollLeft}function Th(e){return ds(e)?e.scrollY:e.scrollTop}function tc(e){return{x:Nh(e),y:Th(e)}}var It;(function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"})(It||(It={}));function Oh(e){return!Ei||!e?!1:e===document.scrollingElement}function Ah(e){const t={x:0,y:0},n=Oh(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},r={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height},s=e.scrollTop<=t.y,o=e.scrollLeft<=t.x,i=e.scrollTop>=r.y,a=e.scrollLeft>=r.x;return{isTop:s,isLeft:o,isBottom:i,isRight:a,maxScroll:r,minScroll:t}}const HC={x:.2,y:.2};function UC(e,t,n,r,s){let{top:o,left:i,right:a,bottom:l}=n;r===void 0&&(r=10),s===void 0&&(s=HC);const{isTop:c,isBottom:d,isLeft:u,isRight:f}=Ah(e),g={x:0,y:0},m={x:0,y:0},p={height:t.height*s.y,width:t.width*s.x};return!c&&o<=t.top+p.height?(g.y=It.Backward,m.y=r*Math.abs((t.top+p.height-o)/p.height)):!d&&l>=t.bottom-p.height&&(g.y=It.Forward,m.y=r*Math.abs((t.bottom-p.height-l)/p.height)),!f&&a>=t.right-p.width?(g.x=It.Forward,m.x=r*Math.abs((t.right-p.width-a)/p.width)):!u&&i<=t.left+p.width&&(g.x=It.Backward,m.x=r*Math.abs((t.left+p.width-i)/p.width)),{direction:g,speed:m}}function BC(e){if(e===document.scrollingElement){const{innerWidth:o,innerHeight:i}=window;return{top:0,left:0,right:o,bottom:i,width:o,height:i}}const{top:t,left:n,right:r,bottom:s}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:s,width:e.clientWidth,height:e.clientHeight}}function Dh(e){return e.reduce((t,n)=>fs(t,tc(n)),Mn)}function jC(e){return e.reduce((t,n)=>t+Nh(n),0)}function WC(e){return e.reduce((t,n)=>t+Th(n),0)}function Ih(e,t){if(t===void 0&&(t=gs),!e)return;const{top:n,left:r,bottom:s,right:o}=t(e);Mh(e)&&(s<=0||o<=0||n>=window.innerHeight||r>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}const GC=[["x",["left","right"],jC],["y",["top","bottom"],WC]];class nc{constructor(t,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const r=Ii(n),s=Dh(r);this.rect={...t},this.width=t.width,this.height=t.height;for(const[o,i,a]of GC)for(const l of i)Object.defineProperty(this,l,{get:()=>{const c=a(r),d=s[o]-c;return this.rect[l]+d},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class oo{constructor(t){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var r;return(r=this.target)==null?void 0:r.removeEventListener(...n)})},this.target=t}add(t,n,r){var s;(s=this.target)==null||s.addEventListener(t,n,r),this.listeners.push([t,n,r])}}function YC(e){const{EventTarget:t}=Qt(e);return e instanceof t?e:us(e)}function rc(e,t){const n=Math.abs(e.x),r=Math.abs(e.y);return typeof t=="number"?Math.sqrt(n**2+r**2)>t:"x"in t&&"y"in t?n>t.x&&r>t.y:"x"in t?n>t.x:"y"in t?r>t.y:!1}var pn;(function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"})(pn||(pn={}));function Lh(e){e.preventDefault()}function XC(e){e.stopPropagation()}var Ze;(function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"})(Ze||(Ze={}));const Ph={start:[Ze.Space,Ze.Enter],cancel:[Ze.Esc],end:[Ze.Space,Ze.Enter,Ze.Tab]},KC=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Ze.Right:return{...n,x:n.x+25};case Ze.Left:return{...n,x:n.x-25};case Ze.Down:return{...n,y:n.y+25};case Ze.Up:return{...n,y:n.y-25}}};class io{constructor(t){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=t;const{event:{target:n}}=t;this.props=t,this.listeners=new oo(us(n)),this.windowListeners=new oo(Qt(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(pn.Resize,this.handleCancel),this.windowListeners.add(pn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(pn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:t,onStart:n}=this.props,r=t.node.current;r&&Ih(r),n(Mn)}handleKeyDown(t){if(Oi(t)){const{active:n,context:r,options:s}=this.props,{keyboardCodes:o=Ph,coordinateGetter:i=KC,scrollBehavior:a="smooth"}=s,{code:l}=t;if(o.end.includes(l)){this.handleEnd(t);return}if(o.cancel.includes(l)){this.handleCancel(t);return}const{collisionRect:c}=r.current,d=c?{x:c.left,y:c.top}:Mn;this.referenceCoordinates||(this.referenceCoordinates=d);const u=i(t,{active:n,context:r.current,currentCoordinates:d});if(u){const f=so(u,d),g={x:0,y:0},{scrollableAncestors:m}=r.current;for(const p of m){const y=t.code,{isTop:b,isRight:_,isLeft:x,isBottom:M,maxScroll:C,minScroll:k}=Ah(p),N=BC(p),S={x:Math.min(y===Ze.Right?N.right-N.width/2:N.right,Math.max(y===Ze.Right?N.left:N.left+N.width/2,u.x)),y:Math.min(y===Ze.Down?N.bottom-N.height/2:N.bottom,Math.max(y===Ze.Down?N.top:N.top+N.height/2,u.y))},w=y===Ze.Right&&!_||y===Ze.Left&&!x,E=y===Ze.Down&&!M||y===Ze.Up&&!b;if(w&&S.x!==u.x){const D=p.scrollLeft+f.x,P=y===Ze.Right&&D<=C.x||y===Ze.Left&&D>=k.x;if(P&&!f.y){p.scrollTo({left:D,behavior:a});return}P?g.x=p.scrollLeft-D:g.x=y===Ze.Right?p.scrollLeft-C.x:p.scrollLeft-k.x,g.x&&p.scrollBy({left:-g.x,behavior:a});break}else if(E&&S.y!==u.y){const D=p.scrollTop+f.y,P=y===Ze.Down&&D<=C.y||y===Ze.Up&&D>=k.y;if(P&&!f.x){p.scrollTo({top:D,behavior:a});return}P?g.y=p.scrollTop-D:g.y=y===Ze.Down?p.scrollTop-C.y:p.scrollTop-k.y,g.y&&p.scrollBy({top:-g.y,behavior:a});break}}this.handleMove(t,fs(so(u,this.referenceCoordinates),g))}}}handleMove(t,n){const{onMove:r}=this.props;t.preventDefault(),r(n)}handleEnd(t){const{onEnd:n}=this.props;t.preventDefault(),this.detach(),n()}handleCancel(t){const{onCancel:n}=this.props;t.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}io.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:r=Ph,onActivation:s}=t,{active:o}=n;const{code:i}=e.nativeEvent;if(r.start.includes(i)){const a=o.activatorNode.current;return a&&e.target!==a?!1:(e.preventDefault(),s?.({event:e.nativeEvent}),!0)}return!1}}];function $h(e){return!!(e&&"distance"in e)}function Fh(e){return!!(e&&"delay"in e)}class sc{constructor(t,n,r){var s;r===void 0&&(r=YC(t.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=t,this.events=n;const{event:o}=t,{target:i}=o;this.props=t,this.events=n,this.document=us(i),this.documentListeners=new oo(this.document),this.listeners=new oo(r),this.windowListeners=new oo(Qt(i)),this.initialCoordinates=(s=Ai(o))!=null?s:Mn,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:t,props:{options:{activationConstraint:n,bypassActivationConstraint:r}}}=this;if(this.listeners.add(t.move.name,this.handleMove,{passive:!1}),this.listeners.add(t.end.name,this.handleEnd),t.cancel&&this.listeners.add(t.cancel.name,this.handleCancel),this.windowListeners.add(pn.Resize,this.handleCancel),this.windowListeners.add(pn.DragStart,Lh),this.windowListeners.add(pn.VisibilityChange,this.handleCancel),this.windowListeners.add(pn.ContextMenu,Lh),this.documentListeners.add(pn.Keydown,this.handleKeydown),n){if(r!=null&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Fh(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if($h(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(t,n){const{active:r,onPending:s}=this.props;s(r,t,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:t}=this,{onStart:n}=this.props;t&&(this.activated=!0,this.documentListeners.add(pn.Click,XC,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(pn.SelectionChange,this.removeTextSelection),n(t))}handleMove(t){var n;const{activated:r,initialCoordinates:s,props:o}=this,{onMove:i,options:{activationConstraint:a}}=o;if(!s)return;const l=(n=Ai(t))!=null?n:Mn,c=so(s,l);if(!r&&a){if($h(a)){if(a.tolerance!=null&&rc(c,a.tolerance))return this.handleCancel();if(rc(c,a.distance))return this.handleStart()}if(Fh(a)&&rc(c,a.tolerance))return this.handleCancel();this.handlePending(a,c);return}t.cancelable&&t.preventDefault(),i(l)}handleEnd(){const{onAbort:t,onEnd:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleCancel(){const{onAbort:t,onCancel:n}=this.props;this.detach(),this.activated||t(this.props.active),n()}handleKeydown(t){t.code===Ze.Esc&&this.handleCancel()}removeTextSelection(){var t;(t=this.document.getSelection())==null||t.removeAllRanges()}}const qC={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class ao extends sc{constructor(t){const{event:n}=t,r=us(n.target);super(t,qC,r)}}ao.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];const ZC={move:{name:"mousemove"},end:{name:"mouseup"}};var oc;(function(e){e[e.RightClick=2]="RightClick"})(oc||(oc={}));class QC extends sc{constructor(t){super(t,ZC,us(t.event.target))}}QC.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return n.button===oc.RightClick?!1:(r?.({event:n}),!0)}}];const ic={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class JC extends sc{constructor(t){super(t,ic)}static setup(){return window.addEventListener(ic.move.name,t,{capture:!1,passive:!1}),function(){window.removeEventListener(ic.move.name,t)};function t(){}}}JC.activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;const{touches:s}=n;return s.length>1?!1:(r?.({event:n}),!0)}}];var lo;(function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"})(lo||(lo={}));var Li;(function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"})(Li||(Li={}));function RC(e){let{acceleration:t,activator:n=lo.Pointer,canScroll:r,draggingRect:s,enabled:o,interval:i=5,order:a=Li.TreeOrder,pointerCoordinates:l,scrollableAncestors:c,scrollableAncestorRects:d,delta:u,threshold:f}=e;const g=tS({delta:u,disabled:!o}),[m,p]=gC(),y=v.useRef({x:0,y:0}),b=v.useRef({x:0,y:0}),_=v.useMemo(()=>{switch(n){case lo.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case lo.DraggableRect:return s}},[n,s,l]),x=v.useRef(null),M=v.useCallback(()=>{const k=x.current;if(!k)return;const N=y.current.x*b.current.x,S=y.current.y*b.current.y;k.scrollBy(N,S)},[]),C=v.useMemo(()=>a===Li.TreeOrder?[...c].reverse():c,[a,c]);v.useEffect(()=>{if(!o||!c.length||!_){p();return}for(const k of C){if(r?.(k)===!1)continue;const N=c.indexOf(k),S=d[N];if(!S)continue;const{direction:w,speed:E}=UC(k,S,_,t,f);for(const D of["x","y"])g[D][w[D]]||(E[D]=0,w[D]=0);if(E.x>0||E.y>0){p(),x.current=k,m(M,i),y.current=E,b.current=w;return}}y.current={x:0,y:0},b.current={x:0,y:0},p()},[t,M,r,p,o,i,JSON.stringify(_),JSON.stringify(g),m,c,C,d,JSON.stringify(f)])}const eS={x:{[It.Backward]:!1,[It.Forward]:!1},y:{[It.Backward]:!1,[It.Forward]:!1}};function tS(e){let{delta:t,disabled:n}=e;const r=Ti(t);return no(s=>{if(n||!r||!s)return eS;const o={x:Math.sign(t.x-r.x),y:Math.sign(t.y-r.y)};return{x:{[It.Backward]:s.x[It.Backward]||o.x===-1,[It.Forward]:s.x[It.Forward]||o.x===1},y:{[It.Backward]:s.y[It.Backward]||o.y===-1,[It.Forward]:s.y[It.Forward]||o.y===1}}},[n,t,r])}function nS(e,t){const n=t!=null?e.get(t):void 0,r=n?n.node.current:null;return no(s=>{var o;return t==null?null:(o=r??s)!=null?o:null},[r,t])}function rS(e,t){return v.useMemo(()=>e.reduce((n,r)=>{const{sensor:s}=r,o=s.activators.map(i=>({eventName:i.eventName,handler:t(i.handler,r)}));return[...n,...o]},[]),[e,t])}var co;(function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"})(co||(co={}));var ac;(function(e){e.Optimized="optimized"})(ac||(ac={}));const zh=new Map;function sS(e,t){let{dragging:n,dependencies:r,config:s}=t;const[o,i]=v.useState(null),{frequency:a,measure:l,strategy:c}=s,d=v.useRef(e),u=y(),f=to(u),g=v.useCallback(function(b){b===void 0&&(b=[]),!f.current&&i(_=>_===null?b:_.concat(b.filter(x=>!_.includes(x))))},[f]),m=v.useRef(null),p=no(b=>{if(u&&!n)return zh;if(!b||b===zh||d.current!==e||o!=null){const _=new Map;for(let x of e){if(!x)continue;if(o&&o.length>0&&!o.includes(x.id)&&x.rect.current){_.set(x.id,x.rect.current);continue}const M=x.node.current,C=M?new nc(l(M),M):null;x.rect.current=C,C&&_.set(x.id,C)}return _}return b},[e,o,n,u,l]);return v.useEffect(()=>{d.current=e},[e]),v.useEffect(()=>{u||g()},[n,u]),v.useEffect(()=>{o&&o.length>0&&i(null)},[JSON.stringify(o)]),v.useEffect(()=>{u||typeof a!="number"||m.current!==null||(m.current=setTimeout(()=>{g(),m.current=null},a))},[a,u,g,...r]),{droppableRects:p,measureDroppableContainers:g,measuringScheduled:o!=null};function y(){switch(c){case co.Always:return!1;case co.BeforeDragging:return n;default:return!n}}}function lc(e,t){return no(n=>e?n||(typeof t=="function"?t(e):e):null,[t,e])}function oS(e,t){return lc(e,t)}function iS(e){let{callback:t,disabled:n}=e;const r=Mi(t),s=v.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:o}=window;return new o(r)},[r,n]);return v.useEffect(()=>()=>s?.disconnect(),[s]),s}function Pi(e){let{callback:t,disabled:n}=e;const r=Mi(t),s=v.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:o}=window;return new o(r)},[n]);return v.useEffect(()=>()=>s?.disconnect(),[s]),s}function aS(e){return new nc(gs(e),e)}function Vh(e,t,n){t===void 0&&(t=aS);const[r,s]=v.useState(null);function o(){s(l=>{if(!e)return null;if(e.isConnected===!1){var c;return(c=l??n)!=null?c:null}const d=t(e);return JSON.stringify(l)===JSON.stringify(d)?l:d})}const i=iS({callback(l){if(e)for(const c of l){const{type:d,target:u}=c;if(d==="childList"&&u instanceof HTMLElement&&u.contains(e)){o();break}}}}),a=Pi({callback:o});return En(()=>{o(),e?(a?.observe(e),i?.observe(document.body,{childList:!0,subtree:!0})):(a?.disconnect(),i?.disconnect())},[e]),r}function lS(e){const t=lc(e);return Sh(e,t)}const Hh=[];function cS(e){const t=v.useRef(e),n=no(r=>e?r&&r!==Hh&&e&&t.current&&e.parentNode===t.current.parentNode?r:Ii(e):Hh,[e]);return v.useEffect(()=>{t.current=e},[e]),n}function dS(e){const[t,n]=v.useState(null),r=v.useRef(e),s=v.useCallback(o=>{const i=ec(o.target);i&&n(a=>a?(a.set(i,tc(i)),new Map(a)):null)},[]);return v.useEffect(()=>{const o=r.current;if(e!==o){i(o);const a=e.map(l=>{const c=ec(l);return c?(c.addEventListener("scroll",s,{passive:!0}),[c,tc(c)]):null}).filter(l=>l!=null);n(a.length?new Map(a):null),r.current=e}return()=>{i(e),i(o)};function i(a){a.forEach(l=>{const c=ec(l);c?.removeEventListener("scroll",s)})}},[s,e]),v.useMemo(()=>e.length?t?Array.from(t.values()).reduce((o,i)=>fs(o,i),Mn):Dh(e):Mn,[e,t])}function Uh(e,t){t===void 0&&(t=[]);const n=v.useRef(null);return v.useEffect(()=>{n.current=null},t),v.useEffect(()=>{const r=e!==Mn;r&&!n.current&&(n.current=e),!r&&n.current&&(n.current=null)},[e]),n.current?so(e,n.current):Mn}function uS(e){v.useEffect(()=>{if(!Ei)return;const t=e.map(n=>{let{sensor:r}=n;return r.setup==null?void 0:r.setup()});return()=>{for(const n of t)n?.()}},e.map(t=>{let{sensor:n}=t;return n}))}function fS(e,t){return v.useMemo(()=>e.reduce((n,r)=>{let{eventName:s,handler:o}=r;return n[s]=i=>{o(i,t)},n},{}),[e,t])}function Bh(e){return v.useMemo(()=>e?FC(e):null,[e])}const jh=[];function hS(e,t){t===void 0&&(t=gs);const[n]=e,r=Bh(n?Qt(n):null),[s,o]=v.useState(jh);function i(){o(()=>e.length?e.map(l=>Oh(l)?r:new nc(t(l),l)):jh)}const a=Pi({callback:i});return En(()=>{a?.disconnect(),i(),e.forEach(l=>a?.observe(l))},[e]),s}function Wh(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return eo(t)?t:e}function gS(e){let{measure:t}=e;const[n,r]=v.useState(null),s=v.useCallback(c=>{for(const{target:d}of c)if(eo(d)){r(u=>{const f=t(d);return u?{...u,width:f.width,height:f.height}:f});break}},[t]),o=Pi({callback:s}),i=v.useCallback(c=>{const d=Wh(c);o?.disconnect(),d&&o?.observe(d),r(d?t(d):null)},[t,o]),[a,l]=Ni(i);return v.useMemo(()=>({nodeRef:a,rect:n,setRef:l}),[n,a,l])}const pS=[{sensor:ao,options:{}},{sensor:io,options:{}}],mS={current:{}},$i={draggable:{measure:Eh},droppable:{measure:Eh,strategy:co.WhileDragging,frequency:ac.Optimized},dragOverlay:{measure:gs}};class uo extends Map{get(t){var n;return t!=null&&(n=super.get(t))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(t=>{let{disabled:n}=t;return!n})}getNodeFor(t){var n,r;return(n=(r=this.get(t))==null?void 0:r.node.current)!=null?n:void 0}}const bS={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new uo,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Di},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:$i,measureDroppableContainers:Di,windowRect:null,measuringScheduled:!1},Gh={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:Di,draggableNodes:new Map,over:null,measureDroppableContainers:Di},fo=v.createContext(Gh),Yh=v.createContext(bS);function yS(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new uo}}}function vS(e,t){switch(t.type){case Tt.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case Tt.DragMove:return e.draggable.active==null?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case Tt.DragEnd:case Tt.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case Tt.RegisterDroppable:{const{element:n}=t,{id:r}=n,s=new uo(e.droppable.containers);return s.set(r,n),{...e,droppable:{...e.droppable,containers:s}}}case Tt.SetDroppableDisabled:{const{id:n,key:r,disabled:s}=t,o=e.droppable.containers.get(n);if(!o||r!==o.key)return e;const i=new uo(e.droppable.containers);return i.set(n,{...o,disabled:s}),{...e,droppable:{...e.droppable,containers:i}}}case Tt.UnregisterDroppable:{const{id:n,key:r}=t,s=e.droppable.containers.get(n);if(!s||r!==s.key)return e;const o=new uo(e.droppable.containers);return o.delete(n),{...e,droppable:{...e.droppable,containers:o}}}default:return e}}function xS(e){let{disabled:t}=e;const{active:n,activatorEvent:r,draggableNodes:s}=v.useContext(fo),o=Ti(r),i=Ti(n?.id);return v.useEffect(()=>{if(!t&&!r&&o&&i!=null){if(!Oi(o)||document.activeElement===o.target)return;const a=s.get(i);if(!a)return;const{activatorNode:l,node:c}=a;if(!l.current&&!c.current)return;requestAnimationFrame(()=>{for(const d of[l.current,c.current]){if(!d)continue;const u=bC(d);if(u){u.focus();break}}})}},[r,t,s,i,o]),null}function Xh(e,t){let{transform:n,...r}=t;return e!=null&&e.length?e.reduce((s,o)=>o({transform:s,...r}),n):n}function wS(e){return v.useMemo(()=>({draggable:{...$i.draggable,...e?.draggable},droppable:{...$i.droppable,...e?.droppable},dragOverlay:{...$i.dragOverlay,...e?.dragOverlay}}),[e?.draggable,e?.droppable,e?.dragOverlay])}function kS(e){let{activeNode:t,measure:n,initialRect:r,config:s=!0}=e;const o=v.useRef(!1),{x:i,y:a}=typeof s=="boolean"?{x:s,y:s}:s;En(()=>{if(!i&&!a||!t){o.current=!1;return}if(o.current||!r)return;const c=t?.node.current;if(!c||c.isConnected===!1)return;const d=n(c),u=Sh(d,r);if(i||(u.x=0),a||(u.y=0),o.current=!0,Math.abs(u.x)>0||Math.abs(u.y)>0){const f=Mh(c);f&&f.scrollBy({top:u.y,left:u.x})}},[t,i,a,r,n])}const Fi=v.createContext({...Mn,scaleX:1,scaleY:1});var ur;(function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"})(ur||(ur={}));const cc=v.memo(function(t){var n,r,s,o;let{id:i,accessibility:a,autoScroll:l=!0,children:c,sensors:d=pS,collisionDetection:u=AC,measuring:f,modifiers:g,...m}=t;const p=v.useReducer(vS,void 0,yS),[y,b]=p,[_,x]=CC(),[M,C]=v.useState(ur.Uninitialized),k=M===ur.Initialized,{draggable:{active:N,nodes:S,translate:w},droppable:{containers:E}}=y,D=N!=null?S.get(N):null,P=v.useRef({initial:null,translated:null}),L=v.useMemo(()=>{var ze;return N!=null?{id:N,data:(ze=D?.data)!=null?ze:mS,rect:P}:null},[N,D]),$=v.useRef(null),[A,O]=v.useState(null),[T,z]=v.useState(null),H=to(m,Object.values(m)),j=ro("DndDescribedBy",i),G=v.useMemo(()=>E.getEnabled(),[E]),U=wS(f),{droppableRects:W,measureDroppableContainers:Y,measuringScheduled:V}=sS(G,{dragging:k,dependencies:[w.x,w.y],config:U.droppable}),X=nS(S,N),K=v.useMemo(()=>T?Ai(T):null,[T]),q=Qe(),ne=oS(X,U.draggable.measure);kS({activeNode:N!=null?S.get(N):null,config:q.layoutShiftCompensation,initialRect:ne,measure:U.draggable.measure});const ee=Vh(X,U.draggable.measure,ne),de=Vh(X?X.parentElement:null),J=v.useRef({activatorEvent:null,active:null,activeNode:X,collisionRect:null,collisions:null,droppableRects:W,draggableNodes:S,draggingNode:null,draggingNodeRect:null,droppableContainers:E,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),le=E.getNodeFor((n=J.current.over)==null?void 0:n.id),pe=gS({measure:U.dragOverlay.measure}),ae=(r=pe.nodeRef.current)!=null?r:X,ye=k?(s=pe.rect)!=null?s:ee:null,he=!!(pe.nodeRef.current&&pe.rect),ge=lS(he?null:ee),ke=Bh(ae?Qt(ae):null),Fe=cS(k?le??X:null),oe=hS(Fe),Me=Xh(g,{transform:{x:w.x-ge.x,y:w.y-ge.y,scaleX:1,scaleY:1},activatorEvent:T,active:L,activeNodeRect:ee,containerNodeRect:de,draggingNodeRect:ye,over:J.current.over,overlayNodeRect:pe.rect,scrollableAncestors:Fe,scrollableAncestorRects:oe,windowRect:ke}),Ae=K?fs(K,w):null,De=dS(Fe),Ke=Uh(De),pt=Uh(De,[ee]),mt=fs(Me,Ke),je=ye?LC(ye,Me):null,tt=L&&je?u({active:L,collisionRect:je,droppableRects:W,droppableContainers:G,pointerCoordinates:Ae}):null,We=kh(tt,"id"),[re,ue]=v.useState(null),me=he?Me:fs(Me,pt),we=DC(me,(o=re?.rect)!=null?o:null,ee),ve=v.useRef(null),Ue=v.useCallback((ze,Ne)=>{let{sensor:Ve,options:qe}=Ne;if($.current==null)return;const nt=S.get($.current);if(!nt)return;const lt=ze.nativeEvent,dt=new Ve({active:$.current,activeNode:nt,event:lt,options:qe,context:J,onAbort(vt){if(!S.get(vt))return;const{onDragAbort:xt}=H.current,Je={id:vt};xt?.(Je),_({type:"onDragAbort",event:Je})},onPending(vt,Be,xt,Je){if(!S.get(vt))return;const{onDragPending:an}=H.current,ln={id:vt,constraint:Be,initialCoordinates:xt,offset:Je};an?.(ln),_({type:"onDragPending",event:ln})},onStart(vt){const Be=$.current;if(Be==null)return;const xt=S.get(Be);if(!xt)return;const{onDragStart:Je}=H.current,In={activatorEvent:lt,active:{id:Be,data:xt.data,rect:P}};$n.unstable_batchedUpdates(()=>{Je?.(In),C(ur.Initializing),b({type:Tt.DragStart,initialCoordinates:vt,active:Be}),_({type:"onDragStart",event:In}),O(ve.current),z(lt)})},onMove(vt){b({type:Tt.DragMove,coordinates:vt})},onEnd:kn(Tt.DragEnd),onCancel:kn(Tt.DragCancel)});ve.current=dt;function kn(vt){return async function(){const{active:xt,collisions:Je,over:In,scrollAdjustedTranslate:an}=J.current;let ln=null;if(xt&&an){const{cancelDrop:qn}=H.current;ln={activatorEvent:lt,active:xt,collisions:Je,delta:an,over:In},vt===Tt.DragEnd&&typeof qn=="function"&&await Promise.resolve(qn(ln))&&(vt=Tt.DragCancel)}$.current=null,$n.unstable_batchedUpdates(()=>{b({type:vt}),C(ur.Uninitialized),ue(null),O(null),z(null),ve.current=null;const qn=vt===Tt.DragEnd?"onDragEnd":"onDragCancel";if(ln){const Wr=H.current[qn];Wr?.(ln),_({type:qn,event:ln})}})}}},[S]),_e=v.useCallback((ze,Ne)=>(Ve,qe)=>{const nt=Ve.nativeEvent,lt=S.get(qe);if($.current!==null||!lt||nt.dndKit||nt.defaultPrevented)return;const dt={active:lt};ze(Ve,Ne.options,dt)===!0&&(nt.dndKit={capturedBy:Ne.sensor},$.current=qe,Ue(Ve,Ne))},[S,Ue]),Le=rS(d,_e);uS(d),En(()=>{ee&&M===ur.Initializing&&C(ur.Initialized)},[ee,M]),v.useEffect(()=>{const{onDragMove:ze}=H.current,{active:Ne,activatorEvent:Ve,collisions:qe,over:nt}=J.current;if(!Ne||!Ve)return;const lt={active:Ne,activatorEvent:Ve,collisions:qe,delta:{x:mt.x,y:mt.y},over:nt};$n.unstable_batchedUpdates(()=>{ze?.(lt),_({type:"onDragMove",event:lt})})},[mt.x,mt.y]),v.useEffect(()=>{const{active:ze,activatorEvent:Ne,collisions:Ve,droppableContainers:qe,scrollAdjustedTranslate:nt}=J.current;if(!ze||$.current==null||!Ne||!nt)return;const{onDragOver:lt}=H.current,dt=qe.get(We),kn=dt&&dt.rect.current?{id:dt.id,rect:dt.rect.current,data:dt.data,disabled:dt.disabled}:null,vt={active:ze,activatorEvent:Ne,collisions:Ve,delta:{x:nt.x,y:nt.y},over:kn};$n.unstable_batchedUpdates(()=>{ue(kn),lt?.(vt),_({type:"onDragOver",event:vt})})},[We]),En(()=>{J.current={activatorEvent:T,active:L,activeNode:X,collisionRect:je,collisions:tt,droppableRects:W,draggableNodes:S,draggingNode:ae,draggingNodeRect:ye,droppableContainers:E,over:re,scrollableAncestors:Fe,scrollAdjustedTranslate:mt},P.current={initial:ye,translated:je}},[L,X,tt,je,S,ae,ye,W,E,re,Fe,mt]),RC({...q,delta:w,draggingRect:je,pointerCoordinates:Ae,scrollableAncestors:Fe,scrollableAncestorRects:oe});const Ce=v.useMemo(()=>({active:L,activeNode:X,activeNodeRect:ee,activatorEvent:T,collisions:tt,containerNodeRect:de,dragOverlay:pe,draggableNodes:S,droppableContainers:E,droppableRects:W,over:re,measureDroppableContainers:Y,scrollableAncestors:Fe,scrollableAncestorRects:oe,measuringConfiguration:U,measuringScheduled:V,windowRect:ke}),[L,X,ee,T,tt,de,pe,S,E,W,re,Y,Fe,oe,U,V,ke]),Ge=v.useMemo(()=>({activatorEvent:T,activators:Le,active:L,activeNodeRect:ee,ariaDescribedById:{draggable:j},dispatch:b,draggableNodes:S,over:re,measureDroppableContainers:Y}),[T,Le,L,ee,b,j,S,re,Y]);return v.createElement(yh.Provider,{value:x},v.createElement(fo.Provider,{value:Ge},v.createElement(Yh.Provider,{value:Ce},v.createElement(Fi.Provider,{value:we},c)),v.createElement(xS,{disabled:a?.restoreFocus===!1})),v.createElement(EC,{...a,hiddenTextDescribedById:j}));function Qe(){const ze=A?.autoScrollEnabled===!1,Ne=typeof l=="object"?l.enabled===!1:l===!1,Ve=k&&!ze&&!Ne;return typeof l=="object"?{...l,enabled:Ve}:{enabled:Ve}}}),CS=v.createContext(null),Kh="button",SS="Draggable";function _S(e){let{id:t,data:n,disabled:r=!1,attributes:s}=e;const o=ro(SS),{activators:i,activatorEvent:a,active:l,activeNodeRect:c,ariaDescribedById:d,draggableNodes:u,over:f}=v.useContext(fo),{role:g=Kh,roleDescription:m="draggable",tabIndex:p=0}=s??{},y=l?.id===t,b=v.useContext(y?Fi:CS),[_,x]=Ni(),[M,C]=Ni(),k=fS(i,t),N=to(n);En(()=>(u.set(t,{id:t,key:o,node:_,activatorNode:M,data:N}),()=>{const w=u.get(t);w&&w.key===o&&u.delete(t)}),[u,t]);const S=v.useMemo(()=>({role:g,tabIndex:p,"aria-disabled":r,"aria-pressed":y&&g===Kh?!0:void 0,"aria-roledescription":m,"aria-describedby":d.draggable}),[r,g,p,y,m,d.draggable]);return{active:l,activatorEvent:a,activeNodeRect:c,attributes:S,isDragging:y,listeners:r?void 0:k,node:_,over:f,setNodeRef:x,setActivatorNodeRef:C,transform:b}}function qh(){return v.useContext(Yh)}const ES="Droppable",MS={timeout:25};function NS(e){let{data:t,disabled:n=!1,id:r,resizeObserverConfig:s}=e;const o=ro(ES),{active:i,dispatch:a,over:l,measureDroppableContainers:c}=v.useContext(fo),d=v.useRef({disabled:n}),u=v.useRef(!1),f=v.useRef(null),g=v.useRef(null),{disabled:m,updateMeasurementsFor:p,timeout:y}={...MS,...s},b=to(p??r),_=v.useCallback(()=>{if(!u.current){u.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{c(Array.isArray(b.current)?b.current:[b.current]),g.current=null},y)},[y]),x=Pi({callback:_,disabled:m||!i}),M=v.useCallback((S,w)=>{x&&(w&&(x.unobserve(w),u.current=!1),S&&x.observe(S))},[x]),[C,k]=Ni(M),N=to(t);return v.useEffect(()=>{!x||!C.current||(x.disconnect(),u.current=!1,x.observe(C.current))},[C,x]),v.useEffect(()=>(a({type:Tt.RegisterDroppable,element:{id:r,key:o,disabled:n,node:C,rect:f,data:N}}),()=>a({type:Tt.UnregisterDroppable,key:o,id:r})),[r]),v.useEffect(()=>{n!==d.current.disabled&&(a({type:Tt.SetDroppableDisabled,id:r,key:o,disabled:n}),d.current.disabled=n)},[r,o,n,a]),{active:i,rect:f,isOver:l?.id===r,node:C,over:l,setNodeRef:k}}function TS(e){let{animation:t,children:n}=e;const[r,s]=v.useState(null),[o,i]=v.useState(null),a=Ti(n);return!n&&!r&&a&&s(a),En(()=>{if(!o)return;const l=r?.key,c=r?.props.id;if(l==null||c==null){s(null);return}Promise.resolve(t(c,o)).then(()=>{s(null)})},[t,r,o]),v.createElement(v.Fragment,null,n,r?v.cloneElement(r,{ref:i}):null)}const OS={x:0,y:0,scaleX:1,scaleY:1};function AS(e){let{children:t}=e;return v.createElement(fo.Provider,{value:Gh},v.createElement(Fi.Provider,{value:OS},t))}const DS={position:"fixed",touchAction:"none"},IS=e=>Oi(e)?"transform 250ms ease":void 0,LS=v.forwardRef((e,t)=>{let{as:n,activatorEvent:r,adjustScale:s,children:o,className:i,rect:a,style:l,transform:c,transition:d=IS}=e;if(!a)return null;const u=s?c:{...c,scaleX:1,scaleY:1},f={...DS,width:a.width,height:a.height,top:a.top,left:a.left,transform:jn.Transform.toString(u),transformOrigin:s&&r?MC(r,a):void 0,transition:typeof d=="function"?d(r):d,...l};return v.createElement(n,{className:i,style:f,ref:t},o)}),PS={duration:250,easing:"ease",keyframes:e=>{let{transform:{initial:t,final:n}}=e;return[{transform:jn.Transform.toString(t)},{transform:jn.Transform.toString(n)}]},sideEffects:(e=>t=>{let{active:n,dragOverlay:r}=t;const s={},{styles:o,className:i}=e;if(o!=null&&o.active)for(const[a,l]of Object.entries(o.active))l!==void 0&&(s[a]=n.node.style.getPropertyValue(a),n.node.style.setProperty(a,l));if(o!=null&&o.dragOverlay)for(const[a,l]of Object.entries(o.dragOverlay))l!==void 0&&r.node.style.setProperty(a,l);return i!=null&&i.active&&n.node.classList.add(i.active),i!=null&&i.dragOverlay&&r.node.classList.add(i.dragOverlay),function(){for(const[l,c]of Object.entries(s))n.node.style.setProperty(l,c);i!=null&&i.active&&n.node.classList.remove(i.active)}})({styles:{active:{opacity:"0"}}})};function $S(e){let{config:t,draggableNodes:n,droppableContainers:r,measuringConfiguration:s}=e;return Mi((o,i)=>{if(t===null)return;const a=n.get(o);if(!a)return;const l=a.node.current;if(!l)return;const c=Wh(i);if(!c)return;const{transform:d}=Qt(i).getComputedStyle(i),u=_h(d);if(!u)return;const f=typeof t=="function"?t:FS(t);return Ih(l,s.draggable.measure),f({active:{id:o,data:a.data,node:l,rect:s.draggable.measure(l)},draggableNodes:n,dragOverlay:{node:i,rect:s.dragOverlay.measure(c)},droppableContainers:r,measuringConfiguration:s,transform:u})})}function FS(e){const{duration:t,easing:n,sideEffects:r,keyframes:s}={...PS,...e};return o=>{let{active:i,dragOverlay:a,transform:l,...c}=o;if(!t)return;const d={x:a.rect.left-i.rect.left,y:a.rect.top-i.rect.top},u={scaleX:l.scaleX!==1?i.rect.width*l.scaleX/a.rect.width:1,scaleY:l.scaleY!==1?i.rect.height*l.scaleY/a.rect.height:1},f={x:l.x-d.x,y:l.y-d.y,...u},g=s({...c,active:i,dragOverlay:a,transform:{initial:l,final:f}}),[m]=g,p=g[g.length-1];if(JSON.stringify(m)===JSON.stringify(p))return;const y=r?.({active:i,dragOverlay:a,...c}),b=a.node.animate(g,{duration:t,easing:n,fill:"forwards"});return new Promise(_=>{b.onfinish=()=>{y?.(),_()}})}}let Zh=0;function zS(e){return v.useMemo(()=>{if(e!=null)return Zh++,Zh},[e])}const VS=v.memo(e=>{let{adjustScale:t=!1,children:n,dropAnimation:r,style:s,transition:o,modifiers:i,wrapperElement:a="div",className:l,zIndex:c=999}=e;const{activatorEvent:d,active:u,activeNodeRect:f,containerNodeRect:g,draggableNodes:m,droppableContainers:p,dragOverlay:y,over:b,measuringConfiguration:_,scrollableAncestors:x,scrollableAncestorRects:M,windowRect:C}=qh(),k=v.useContext(Fi),N=zS(u?.id),S=Xh(i,{activatorEvent:d,active:u,activeNodeRect:f,containerNodeRect:g,draggingNodeRect:y.rect,over:b,overlayNodeRect:y.rect,scrollableAncestors:x,scrollableAncestorRects:M,transform:k,windowRect:C}),w=lc(f),E=$S({config:r,draggableNodes:m,droppableContainers:p,measuringConfiguration:_}),D=w?y.setRef:void 0;return v.createElement(AS,null,v.createElement(TS,{animation:E},u&&N?v.createElement(LS,{key:N,id:u.id,ref:D,as:a,activatorEvent:d,adjustScale:t,className:l,transition:o,rect:w,style:{zIndex:c,...s},transform:S},n):null))});function ps(e,t,n){const r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function HS(e,t){return e.reduce((n,r,s)=>{const o=t.get(r);return o&&(n[s]=o),n},Array(e.length))}function zi(e){return e!==null&&e>=0}function US(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n{let{rects:t,activeIndex:n,overIndex:r,index:s}=e;const o=ps(t,r,n),i=t[s],a=o[s];return!a||!i?null:{x:a.left-i.left,y:a.top-i.top,scaleX:a.width/i.width,scaleY:a.height/i.height}},Hi={scaleX:1,scaleY:1},dc=e=>{var t;let{activeIndex:n,activeNodeRect:r,index:s,rects:o,overIndex:i}=e;const a=(t=o[n])!=null?t:r;if(!a)return null;if(s===n){const c=o[i];return c?{x:0,y:nn&&s<=i?{x:0,y:-a.height-l,...Hi}:s=i?{x:0,y:a.height+l,...Hi}:{x:0,y:0,...Hi}};function jS(e,t,n){const r=e[t],s=e[t-1],o=e[t+1];return r?nr.map(k=>typeof k=="object"&&"id"in k?k.id:k),[r]),m=i!=null,p=i?g.indexOf(i.id):-1,y=c?g.indexOf(c.id):-1,b=v.useRef(g),_=!US(g,b.current),x=y!==-1&&p===-1||_,M=BS(o);En(()=>{_&&m&&d(g)},[_,g,m,d]),v.useEffect(()=>{b.current=g},[g]);const C=v.useMemo(()=>({activeIndex:p,containerId:u,disabled:M,disableTransforms:x,items:g,overIndex:y,useDragOverlay:f,sortedRects:HS(g,l),strategy:s}),[p,u,M.draggable,M.droppable,x,g,y,l,f,s]);return v.createElement(Jh.Provider,{value:C},t)}const Rh=e=>{let{id:t,items:n,activeIndex:r,overIndex:s}=e;return ps(n,r,s).indexOf(t)},eg=e=>{let{containerId:t,isSorting:n,wasDragging:r,index:s,items:o,newIndex:i,previousItems:a,previousContainerId:l,transition:c}=e;return!c||!r||a!==o&&s===i?!1:n?!0:i!==s&&t===l},WS={duration:200,easing:"ease"},tg="transform",GS=jn.Transition.toString({property:tg,duration:0,easing:"linear"}),YS={roleDescription:"sortable"};function XS(e){let{disabled:t,index:n,node:r,rect:s}=e;const[o,i]=v.useState(null),a=v.useRef(n);return En(()=>{if(!t&&n!==a.current&&r.current){const l=s.current;if(l){const c=gs(r.current,{ignoreTransform:!0}),d={x:l.left-c.left,y:l.top-c.top,scaleX:l.width/c.width,scaleY:l.height/c.height};(d.x||d.y)&&i(d)}}n!==a.current&&(a.current=n)},[t,n,r,s]),v.useEffect(()=>{o&&i(null)},[o]),o}function Bi(e){let{animateLayoutChanges:t=eg,attributes:n,disabled:r,data:s,getNewIndex:o=Rh,id:i,strategy:a,resizeObserverConfig:l,transition:c=WS}=e;const{items:d,containerId:u,activeIndex:f,disabled:g,disableTransforms:m,sortedRects:p,overIndex:y,useDragOverlay:b,strategy:_}=v.useContext(Jh),x=KS(r,g),M=d.indexOf(i),C=v.useMemo(()=>({sortable:{containerId:u,index:M,items:d},...s}),[u,s,M,d]),k=v.useMemo(()=>d.slice(d.indexOf(i)),[d,i]),{rect:N,node:S,isOver:w,setNodeRef:E}=NS({id:i,data:C,disabled:x.droppable,resizeObserverConfig:{updateMeasurementsFor:k,...l}}),{active:D,activatorEvent:P,activeNodeRect:L,attributes:$,setNodeRef:A,listeners:O,isDragging:T,over:z,setActivatorNodeRef:H,transform:j}=_S({id:i,data:C,attributes:{...YS,...n},disabled:x.draggable}),G=hC(E,A),U=!!D,W=U&&!m&&zi(f)&&zi(y),Y=!b&&T,V=Y&&W?j:null,K=W?V??(a??_)({rects:p,activeNodeRect:L,activeIndex:f,overIndex:y,index:M}):null,q=zi(f)&&zi(y)?o({id:i,items:d,activeIndex:f,overIndex:y}):M,ne=D?.id,ee=v.useRef({activeId:ne,items:d,newIndex:q,containerId:u}),de=d!==ee.current.items,J=t({active:D,containerId:u,isDragging:T,isSorting:U,id:i,index:M,items:d,newIndex:ee.current.newIndex,previousItems:ee.current.items,previousContainerId:ee.current.containerId,transition:c,wasDragging:ee.current.activeId!=null}),le=XS({disabled:!J,index:M,node:S,rect:N});return v.useEffect(()=>{U&&ee.current.newIndex!==q&&(ee.current.newIndex=q),u!==ee.current.containerId&&(ee.current.containerId=u),d!==ee.current.items&&(ee.current.items=d)},[U,q,u,d]),v.useEffect(()=>{if(ne===ee.current.activeId)return;if(ne!=null&&ee.current.activeId==null){ee.current.activeId=ne;return}const ae=setTimeout(()=>{ee.current.activeId=ne},50);return()=>clearTimeout(ae)},[ne]),{active:D,activeIndex:f,attributes:$,data:C,rect:N,index:M,newIndex:q,items:d,isOver:w,isSorting:U,isDragging:T,listeners:O,node:S,overIndex:y,over:z,setNodeRef:G,setActivatorNodeRef:H,setDroppableNodeRef:E,setDraggableNodeRef:A,transform:le??K,transition:pe()};function pe(){if(le||de&&ee.current.newIndex===M)return GS;if(!(Y&&!Oi(P)||!c)&&(U||J))return jn.Transition.toString({...c,property:tg})}}function KS(e,t){var n,r;return typeof e=="boolean"?{draggable:e,droppable:!1}:{draggable:(n=e?.draggable)!=null?n:t.draggable,droppable:(r=e?.droppable)!=null?r:t.droppable}}function ho(e){if(!e)return!1;const t=e.data.current;return!!(t&&"sortable"in t&&typeof t.sortable=="object"&&"containerId"in t.sortable&&"items"in t.sortable&&"index"in t.sortable)}const qS=[Ze.Down,Ze.Right,Ze.Up,Ze.Left],ji=(e,t)=>{let{context:{active:n,collisionRect:r,droppableRects:s,droppableContainers:o,over:i,scrollableAncestors:a}}=t;if(qS.includes(e.code)){if(e.preventDefault(),!n||!r)return;const l=[];o.getEnabled().forEach(u=>{if(!u||u!=null&&u.disabled)return;const f=s.get(u.id);if(f)switch(e.code){case Ze.Down:r.topf.top&&l.push(u);break;case Ze.Left:r.left>f.left&&l.push(u);break;case Ze.Right:r.left1&&(d=c[1].id),d!=null){const u=o.get(n.id),f=o.get(d),g=f?s.get(f.id):null,m=f?.node.current;if(m&&g&&u&&f){const y=Ii(m).some((k,N)=>a[N]!==k),b=ng(u,f),_=ZS(u,f),x=y||!b?{x:0,y:0}:{x:_?r.width-g.width:0,y:_?r.height-g.height:0},M={x:g.left,y:g.top};return x.x&&x.y?M:so(M,x)}}}};function ng(e,t){return!ho(e)||!ho(t)?!1:e.data.current.sortable.containerId===t.data.current.sortable.containerId}function ZS(e,t){return!ho(e)||!ho(t)||!ng(e,t)?!1:e.data.current.sortable.indexwindow.G7Core,rg=({item:e,isSelected:t=!1,isExpanded:n=!1,level:r=0,onClick:s,onToggle:o,onExpandToggle:i,className:a="",selectedClassName:l="bg-blue-50 dark:bg-blue-900/30 border-blue-500",defaultClassName:c="bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600",dragHandleClassName:d="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300",iconContainerClassName:u="bg-gray-100 dark:bg-gray-700",contentClassName:f="",enableDrag:g=!0,toggleDisabled:m=!1})=>{const{attributes:p,listeners:y,setNodeRef:b,transform:_,transition:x,isDragging:M}=Bi({id:e.id}),C={transform:jn.Transform.toString(_),transition:x,opacity:M?.5:1},k=r*24+12,S=`flex items-center gap-2 cursor-pointer transition-colors ${t?l:c}`,w=JS()?.style?.mergeClasses?.(S,a)??`${S} ${a}`.trim();return h.jsxs(I,{ref:b,style:C,className:w,onClick:s,children:[g&&h.jsx(I,{...p,...y,className:`flex-shrink-0 cursor-grab active:cursor-grabbing p-1 ${d}`,style:{paddingLeft:`${k}px`},children:h.jsx(se,{name:"grip-vertical",className:"w-4 h-4"})}),!g&&r>0&&h.jsx(I,{style:{paddingLeft:`${k}px`}}),g&&h.jsx(h.Fragment,{children:e.hasChildren?h.jsx(ie,{type:"button",onClick:E=>{E.stopPropagation(),i?.()},className:`flex-shrink-0 p-1 ${d}`,children:h.jsx(se,{name:n?"chevron-down":"chevron-right",className:"w-4 h-4"})}):h.jsx(I,{className:"flex-shrink-0 w-6"})}),h.jsx(I,{className:`flex-shrink-0 w-8 h-8 flex items-center justify-center rounded ${u}`,children:h.jsx(se,{name:e.icon||"file",className:"w-4 h-4 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:`flex-1 min-w-0 ${f}`,children:[h.jsxs(I,{className:"flex items-center gap-1",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-900 dark:text-white truncate",children:e.name}),e.isModuleMenu&&h.jsx(se,{name:"cube",className:"w-2 h-2 text-gray-300 dark:text-gray-500 ml-2",title:e.moduleName})]}),h.jsx(Q,{className:"text-xs text-gray-500 dark:text-gray-400 truncate block",children:e.url})]}),h.jsx(I,{className:"flex-shrink-0",onClick:E=>E.stopPropagation(),children:h.jsx(Ja,{checked:e.isActive,onChange:()=>o?.(new MouseEvent("click")),size:"sm",disabled:m})})]})},RS=()=>window.G7Core;function sg(e,t,n=0,r=null){const s=[];for(const o of e)s.push({item:o,depth:n,parentId:r}),o.children?.length>0&&t.has(o.id)&&s.push(...sg(o.children,t,n+1,o.id));return s}function uc(e,t){for(const n of e){if(n.id===t)return n;if(n.children){const r=uc(n.children,t);if(r)return r}}return null}function og(e){const t=[];if(e.children)for(const n of e.children)t.push(n.id),t.push(...og(n));return t}function ig(e,t){return e.filter(n=>n.id!==t).map(n=>n.children?.length>0?{...n,children:ig(n.children,t)}:n)}function ag(e,t,n,r){if(n===null){const s=[...e];return s.splice(r,0,t),s}return e.map(s=>{if(s.id===n){const o=[...s.children||[]];return o.splice(r,0,t),{...s,children:o}}return s.children?.length>0?{...s,children:ag(s.children,t,n,r)}:s})}function lg(e,t,n,r){if(t===null){const s=e.findIndex(i=>i.id===n),o=e.findIndex(i=>i.id===r);return s===-1||o===-1?e:ps(e,s,o)}return e.map(s=>{if(s.id===t&&s.children){const o=s.children.findIndex(a=>a.id===n),i=s.children.findIndex(a=>a.id===r);return o===-1||i===-1?s:{...s,children:ps(s.children,o,i)}}return s.children?.length>0?{...s,children:lg(s.children,t,n,r)}:s})}function cg(e){const t=e.map((s,o)=>({id:s.id,order:o+1})),n={},r=s=>{s.forEach(o=>{o.children?.length>0&&(n[o.id]=o.children.map((i,a)=>({id:i.id,order:a+1})),r(o.children))})};return r(e),{parent_menus:t,child_menus:n}}const e_=({items:e=[],selectedId:t,onSelect:n,onOrderChange:r,onToggleStatus:s,title:o,className:i="",listClassName:a="",childrenContainerClassName:l="bg-gray-50 dark:bg-gray-900/50",itemClassName:c="py-2.5 px-3 mx-4 my-2 rounded-lg border",itemSelectedClassName:d,itemDefaultClassName:u,titleClassName:f="px-4 py-3 border-b border-gray-200 dark:border-gray-700",enableDrag:g=!0,toggleDisabled:m=!1,id:p,editorAttrs:y})=>{const[b,_]=v.useState(new Set),[x,M]=v.useState(null),[C,k]=v.useState(e),N=v.useRef(!1),S=v.useRef(""),w=v.useRef(""),E=v.useCallback(W=>JSON.stringify(W),[]),D=v.useCallback(W=>W.map(Y=>{const V=Y.children?.map(X=>X.id).join(",")||"";return`${Y.id}:${V}`}).join("|"),[]);v.useEffect(()=>{const W=D(e),Y=E(e);if(N.current){const V=D(C);(W===V||W!==S.current)&&(N.current=!1,S.current=W,w.current=Y,(W!==V||Y!==E(C))&&k(e));return}Y!==w.current&&(S.current=W,w.current=Y,k(e))},[e,D,E,C]);const P=Jl(hs(ao,{activationConstraint:{distance:8}}),hs(io,{coordinateGetter:ji})),L=v.useCallback(W=>{if(typeof W=="string")return W;const V=RS()?.locale?.current?.()||"ko";return W[V]||W.en||Object.values(W)[0]||""},[]),$=v.useCallback(W=>{_(Y=>{const V=new Set(Y);return V.has(W)?V.delete(W):V.add(W),V})},[]),A=v.useCallback(W=>{n?.(W)},[n]),O=v.useCallback((W,Y,V)=>{V.stopPropagation(),s?.(W,Y)},[s]),T=W=>{M(W.active.id)},z=W=>{const{active:Y,over:V}=W;if(M(null),!V||Y.id===V.id)return;const X=j.find(q=>q.item.id===Number(Y.id)),K=j.find(q=>q.item.id===Number(V.id));if(!(!X||!K))if(X.parentId===K.parentId){const q=lg(C,X.parentId,Number(Y.id),Number(V.id));k(q),N.current=!0,r?.(cg(q))}else{const q=X.item;if(og(q).includes(Number(V.id)))return;let ee=ig(C,q.id);const de=j.findIndex(ge=>ge.item.id===Number(Y.id)),J=j.findIndex(ge=>ge.item.id===Number(V.id)),le=de>J,pe=K.parentId;let ae;if(pe===null)ae=ee.findIndex(ge=>ge.id===Number(V.id)),ae===-1?ae=ee.length:le||(ae+=1);else{const ke=uc(ee,pe)?.children||[];ae=ke.findIndex(Fe=>Fe.id===Number(V.id)),ae===-1?ae=ke.length:le||(ae+=1)}const ye={...q,parent_id:pe,children:q.children||[]};ee=ag(ee,ye,pe,ae),k(ee),N.current=!0;const he=cg(ee);he.moved_items=[{id:q.id,new_parent_id:pe}],r?.(he)}},H=W=>({id:W.id,name:L(W.name),slug:W.slug,url:W.url,icon:W.icon,isActive:W.is_active,isModuleMenu:!!W.module_id,moduleName:W.module?L(W.module.name):void 0,hasChildren:W.children&&W.children.length>0}),j=v.useMemo(()=>sg(C,b),[C,b]),G=v.useMemo(()=>j.map(W=>W.item.id),[j]),U=x?uc(C,Number(x)):null;return h.jsxs(I,{className:i,id:p,...y,children:[o&&h.jsx(I,{className:f,children:h.jsx(Xr,{className:"text-sm font-medium text-gray-900 dark:text-white",children:o})}),h.jsxs(cc,{sensors:P,collisionDetection:Rl,onDragStart:T,onDragEnd:z,children:[h.jsx(Ui,{items:G,strategy:dc,children:h.jsx(I,{className:a,children:j.map(W=>h.jsx(I,{className:W.depth>0?l:"",children:h.jsx(rg,{item:H(W.item),isSelected:t===W.item.id,isExpanded:b.has(W.item.id),level:W.depth,onClick:()=>A(W.item),onToggle:Y=>O(W.item.id,W.item.is_active,Y),onExpandToggle:()=>$(W.item.id),className:c,selectedClassName:d,defaultClassName:u,enableDrag:g,toggleDisabled:m})},W.item.id))})}),h.jsx(VS,{children:U?h.jsx(I,{className:"bg-white dark:bg-gray-800 shadow-lg rounded-lg border border-blue-500 p-3",children:h.jsx(I,{className:"flex items-center gap-2",children:h.jsx(I,{className:"text-sm font-medium text-gray-900 dark:text-white",children:L(U.name)})})}):null})]})]})},t_=[{value:"LayoutDashboard",label:"LayoutDashboard",faIcon:"tachometer-alt"},{value:"Settings",label:"Settings",faIcon:"cog"},{value:"Menu",label:"Menu",faIcon:"bars"},{value:"Users",label:"Users",faIcon:"users"},{value:"Package",label:"Package",faIcon:"cube"},{value:"Puzzle",label:"Puzzle",faIcon:"puzzle-piece"},{value:"FileText",label:"FileText",faIcon:"file-alt"},{value:"ShoppingCart",label:"ShoppingCart",faIcon:"shopping-cart"},{value:"MessageSquare",label:"MessageSquare",faIcon:"comment"},{value:"Home",label:"Home",faIcon:"home"},{value:"User",label:"User",faIcon:"user"},{value:"Search",label:"Search",faIcon:"search"},{value:"Bell",label:"Bell",faIcon:"bell"},{value:"Mail",label:"Mail",faIcon:"envelope"},{value:"Calendar",label:"Calendar",faIcon:"calendar"},{value:"Chart",label:"Chart",faIcon:"chart-bar"},{value:"Database",label:"Database",faIcon:"database"},{value:"Lock",label:"Lock",faIcon:"lock"},{value:"Globe",label:"Globe",faIcon:"globe"},{value:"Image",label:"Image",faIcon:"image"}],n_=({value:e,onChange:t,options:n=t_,placeholder:r,searchPlaceholder:s,noResultsText:o,className:i="",disabled:a=!1,name:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(!1),[g,m]=v.useState(""),p=v.useRef(null),y=n.find(x=>{if(!e)return!1;if(e.includes("fa-")){const M=e.replace(/^(fas|far|fab|fal|fad)\s+fa-/,"");return x.faIcon===M||x.faIcon===e}return x.value===e||x.faIcon===e}),b=n.filter(x=>x.label.toLowerCase().includes(g.toLowerCase()));v.useEffect(()=>{const x=M=>{p.current&&!p.current.contains(M.target)&&(f(!1),m(""))};if(u)return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[u]);const _=x=>{const M=`fas fa-${x.faIcon}`;t?.(M),f(!1),m("")};return h.jsxs(I,{ref:p,className:`relative ${i}`,id:c,...d,children:[h.jsxs(ie,{type:"button",onClick:()=>!a&&f(!u),disabled:a,className:` + `},iC={onDragStart(t){let{active:e}=t;return"Picked up draggable item "+e.id+"."},onDragOver(t){let{active:e,over:n}=t;return n?"Draggable item "+e.id+" was moved over droppable area "+n.id+".":"Draggable item "+e.id+" is no longer over a droppable area."},onDragEnd(t){let{active:e,over:n}=t;return n?"Draggable item "+e.id+" was dropped over droppable area "+n.id:"Draggable item "+e.id+" was dropped."},onDragCancel(t){let{active:e}=t;return"Dragging was cancelled. Draggable item "+e.id+" was dropped."}};function aC(t){let{announcements:e=iC,container:n,hiddenTextDescribedById:r,screenReaderInstructions:s=oC}=t;const{announce:o,announcement:i}=nC(),a=js("DndLiveRegion"),[l,c]=v.useState(!1);if(v.useEffect(()=>{c(!0)},[]),rC(v.useMemo(()=>({onDragStart(u){let{active:f}=u;o(e.onDragStart({active:f}))},onDragMove(u){let{active:f,over:g}=u;e.onDragMove&&o(e.onDragMove({active:f,over:g}))},onDragOver(u){let{active:f,over:g}=u;o(e.onDragOver({active:f,over:g}))},onDragEnd(u){let{active:f,over:g}=u;o(e.onDragEnd({active:f,over:g}))},onDragCancel(u){let{active:f,over:g}=u;o(e.onDragCancel({active:f,over:g}))}}),[o,e])),!l)return null;const d=v.createElement(v.Fragment,null,v.createElement(eC,{id:r,value:s.draggable}),v.createElement(tC,{id:a,announcement:i}));return n?Tn.createPortal(d,n):d}var _t;(function(t){t.DragStart="dragStart",t.DragMove="dragMove",t.DragEnd="dragEnd",t.DragCancel="dragCancel",t.DragOver="dragOver",t.RegisterDroppable="registerDroppable",t.SetDroppableDisabled="setDroppableDisabled",t.UnregisterDroppable="unregisterDroppable"})(_t||(_t={}));function Ci(){}function qr(t,e){return v.useMemo(()=>({sensor:t,options:e??{}}),[t,e])}function Wl(){for(var t=arguments.length,e=new Array(t),n=0;n[...e].filter(r=>r!=null),[...e])}const kn=Object.freeze({x:0,y:0});function ah(t,e){return Math.sqrt(Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2))}function lC(t,e){const n=ki(t);if(!n)return"0 0";const r={x:(n.x-e.left)/e.width*100,y:(n.y-e.top)/e.height*100};return r.x+"% "+r.y+"%"}function lh(t,e){let{data:{value:n}}=t,{data:{value:r}}=e;return n-r}function cC(t,e){let{data:{value:n}}=t,{data:{value:r}}=e;return r-n}function ch(t){let{left:e,top:n,height:r,width:s}=t;return[{x:e,y:n},{x:e+s,y:n},{x:e,y:n+r},{x:e+s,y:n+r}]}function dh(t,e){if(!t||t.length===0)return null;const[n]=t;return n[e]}function uh(t,e,n){return e===void 0&&(e=t.left),n===void 0&&(n=t.top),{x:e+t.width*.5,y:n+t.height*.5}}const Gl=t=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=t;const s=uh(e,e.left,e.top),o=[];for(const i of r){const{id:a}=i,l=n.get(a);if(l){const c=ah(uh(l),s);o.push({id:a,data:{droppableContainer:i,value:c}})}}return o.sort(lh)},dC=t=>{let{collisionRect:e,droppableRects:n,droppableContainers:r}=t;const s=ch(e),o=[];for(const i of r){const{id:a}=i,l=n.get(a);if(l){const c=ch(l),d=s.reduce((f,g,m)=>f+ah(c[m],g),0),u=Number((d/4).toFixed(4));o.push({id:a,data:{droppableContainer:i,value:u}})}}return o.sort(lh)};function uC(t,e){const n=Math.max(e.top,t.top),r=Math.max(e.left,t.left),s=Math.min(e.left+e.width,t.left+t.width),o=Math.min(e.top+e.height,t.top+t.height),i=s-r,a=o-n;if(r{let{collisionRect:e,droppableRects:n,droppableContainers:r}=t;const s=[];for(const o of r){const{id:i}=o,a=n.get(i);if(a){const l=uC(a,e);l>0&&s.push({id:i,data:{droppableContainer:o,value:l}})}}return s.sort(cC)};function hC(t,e,n){return{...t,scaleX:e&&n?e.width/n.width:1,scaleY:e&&n?e.height/n.height:1}}function fh(t,e){return t&&e?{x:t.left-e.left,y:t.top-e.top}:kn}function gC(t){return function(n){for(var r=arguments.length,s=new Array(r>1?r-1:0),o=1;o({...i,top:i.top+t*a.y,bottom:i.bottom+t*a.y,left:i.left+t*a.x,right:i.right+t*a.x}),{...n})}}const pC=gC(1);function hh(t){if(t.startsWith("matrix3d(")){const e=t.slice(9,-1).split(/, /);return{x:+e[12],y:+e[13],scaleX:+e[0],scaleY:+e[5]}}else if(t.startsWith("matrix(")){const e=t.slice(7,-1).split(/, /);return{x:+e[4],y:+e[5],scaleX:+e[0],scaleY:+e[3]}}return null}function mC(t,e,n){const r=hh(e);if(!r)return t;const{scaleX:s,scaleY:o,x:i,y:a}=r,l=t.left-i-(1-s)*parseFloat(n),c=t.top-a-(1-o)*parseFloat(n.slice(n.indexOf(" ")+1)),d=s?t.width/s:t.width,u=o?t.height/o:t.height;return{width:d,height:u,top:c,right:l+d,bottom:c+u,left:l}}const bC={ignoreTransform:!1};function Zr(t,e){e===void 0&&(e=bC);let n=t.getBoundingClientRect();if(e.ignoreTransform){const{transform:c,transformOrigin:d}=Kt(t).getComputedStyle(t);c&&(n=mC(n,c,d))}const{top:r,left:s,width:o,height:i,bottom:a,right:l}=n;return{top:r,left:s,width:o,height:i,bottom:a,right:l}}function gh(t){return Zr(t,{ignoreTransform:!0})}function yC(t){const e=t.innerWidth,n=t.innerHeight;return{top:0,left:0,right:e,bottom:n,width:e,height:n}}function vC(t,e){return e===void 0&&(e=Kt(t).getComputedStyle(t)),e.position==="fixed"}function xC(t,e){e===void 0&&(e=Kt(t).getComputedStyle(t));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(s=>{const o=e[s];return typeof o=="string"?n.test(o):!1})}function Si(t,e){const n=[];function r(s){if(e!=null&&n.length>=e||!s)return n;if(Bl(s)&&s.scrollingElement!=null&&!n.includes(s.scrollingElement))return n.push(s.scrollingElement),n;if(!Hs(s)||rh(s)||n.includes(s))return n;const o=Kt(t).getComputedStyle(s);return s!==t&&xC(s,o)&&n.push(s),vC(s,o)?n:r(s.parentNode)}return t?r(t):n}function ph(t){const[e]=Si(t,1);return e??null}function Yl(t){return!bi||!t?null:Yr(t)?t:Ul(t)?Bl(t)||t===Xr(t).scrollingElement?window:Hs(t)?t:null:null}function mh(t){return Yr(t)?t.scrollX:t.scrollLeft}function bh(t){return Yr(t)?t.scrollY:t.scrollTop}function Xl(t){return{x:mh(t),y:bh(t)}}var Ot;(function(t){t[t.Forward=1]="Forward",t[t.Backward=-1]="Backward"})(Ot||(Ot={}));function yh(t){return!bi||!t?!1:t===document.scrollingElement}function vh(t){const e={x:0,y:0},n=yh(t)?{height:window.innerHeight,width:window.innerWidth}:{height:t.clientHeight,width:t.clientWidth},r={x:t.scrollWidth-n.width,y:t.scrollHeight-n.height},s=t.scrollTop<=e.y,o=t.scrollLeft<=e.x,i=t.scrollTop>=r.y,a=t.scrollLeft>=r.x;return{isTop:s,isLeft:o,isBottom:i,isRight:a,maxScroll:r,minScroll:e}}const wC={x:.2,y:.2};function kC(t,e,n,r,s){let{top:o,left:i,right:a,bottom:l}=n;r===void 0&&(r=10),s===void 0&&(s=wC);const{isTop:c,isBottom:d,isLeft:u,isRight:f}=vh(t),g={x:0,y:0},m={x:0,y:0},p={height:e.height*s.y,width:e.width*s.x};return!c&&o<=e.top+p.height?(g.y=Ot.Backward,m.y=r*Math.abs((e.top+p.height-o)/p.height)):!d&&l>=e.bottom-p.height&&(g.y=Ot.Forward,m.y=r*Math.abs((e.bottom-p.height-l)/p.height)),!f&&a>=e.right-p.width?(g.x=Ot.Forward,m.x=r*Math.abs((e.right-p.width-a)/p.width)):!u&&i<=e.left+p.width&&(g.x=Ot.Backward,m.x=r*Math.abs((e.left+p.width-i)/p.width)),{direction:g,speed:m}}function CC(t){if(t===document.scrollingElement){const{innerWidth:o,innerHeight:i}=window;return{top:0,left:0,right:o,bottom:i,width:o,height:i}}const{top:e,left:n,right:r,bottom:s}=t.getBoundingClientRect();return{top:e,left:n,right:r,bottom:s,width:t.clientWidth,height:t.clientHeight}}function xh(t){return t.reduce((e,n)=>Kr(e,Xl(n)),kn)}function SC(t){return t.reduce((e,n)=>e+mh(n),0)}function _C(t){return t.reduce((e,n)=>e+bh(n),0)}function wh(t,e){if(e===void 0&&(e=Zr),!t)return;const{top:n,left:r,bottom:s,right:o}=e(t);ph(t)&&(s<=0||o<=0||n>=window.innerHeight||r>=window.innerWidth)&&t.scrollIntoView({block:"center",inline:"center"})}const EC=[["x",["left","right"],SC],["y",["top","bottom"],_C]];class Kl{constructor(e,n){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const r=Si(n),s=xh(r);this.rect={...e},this.width=e.width,this.height=e.height;for(const[o,i,a]of EC)for(const l of i)Object.defineProperty(this,l,{get:()=>{const c=a(r),d=s[o]-c;return this.rect[l]+d},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Gs{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(n=>{var r;return(r=this.target)==null?void 0:r.removeEventListener(...n)})},this.target=e}add(e,n,r){var s;(s=this.target)==null||s.addEventListener(e,n,r),this.listeners.push([e,n,r])}}function MC(t){const{EventTarget:e}=Kt(t);return t instanceof e?t:Xr(t)}function ql(t,e){const n=Math.abs(t.x),r=Math.abs(t.y);return typeof e=="number"?Math.sqrt(n**2+r**2)>e:"x"in e&&"y"in e?n>e.x&&r>e.y:"x"in e?n>e.x:"y"in e?r>e.y:!1}var dn;(function(t){t.Click="click",t.DragStart="dragstart",t.Keydown="keydown",t.ContextMenu="contextmenu",t.Resize="resize",t.SelectionChange="selectionchange",t.VisibilityChange="visibilitychange"})(dn||(dn={}));function kh(t){t.preventDefault()}function NC(t){t.stopPropagation()}var Ke;(function(t){t.Space="Space",t.Down="ArrowDown",t.Right="ArrowRight",t.Left="ArrowLeft",t.Up="ArrowUp",t.Esc="Escape",t.Enter="Enter",t.Tab="Tab"})(Ke||(Ke={}));const Ch={start:[Ke.Space,Ke.Enter],cancel:[Ke.Esc],end:[Ke.Space,Ke.Enter,Ke.Tab]},OC=(t,e)=>{let{currentCoordinates:n}=e;switch(t.code){case Ke.Right:return{...n,x:n.x+25};case Ke.Left:return{...n,x:n.x-25};case Ke.Down:return{...n,y:n.y+25};case Ke.Up:return{...n,y:n.y-25}}};class Ys{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:n}}=e;this.props=e,this.listeners=new Gs(Xr(n)),this.windowListeners=new Gs(Kt(n)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(dn.Resize,this.handleCancel),this.windowListeners.add(dn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(dn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:n}=this.props,r=e.node.current;r&&wh(r),n(kn)}handleKeyDown(e){if(wi(e)){const{active:n,context:r,options:s}=this.props,{keyboardCodes:o=Ch,coordinateGetter:i=OC,scrollBehavior:a="smooth"}=s,{code:l}=e;if(o.end.includes(l)){this.handleEnd(e);return}if(o.cancel.includes(l)){this.handleCancel(e);return}const{collisionRect:c}=r.current,d=c?{x:c.left,y:c.top}:kn;this.referenceCoordinates||(this.referenceCoordinates=d);const u=i(e,{active:n,context:r.current,currentCoordinates:d});if(u){const f=Ws(u,d),g={x:0,y:0},{scrollableAncestors:m}=r.current;for(const p of m){const y=e.code,{isTop:b,isRight:_,isLeft:x,isBottom:E,maxScroll:C,minScroll:k}=vh(p),N=CC(p),S={x:Math.min(y===Ke.Right?N.right-N.width/2:N.right,Math.max(y===Ke.Right?N.left:N.left+N.width/2,u.x)),y:Math.min(y===Ke.Down?N.bottom-N.height/2:N.bottom,Math.max(y===Ke.Down?N.top:N.top+N.height/2,u.y))},w=y===Ke.Right&&!_||y===Ke.Left&&!x,M=y===Ke.Down&&!E||y===Ke.Up&&!b;if(w&&S.x!==u.x){const T=p.scrollLeft+f.x,P=y===Ke.Right&&T<=C.x||y===Ke.Left&&T>=k.x;if(P&&!f.y){p.scrollTo({left:T,behavior:a});return}P?g.x=p.scrollLeft-T:g.x=y===Ke.Right?p.scrollLeft-C.x:p.scrollLeft-k.x,g.x&&p.scrollBy({left:-g.x,behavior:a});break}else if(M&&S.y!==u.y){const T=p.scrollTop+f.y,P=y===Ke.Down&&T<=C.y||y===Ke.Up&&T>=k.y;if(P&&!f.x){p.scrollTo({top:T,behavior:a});return}P?g.y=p.scrollTop-T:g.y=y===Ke.Down?p.scrollTop-C.y:p.scrollTop-k.y,g.y&&p.scrollBy({top:-g.y,behavior:a});break}}this.handleMove(e,Kr(Ws(u,this.referenceCoordinates),g))}}}handleMove(e,n){const{onMove:r}=this.props;e.preventDefault(),r(n)}handleEnd(e){const{onEnd:n}=this.props;e.preventDefault(),this.detach(),n()}handleCancel(e){const{onCancel:n}=this.props;e.preventDefault(),this.detach(),n()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}Ys.activators=[{eventName:"onKeyDown",handler:(t,e,n)=>{let{keyboardCodes:r=Ch,onActivation:s}=e,{active:o}=n;const{code:i}=t.nativeEvent;if(r.start.includes(i)){const a=o.activatorNode.current;return a&&t.target!==a?!1:(t.preventDefault(),s?.({event:t.nativeEvent}),!0)}return!1}}];function Sh(t){return!!(t&&"distance"in t)}function _h(t){return!!(t&&"delay"in t)}class Zl{constructor(e,n,r){var s;r===void 0&&(r=MC(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=n;const{event:o}=e,{target:i}=o;this.props=e,this.events=n,this.document=Xr(i),this.documentListeners=new Gs(this.document),this.listeners=new Gs(r),this.windowListeners=new Gs(Kt(i)),this.initialCoordinates=(s=ki(o))!=null?s:kn,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:n,bypassActivationConstraint:r}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(dn.Resize,this.handleCancel),this.windowListeners.add(dn.DragStart,kh),this.windowListeners.add(dn.VisibilityChange,this.handleCancel),this.windowListeners.add(dn.ContextMenu,kh),this.documentListeners.add(dn.Keydown,this.handleKeydown),n){if(r!=null&&r({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(_h(n)){this.timeoutId=setTimeout(this.handleStart,n.delay),this.handlePending(n);return}if(Sh(n)){this.handlePending(n);return}}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),this.timeoutId!==null&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,n){const{active:r,onPending:s}=this.props;s(r,e,this.initialCoordinates,n)}handleStart(){const{initialCoordinates:e}=this,{onStart:n}=this.props;e&&(this.activated=!0,this.documentListeners.add(dn.Click,NC,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(dn.SelectionChange,this.removeTextSelection),n(e))}handleMove(e){var n;const{activated:r,initialCoordinates:s,props:o}=this,{onMove:i,options:{activationConstraint:a}}=o;if(!s)return;const l=(n=ki(e))!=null?n:kn,c=Ws(s,l);if(!r&&a){if(Sh(a)){if(a.tolerance!=null&&ql(c,a.tolerance))return this.handleCancel();if(ql(c,a.distance))return this.handleStart()}if(_h(a)&&ql(c,a.tolerance))return this.handleCancel();this.handlePending(a,c);return}e.cancelable&&e.preventDefault(),i(l)}handleEnd(){const{onAbort:e,onEnd:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleCancel(){const{onAbort:e,onCancel:n}=this.props;this.detach(),this.activated||e(this.props.active),n()}handleKeydown(e){e.code===Ke.Esc&&this.handleCancel()}removeTextSelection(){var e;(e=this.document.getSelection())==null||e.removeAllRanges()}}const TC={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Xs extends Zl{constructor(e){const{event:n}=e,r=Xr(n.target);super(e,TC,r)}}Xs.activators=[{eventName:"onPointerDown",handler:(t,e)=>{let{nativeEvent:n}=t,{onActivation:r}=e;return!n.isPrimary||n.button!==0?!1:(r?.({event:n}),!0)}}];const AC={move:{name:"mousemove"},end:{name:"mouseup"}};var Ql;(function(t){t[t.RightClick=2]="RightClick"})(Ql||(Ql={}));class DC extends Zl{constructor(e){super(e,AC,Xr(e.event.target))}}DC.activators=[{eventName:"onMouseDown",handler:(t,e)=>{let{nativeEvent:n}=t,{onActivation:r}=e;return n.button===Ql.RightClick?!1:(r?.({event:n}),!0)}}];const Jl={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};class IC extends Zl{constructor(e){super(e,Jl)}static setup(){return window.addEventListener(Jl.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(Jl.move.name,e)};function e(){}}}IC.activators=[{eventName:"onTouchStart",handler:(t,e)=>{let{nativeEvent:n}=t,{onActivation:r}=e;const{touches:s}=n;return s.length>1?!1:(r?.({event:n}),!0)}}];var Ks;(function(t){t[t.Pointer=0]="Pointer",t[t.DraggableRect=1]="DraggableRect"})(Ks||(Ks={}));var _i;(function(t){t[t.TreeOrder=0]="TreeOrder",t[t.ReversedTreeOrder=1]="ReversedTreeOrder"})(_i||(_i={}));function LC(t){let{acceleration:e,activator:n=Ks.Pointer,canScroll:r,draggingRect:s,enabled:o,interval:i=5,order:a=_i.TreeOrder,pointerCoordinates:l,scrollableAncestors:c,scrollableAncestorRects:d,delta:u,threshold:f}=t;const g=$C({delta:u,disabled:!o}),[m,p]=qk(),y=v.useRef({x:0,y:0}),b=v.useRef({x:0,y:0}),_=v.useMemo(()=>{switch(n){case Ks.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case Ks.DraggableRect:return s}},[n,s,l]),x=v.useRef(null),E=v.useCallback(()=>{const k=x.current;if(!k)return;const N=y.current.x*b.current.x,S=y.current.y*b.current.y;k.scrollBy(N,S)},[]),C=v.useMemo(()=>a===_i.TreeOrder?[...c].reverse():c,[a,c]);v.useEffect(()=>{if(!o||!c.length||!_){p();return}for(const k of C){if(r?.(k)===!1)continue;const N=c.indexOf(k),S=d[N];if(!S)continue;const{direction:w,speed:M}=kC(k,S,_,e,f);for(const T of["x","y"])g[T][w[T]]||(M[T]=0,w[T]=0);if(M.x>0||M.y>0){p(),x.current=k,m(E,i),y.current=M,b.current=w;return}}y.current={x:0,y:0},b.current={x:0,y:0},p()},[e,E,r,p,o,i,JSON.stringify(_),JSON.stringify(g),m,c,C,d,JSON.stringify(f)])}const PC={x:{[Ot.Backward]:!1,[Ot.Forward]:!1},y:{[Ot.Backward]:!1,[Ot.Forward]:!1}};function $C(t){let{delta:e,disabled:n}=t;const r=xi(e);return Bs(s=>{if(n||!r||!s)return PC;const o={x:Math.sign(e.x-r.x),y:Math.sign(e.y-r.y)};return{x:{[Ot.Backward]:s.x[Ot.Backward]||o.x===-1,[Ot.Forward]:s.x[Ot.Forward]||o.x===1},y:{[Ot.Backward]:s.y[Ot.Backward]||o.y===-1,[Ot.Forward]:s.y[Ot.Forward]||o.y===1}}},[n,e,r])}function FC(t,e){const n=e!=null?t.get(e):void 0,r=n?n.node.current:null;return Bs(s=>{var o;return e==null?null:(o=r??s)!=null?o:null},[r,e])}function zC(t,e){return v.useMemo(()=>t.reduce((n,r)=>{const{sensor:s}=r,o=s.activators.map(i=>({eventName:i.eventName,handler:e(i.handler,r)}));return[...n,...o]},[]),[t,e])}var qs;(function(t){t[t.Always=0]="Always",t[t.BeforeDragging=1]="BeforeDragging",t[t.WhileDragging=2]="WhileDragging"})(qs||(qs={}));var Rl;(function(t){t.Optimized="optimized"})(Rl||(Rl={}));const Eh=new Map;function VC(t,e){let{dragging:n,dependencies:r,config:s}=e;const[o,i]=v.useState(null),{frequency:a,measure:l,strategy:c}=s,d=v.useRef(t),u=y(),f=Us(u),g=v.useCallback(function(b){b===void 0&&(b=[]),!f.current&&i(_=>_===null?b:_.concat(b.filter(x=>!_.includes(x))))},[f]),m=v.useRef(null),p=Bs(b=>{if(u&&!n)return Eh;if(!b||b===Eh||d.current!==t||o!=null){const _=new Map;for(let x of t){if(!x)continue;if(o&&o.length>0&&!o.includes(x.id)&&x.rect.current){_.set(x.id,x.rect.current);continue}const E=x.node.current,C=E?new Kl(l(E),E):null;x.rect.current=C,C&&_.set(x.id,C)}return _}return b},[t,o,n,u,l]);return v.useEffect(()=>{d.current=t},[t]),v.useEffect(()=>{u||g()},[n,u]),v.useEffect(()=>{o&&o.length>0&&i(null)},[JSON.stringify(o)]),v.useEffect(()=>{u||typeof a!="number"||m.current!==null||(m.current=setTimeout(()=>{g(),m.current=null},a))},[a,u,g,...r]),{droppableRects:p,measureDroppableContainers:g,measuringScheduled:o!=null};function y(){switch(c){case qs.Always:return!1;case qs.BeforeDragging:return n;default:return!n}}}function ec(t,e){return Bs(n=>t?n||(typeof e=="function"?e(t):t):null,[e,t])}function HC(t,e){return ec(t,e)}function UC(t){let{callback:e,disabled:n}=t;const r=yi(e),s=v.useMemo(()=>{if(n||typeof window>"u"||typeof window.MutationObserver>"u")return;const{MutationObserver:o}=window;return new o(r)},[r,n]);return v.useEffect(()=>()=>s?.disconnect(),[s]),s}function Ei(t){let{callback:e,disabled:n}=t;const r=yi(e),s=v.useMemo(()=>{if(n||typeof window>"u"||typeof window.ResizeObserver>"u")return;const{ResizeObserver:o}=window;return new o(r)},[n]);return v.useEffect(()=>()=>s?.disconnect(),[s]),s}function BC(t){return new Kl(Zr(t),t)}function Mh(t,e,n){e===void 0&&(e=BC);const[r,s]=v.useState(null);function o(){s(l=>{if(!t)return null;if(t.isConnected===!1){var c;return(c=l??n)!=null?c:null}const d=e(t);return JSON.stringify(l)===JSON.stringify(d)?l:d})}const i=UC({callback(l){if(t)for(const c of l){const{type:d,target:u}=c;if(d==="childList"&&u instanceof HTMLElement&&u.contains(t)){o();break}}}}),a=Ei({callback:o});return wn(()=>{o(),t?(a?.observe(t),i?.observe(document.body,{childList:!0,subtree:!0})):(a?.disconnect(),i?.disconnect())},[t]),r}function jC(t){const e=ec(t);return fh(t,e)}const Nh=[];function WC(t){const e=v.useRef(t),n=Bs(r=>t?r&&r!==Nh&&t&&e.current&&t.parentNode===e.current.parentNode?r:Si(t):Nh,[t]);return v.useEffect(()=>{e.current=t},[t]),n}function GC(t){const[e,n]=v.useState(null),r=v.useRef(t),s=v.useCallback(o=>{const i=Yl(o.target);i&&n(a=>a?(a.set(i,Xl(i)),new Map(a)):null)},[]);return v.useEffect(()=>{const o=r.current;if(t!==o){i(o);const a=t.map(l=>{const c=Yl(l);return c?(c.addEventListener("scroll",s,{passive:!0}),[c,Xl(c)]):null}).filter(l=>l!=null);n(a.length?new Map(a):null),r.current=t}return()=>{i(t),i(o)};function i(a){a.forEach(l=>{const c=Yl(l);c?.removeEventListener("scroll",s)})}},[s,t]),v.useMemo(()=>t.length?e?Array.from(e.values()).reduce((o,i)=>Kr(o,i),kn):xh(t):kn,[t,e])}function Oh(t,e){e===void 0&&(e=[]);const n=v.useRef(null);return v.useEffect(()=>{n.current=null},e),v.useEffect(()=>{const r=t!==kn;r&&!n.current&&(n.current=t),!r&&n.current&&(n.current=null)},[t]),n.current?Ws(t,n.current):kn}function YC(t){v.useEffect(()=>{if(!bi)return;const e=t.map(n=>{let{sensor:r}=n;return r.setup==null?void 0:r.setup()});return()=>{for(const n of e)n?.()}},t.map(e=>{let{sensor:n}=e;return n}))}function XC(t,e){return v.useMemo(()=>t.reduce((n,r)=>{let{eventName:s,handler:o}=r;return n[s]=i=>{o(i,e)},n},{}),[t,e])}function Th(t){return v.useMemo(()=>t?yC(t):null,[t])}const Ah=[];function KC(t,e){e===void 0&&(e=Zr);const[n]=t,r=Th(n?Kt(n):null),[s,o]=v.useState(Ah);function i(){o(()=>t.length?t.map(l=>yh(l)?r:new Kl(e(l),l)):Ah)}const a=Ei({callback:i});return wn(()=>{a?.disconnect(),i(),t.forEach(l=>a?.observe(l))},[t]),s}function Dh(t){if(!t)return null;if(t.children.length>1)return t;const e=t.children[0];return Hs(e)?e:t}function qC(t){let{measure:e}=t;const[n,r]=v.useState(null),s=v.useCallback(c=>{for(const{target:d}of c)if(Hs(d)){r(u=>{const f=e(d);return u?{...u,width:f.width,height:f.height}:f});break}},[e]),o=Ei({callback:s}),i=v.useCallback(c=>{const d=Dh(c);o?.disconnect(),d&&o?.observe(d),r(d?e(d):null)},[e,o]),[a,l]=vi(i);return v.useMemo(()=>({nodeRef:a,rect:n,setRef:l}),[n,a,l])}const ZC=[{sensor:Xs,options:{}},{sensor:Ys,options:{}}],QC={current:{}},Mi={draggable:{measure:gh},droppable:{measure:gh,strategy:qs.WhileDragging,frequency:Rl.Optimized},dragOverlay:{measure:Zr}};class Zs extends Map{get(e){var n;return e!=null&&(n=super.get(e))!=null?n:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:n}=e;return!n})}getNodeFor(e){var n,r;return(n=(r=this.get(e))==null?void 0:r.node.current)!=null?n:void 0}}const JC={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Zs,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:Ci},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:Mi,measureDroppableContainers:Ci,windowRect:null,measuringScheduled:!1},Ih={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:Ci,draggableNodes:new Map,over:null,measureDroppableContainers:Ci},Qs=v.createContext(Ih),Lh=v.createContext(JC);function RC(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Zs}}}function eS(t,e){switch(e.type){case _t.DragStart:return{...t,draggable:{...t.draggable,initialCoordinates:e.initialCoordinates,active:e.active}};case _t.DragMove:return t.draggable.active==null?t:{...t,draggable:{...t.draggable,translate:{x:e.coordinates.x-t.draggable.initialCoordinates.x,y:e.coordinates.y-t.draggable.initialCoordinates.y}}};case _t.DragEnd:case _t.DragCancel:return{...t,draggable:{...t.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case _t.RegisterDroppable:{const{element:n}=e,{id:r}=n,s=new Zs(t.droppable.containers);return s.set(r,n),{...t,droppable:{...t.droppable,containers:s}}}case _t.SetDroppableDisabled:{const{id:n,key:r,disabled:s}=e,o=t.droppable.containers.get(n);if(!o||r!==o.key)return t;const i=new Zs(t.droppable.containers);return i.set(n,{...o,disabled:s}),{...t,droppable:{...t.droppable,containers:i}}}case _t.UnregisterDroppable:{const{id:n,key:r}=e,s=t.droppable.containers.get(n);if(!s||r!==s.key)return t;const o=new Zs(t.droppable.containers);return o.delete(n),{...t,droppable:{...t.droppable,containers:o}}}default:return t}}function tS(t){let{disabled:e}=t;const{active:n,activatorEvent:r,draggableNodes:s}=v.useContext(Qs),o=xi(r),i=xi(n?.id);return v.useEffect(()=>{if(!e&&!r&&o&&i!=null){if(!wi(o)||document.activeElement===o.target)return;const a=s.get(i);if(!a)return;const{activatorNode:l,node:c}=a;if(!l.current&&!c.current)return;requestAnimationFrame(()=>{for(const d of[l.current,c.current]){if(!d)continue;const u=Jk(d);if(u){u.focus();break}}})}},[r,e,s,i,o]),null}function Ph(t,e){let{transform:n,...r}=e;return t!=null&&t.length?t.reduce((s,o)=>o({transform:s,...r}),n):n}function nS(t){return v.useMemo(()=>({draggable:{...Mi.draggable,...t?.draggable},droppable:{...Mi.droppable,...t?.droppable},dragOverlay:{...Mi.dragOverlay,...t?.dragOverlay}}),[t?.draggable,t?.droppable,t?.dragOverlay])}function rS(t){let{activeNode:e,measure:n,initialRect:r,config:s=!0}=t;const o=v.useRef(!1),{x:i,y:a}=typeof s=="boolean"?{x:s,y:s}:s;wn(()=>{if(!i&&!a||!e){o.current=!1;return}if(o.current||!r)return;const c=e?.node.current;if(!c||c.isConnected===!1)return;const d=n(c),u=fh(d,r);if(i||(u.x=0),a||(u.y=0),o.current=!0,Math.abs(u.x)>0||Math.abs(u.y)>0){const f=ph(c);f&&f.scrollBy({top:u.y,left:u.x})}},[e,i,a,r,n])}const Ni=v.createContext({...kn,scaleX:1,scaleY:1});var nr;(function(t){t[t.Uninitialized=0]="Uninitialized",t[t.Initializing=1]="Initializing",t[t.Initialized=2]="Initialized"})(nr||(nr={}));const tc=v.memo(function(e){var n,r,s,o;let{id:i,accessibility:a,autoScroll:l=!0,children:c,sensors:d=ZC,collisionDetection:u=fC,measuring:f,modifiers:g,...m}=e;const p=v.useReducer(eS,void 0,RC),[y,b]=p,[_,x]=sC(),[E,C]=v.useState(nr.Uninitialized),k=E===nr.Initialized,{draggable:{active:N,nodes:S,translate:w},droppable:{containers:M}}=y,T=N!=null?S.get(N):null,P=v.useRef({initial:null,translated:null}),L=v.useMemo(()=>{var Pe;return N!=null?{id:N,data:(Pe=T?.data)!=null?Pe:QC,rect:P}:null},[N,T]),$=v.useRef(null),[A,D]=v.useState(null),[O,z]=v.useState(null),H=Us(m,Object.values(m)),j=js("DndDescribedBy",i),W=v.useMemo(()=>M.getEnabled(),[M]),U=nS(f),{droppableRects:B,measureDroppableContainers:G,measuringScheduled:V}=VC(W,{dragging:k,dependencies:[w.x,w.y],config:U.droppable}),K=FC(S,N),X=v.useMemo(()=>O?ki(O):null,[O]),Z=ot(),ee=HC(K,U.draggable.measure);rS({activeNode:N!=null?S.get(N):null,config:Z.layoutShiftCompensation,initialRect:ee,measure:U.draggable.measure});const ne=Mh(K,U.draggable.measure,ee),he=Mh(K?K.parentElement:null),J=v.useRef({activatorEvent:null,active:null,activeNode:K,collisionRect:null,collisions:null,droppableRects:B,draggableNodes:S,draggingNode:null,draggingNodeRect:null,droppableContainers:M,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),le=M.getNodeFor((n=J.current.over)==null?void 0:n.id),ue=qC({measure:U.dragOverlay.measure}),ae=(r=ue.nodeRef.current)!=null?r:K,ve=k?(s=ue.rect)!=null?s:ne:null,fe=!!(ue.nodeRef.current&&ue.rect),ye=jC(fe?null:ne),we=Th(ae?Kt(ae):null),He=WC(k?le??K:null),se=KC(He),Oe=Ph(g,{transform:{x:w.x-ye.x,y:w.y-ye.y,scaleX:1,scaleY:1},activatorEvent:O,active:L,activeNodeRect:ne,containerNodeRect:he,draggingNodeRect:ve,over:J.current.over,overlayNodeRect:ue.rect,scrollableAncestors:He,scrollableAncestorRects:se,windowRect:we}),Le=X?Kr(X,w):null,Ie=GC(He),Be=Oh(Ie),st=Oh(Ie,[ne]),ut=Kr(Oe,Be),je=ve?pC(ve,Oe):null,nt=L&&je?u({active:L,collisionRect:je,droppableRects:B,droppableContainers:W,pointerCoordinates:Le}):null,Ye=dh(nt,"id"),[te,ge]=v.useState(null),me=fe?Oe:Kr(Oe,st),pe=hC(me,(o=te?.rect)!=null?o:null,ne),be=v.useRef(null),Ue=v.useCallback((Pe,Te)=>{let{sensor:Ve,options:De}=Te;if($.current==null)return;const qe=S.get($.current);if(!qe)return;const it=Pe.nativeEvent,pt=new Ve({active:$.current,activeNode:qe,event:it,options:De,context:J,onAbort(bt){if(!S.get(bt))return;const{onDragAbort:jt}=H.current,ft={id:bt};jt?.(ft),_({type:"onDragAbort",event:ft})},onPending(bt,wt,jt,ft){if(!S.get(bt))return;const{onDragPending:Nn}=H.current,Wt={id:bt,constraint:wt,initialCoordinates:jt,offset:ft};Nn?.(Wt),_({type:"onDragPending",event:Wt})},onStart(bt){const wt=$.current;if(wt==null)return;const jt=S.get(wt);if(!jt)return;const{onDragStart:ft}=H.current,Xe={activatorEvent:it,active:{id:wt,data:jt.data,rect:P}};Tn.unstable_batchedUpdates(()=>{ft?.(Xe),C(nr.Initializing),b({type:_t.DragStart,initialCoordinates:bt,active:wt}),_({type:"onDragStart",event:Xe}),D(be.current),z(it)})},onMove(bt){b({type:_t.DragMove,coordinates:bt})},onEnd:rn(_t.DragEnd),onCancel:rn(_t.DragCancel)});be.current=pt;function rn(bt){return async function(){const{active:jt,collisions:ft,over:Xe,scrollAdjustedTranslate:Nn}=J.current;let Wt=null;if(jt&&Nn){const{cancelDrop:bn}=H.current;Wt={activatorEvent:it,active:jt,collisions:ft,delta:Nn,over:Xe},bt===_t.DragEnd&&typeof bn=="function"&&await Promise.resolve(bn(Wt))&&(bt=_t.DragCancel)}$.current=null,Tn.unstable_batchedUpdates(()=>{b({type:bt}),C(nr.Uninitialized),ge(null),D(null),z(null),be.current=null;const bn=bt===_t.DragEnd?"onDragEnd":"onDragCancel";if(Wt){const hr=H.current[bn];hr?.(Wt),_({type:bn,event:Wt})}})}}},[S]),Ce=v.useCallback((Pe,Te)=>(Ve,De)=>{const qe=Ve.nativeEvent,it=S.get(De);if($.current!==null||!it||qe.dndKit||qe.defaultPrevented)return;const pt={active:it};Pe(Ve,Te.options,pt)===!0&&(qe.dndKit={capturedBy:Te.sensor},$.current=De,Ue(Ve,Te))},[S,Ue]),ze=zC(d,Ce);YC(d),wn(()=>{ne&&E===nr.Initializing&&C(nr.Initialized)},[ne,E]),v.useEffect(()=>{const{onDragMove:Pe}=H.current,{active:Te,activatorEvent:Ve,collisions:De,over:qe}=J.current;if(!Te||!Ve)return;const it={active:Te,activatorEvent:Ve,collisions:De,delta:{x:ut.x,y:ut.y},over:qe};Tn.unstable_batchedUpdates(()=>{Pe?.(it),_({type:"onDragMove",event:it})})},[ut.x,ut.y]),v.useEffect(()=>{const{active:Pe,activatorEvent:Te,collisions:Ve,droppableContainers:De,scrollAdjustedTranslate:qe}=J.current;if(!Pe||$.current==null||!Te||!qe)return;const{onDragOver:it}=H.current,pt=De.get(Ye),rn=pt&&pt.rect.current?{id:pt.id,rect:pt.rect.current,data:pt.data,disabled:pt.disabled}:null,bt={active:Pe,activatorEvent:Te,collisions:Ve,delta:{x:qe.x,y:qe.y},over:rn};Tn.unstable_batchedUpdates(()=>{ge(rn),it?.(bt),_({type:"onDragOver",event:bt})})},[Ye]),wn(()=>{J.current={activatorEvent:O,active:L,activeNode:K,collisionRect:je,collisions:nt,droppableRects:B,draggableNodes:S,draggingNode:ae,draggingNodeRect:ve,droppableContainers:M,over:te,scrollableAncestors:He,scrollAdjustedTranslate:ut},P.current={initial:ve,translated:je}},[L,K,nt,je,S,ae,ve,B,M,te,He,ut]),LC({...Z,delta:w,draggingRect:je,pointerCoordinates:Le,scrollableAncestors:He,scrollableAncestorRects:se});const Se=v.useMemo(()=>({active:L,activeNode:K,activeNodeRect:ne,activatorEvent:O,collisions:nt,containerNodeRect:he,dragOverlay:ue,draggableNodes:S,droppableContainers:M,droppableRects:B,over:te,measureDroppableContainers:G,scrollableAncestors:He,scrollableAncestorRects:se,measuringConfiguration:U,measuringScheduled:V,windowRect:we}),[L,K,ne,O,nt,he,ue,S,M,B,te,G,He,se,U,V,we]),Re=v.useMemo(()=>({activatorEvent:O,activators:ze,active:L,activeNodeRect:ne,ariaDescribedById:{draggable:j},dispatch:b,draggableNodes:S,over:te,measureDroppableContainers:G}),[O,ze,L,ne,b,j,S,te,G]);return v.createElement(ih.Provider,{value:x},v.createElement(Qs.Provider,{value:Re},v.createElement(Lh.Provider,{value:Se},v.createElement(Ni.Provider,{value:pe},c)),v.createElement(tS,{disabled:a?.restoreFocus===!1})),v.createElement(aC,{...a,hiddenTextDescribedById:j}));function ot(){const Pe=A?.autoScrollEnabled===!1,Te=typeof l=="object"?l.enabled===!1:l===!1,Ve=k&&!Pe&&!Te;return typeof l=="object"?{...l,enabled:Ve}:{enabled:Ve}}}),sS=v.createContext(null),$h="button",oS="Draggable";function iS(t){let{id:e,data:n,disabled:r=!1,attributes:s}=t;const o=js(oS),{activators:i,activatorEvent:a,active:l,activeNodeRect:c,ariaDescribedById:d,draggableNodes:u,over:f}=v.useContext(Qs),{role:g=$h,roleDescription:m="draggable",tabIndex:p=0}=s??{},y=l?.id===e,b=v.useContext(y?Ni:sS),[_,x]=vi(),[E,C]=vi(),k=XC(i,e),N=Us(n);wn(()=>(u.set(e,{id:e,key:o,node:_,activatorNode:E,data:N}),()=>{const w=u.get(e);w&&w.key===o&&u.delete(e)}),[u,e]);const S=v.useMemo(()=>({role:g,tabIndex:p,"aria-disabled":r,"aria-pressed":y&&g===$h?!0:void 0,"aria-roledescription":m,"aria-describedby":d.draggable}),[r,g,p,y,m,d.draggable]);return{active:l,activatorEvent:a,activeNodeRect:c,attributes:S,isDragging:y,listeners:r?void 0:k,node:_,over:f,setNodeRef:x,setActivatorNodeRef:C,transform:b}}function Fh(){return v.useContext(Lh)}const aS="Droppable",lS={timeout:25};function cS(t){let{data:e,disabled:n=!1,id:r,resizeObserverConfig:s}=t;const o=js(aS),{active:i,dispatch:a,over:l,measureDroppableContainers:c}=v.useContext(Qs),d=v.useRef({disabled:n}),u=v.useRef(!1),f=v.useRef(null),g=v.useRef(null),{disabled:m,updateMeasurementsFor:p,timeout:y}={...lS,...s},b=Us(p??r),_=v.useCallback(()=>{if(!u.current){u.current=!0;return}g.current!=null&&clearTimeout(g.current),g.current=setTimeout(()=>{c(Array.isArray(b.current)?b.current:[b.current]),g.current=null},y)},[y]),x=Ei({callback:_,disabled:m||!i}),E=v.useCallback((S,w)=>{x&&(w&&(x.unobserve(w),u.current=!1),S&&x.observe(S))},[x]),[C,k]=vi(E),N=Us(e);return v.useEffect(()=>{!x||!C.current||(x.disconnect(),u.current=!1,x.observe(C.current))},[C,x]),v.useEffect(()=>(a({type:_t.RegisterDroppable,element:{id:r,key:o,disabled:n,node:C,rect:f,data:N}}),()=>a({type:_t.UnregisterDroppable,key:o,id:r})),[r]),v.useEffect(()=>{n!==d.current.disabled&&(a({type:_t.SetDroppableDisabled,id:r,key:o,disabled:n}),d.current.disabled=n)},[r,o,n,a]),{active:i,rect:f,isOver:l?.id===r,node:C,over:l,setNodeRef:k}}function dS(t){let{animation:e,children:n}=t;const[r,s]=v.useState(null),[o,i]=v.useState(null),a=xi(n);return!n&&!r&&a&&s(a),wn(()=>{if(!o)return;const l=r?.key,c=r?.props.id;if(l==null||c==null){s(null);return}Promise.resolve(e(c,o)).then(()=>{s(null)})},[e,r,o]),v.createElement(v.Fragment,null,n,r?v.cloneElement(r,{ref:i}):null)}const uS={x:0,y:0,scaleX:1,scaleY:1};function fS(t){let{children:e}=t;return v.createElement(Qs.Provider,{value:Ih},v.createElement(Ni.Provider,{value:uS},e))}const hS={position:"fixed",touchAction:"none"},gS=t=>wi(t)?"transform 250ms ease":void 0,pS=v.forwardRef((t,e)=>{let{as:n,activatorEvent:r,adjustScale:s,children:o,className:i,rect:a,style:l,transform:c,transition:d=gS}=t;if(!a)return null;const u=s?c:{...c,scaleX:1,scaleY:1},f={...hS,width:a.width,height:a.height,top:a.top,left:a.left,transform:Pn.Transform.toString(u),transformOrigin:s&&r?lC(r,a):void 0,transition:typeof d=="function"?d(r):d,...l};return v.createElement(n,{className:i,style:f,ref:e},o)}),mS={duration:250,easing:"ease",keyframes:t=>{let{transform:{initial:e,final:n}}=t;return[{transform:Pn.Transform.toString(e)},{transform:Pn.Transform.toString(n)}]},sideEffects:(t=>e=>{let{active:n,dragOverlay:r}=e;const s={},{styles:o,className:i}=t;if(o!=null&&o.active)for(const[a,l]of Object.entries(o.active))l!==void 0&&(s[a]=n.node.style.getPropertyValue(a),n.node.style.setProperty(a,l));if(o!=null&&o.dragOverlay)for(const[a,l]of Object.entries(o.dragOverlay))l!==void 0&&r.node.style.setProperty(a,l);return i!=null&&i.active&&n.node.classList.add(i.active),i!=null&&i.dragOverlay&&r.node.classList.add(i.dragOverlay),function(){for(const[l,c]of Object.entries(s))n.node.style.setProperty(l,c);i!=null&&i.active&&n.node.classList.remove(i.active)}})({styles:{active:{opacity:"0"}}})};function bS(t){let{config:e,draggableNodes:n,droppableContainers:r,measuringConfiguration:s}=t;return yi((o,i)=>{if(e===null)return;const a=n.get(o);if(!a)return;const l=a.node.current;if(!l)return;const c=Dh(i);if(!c)return;const{transform:d}=Kt(i).getComputedStyle(i),u=hh(d);if(!u)return;const f=typeof e=="function"?e:yS(e);return wh(l,s.draggable.measure),f({active:{id:o,data:a.data,node:l,rect:s.draggable.measure(l)},draggableNodes:n,dragOverlay:{node:i,rect:s.dragOverlay.measure(c)},droppableContainers:r,measuringConfiguration:s,transform:u})})}function yS(t){const{duration:e,easing:n,sideEffects:r,keyframes:s}={...mS,...t};return o=>{let{active:i,dragOverlay:a,transform:l,...c}=o;if(!e)return;const d={x:a.rect.left-i.rect.left,y:a.rect.top-i.rect.top},u={scaleX:l.scaleX!==1?i.rect.width*l.scaleX/a.rect.width:1,scaleY:l.scaleY!==1?i.rect.height*l.scaleY/a.rect.height:1},f={x:l.x-d.x,y:l.y-d.y,...u},g=s({...c,active:i,dragOverlay:a,transform:{initial:l,final:f}}),[m]=g,p=g[g.length-1];if(JSON.stringify(m)===JSON.stringify(p))return;const y=r?.({active:i,dragOverlay:a,...c}),b=a.node.animate(g,{duration:e,easing:n,fill:"forwards"});return new Promise(_=>{b.onfinish=()=>{y?.(),_()}})}}let zh=0;function vS(t){return v.useMemo(()=>{if(t!=null)return zh++,zh},[t])}const xS=v.memo(t=>{let{adjustScale:e=!1,children:n,dropAnimation:r,style:s,transition:o,modifiers:i,wrapperElement:a="div",className:l,zIndex:c=999}=t;const{activatorEvent:d,active:u,activeNodeRect:f,containerNodeRect:g,draggableNodes:m,droppableContainers:p,dragOverlay:y,over:b,measuringConfiguration:_,scrollableAncestors:x,scrollableAncestorRects:E,windowRect:C}=Fh(),k=v.useContext(Ni),N=vS(u?.id),S=Ph(i,{activatorEvent:d,active:u,activeNodeRect:f,containerNodeRect:g,draggingNodeRect:y.rect,over:b,overlayNodeRect:y.rect,scrollableAncestors:x,scrollableAncestorRects:E,transform:k,windowRect:C}),w=ec(f),M=bS({config:r,draggableNodes:m,droppableContainers:p,measuringConfiguration:_}),T=w?y.setRef:void 0;return v.createElement(fS,null,v.createElement(dS,{animation:M},u&&N?v.createElement(pS,{key:N,id:u.id,ref:T,as:a,activatorEvent:d,adjustScale:e,className:l,transition:o,rect:w,style:{zIndex:c,...s},transform:S},n):null))});function Qr(t,e,n){const r=t.slice();return r.splice(n<0?r.length+n:n,0,r.splice(e,1)[0]),r}function wS(t,e){return t.reduce((n,r,s)=>{const o=e.get(r);return o&&(n[s]=o),n},Array(t.length))}function Oi(t){return t!==null&&t>=0}function kS(t,e){if(t===e)return!0;if(t.length!==e.length)return!1;for(let n=0;n{let{rects:e,activeIndex:n,overIndex:r,index:s}=t;const o=Qr(e,r,n),i=e[s],a=o[s];return!a||!i?null:{x:a.left-i.left,y:a.top-i.top,scaleX:a.width/i.width,scaleY:a.height/i.height}},Ai={scaleX:1,scaleY:1},nc=t=>{var e;let{activeIndex:n,activeNodeRect:r,index:s,rects:o,overIndex:i}=t;const a=(e=o[n])!=null?e:r;if(!a)return null;if(s===n){const c=o[i];return c?{x:0,y:nn&&s<=i?{x:0,y:-a.height-l,...Ai}:s=i?{x:0,y:a.height+l,...Ai}:{x:0,y:0,...Ai}};function SS(t,e,n){const r=t[e],s=t[e-1],o=t[e+1];return r?nr.map(k=>typeof k=="object"&&"id"in k?k.id:k),[r]),m=i!=null,p=i?g.indexOf(i.id):-1,y=c?g.indexOf(c.id):-1,b=v.useRef(g),_=!kS(g,b.current),x=y!==-1&&p===-1||_,E=CS(o);wn(()=>{_&&m&&d(g)},[_,g,m,d]),v.useEffect(()=>{b.current=g},[g]);const C=v.useMemo(()=>({activeIndex:p,containerId:u,disabled:E,disableTransforms:x,items:g,overIndex:y,useDragOverlay:f,sortedRects:wS(g,l),strategy:s}),[p,u,E.draggable,E.droppable,x,g,y,l,f,s]);return v.createElement(Hh.Provider,{value:C},e)}const Uh=t=>{let{id:e,items:n,activeIndex:r,overIndex:s}=t;return Qr(n,r,s).indexOf(e)},Bh=t=>{let{containerId:e,isSorting:n,wasDragging:r,index:s,items:o,newIndex:i,previousItems:a,previousContainerId:l,transition:c}=t;return!c||!r||a!==o&&s===i?!1:n?!0:i!==s&&e===l},_S={duration:200,easing:"ease"},jh="transform",ES=Pn.Transition.toString({property:jh,duration:0,easing:"linear"}),MS={roleDescription:"sortable"};function NS(t){let{disabled:e,index:n,node:r,rect:s}=t;const[o,i]=v.useState(null),a=v.useRef(n);return wn(()=>{if(!e&&n!==a.current&&r.current){const l=s.current;if(l){const c=Zr(r.current,{ignoreTransform:!0}),d={x:l.left-c.left,y:l.top-c.top,scaleX:l.width/c.width,scaleY:l.height/c.height};(d.x||d.y)&&i(d)}}n!==a.current&&(a.current=n)},[e,n,r,s]),v.useEffect(()=>{o&&i(null)},[o]),o}function Ii(t){let{animateLayoutChanges:e=Bh,attributes:n,disabled:r,data:s,getNewIndex:o=Uh,id:i,strategy:a,resizeObserverConfig:l,transition:c=_S}=t;const{items:d,containerId:u,activeIndex:f,disabled:g,disableTransforms:m,sortedRects:p,overIndex:y,useDragOverlay:b,strategy:_}=v.useContext(Hh),x=OS(r,g),E=d.indexOf(i),C=v.useMemo(()=>({sortable:{containerId:u,index:E,items:d},...s}),[u,s,E,d]),k=v.useMemo(()=>d.slice(d.indexOf(i)),[d,i]),{rect:N,node:S,isOver:w,setNodeRef:M}=cS({id:i,data:C,disabled:x.droppable,resizeObserverConfig:{updateMeasurementsFor:k,...l}}),{active:T,activatorEvent:P,activeNodeRect:L,attributes:$,setNodeRef:A,listeners:D,isDragging:O,over:z,setActivatorNodeRef:H,transform:j}=iS({id:i,data:C,attributes:{...MS,...n},disabled:x.draggable}),W=Kk(M,A),U=!!T,B=U&&!m&&Oi(f)&&Oi(y),G=!b&&O,V=G&&B?j:null,X=B?V??(a??_)({rects:p,activeNodeRect:L,activeIndex:f,overIndex:y,index:E}):null,Z=Oi(f)&&Oi(y)?o({id:i,items:d,activeIndex:f,overIndex:y}):E,ee=T?.id,ne=v.useRef({activeId:ee,items:d,newIndex:Z,containerId:u}),he=d!==ne.current.items,J=e({active:T,containerId:u,isDragging:O,isSorting:U,id:i,index:E,items:d,newIndex:ne.current.newIndex,previousItems:ne.current.items,previousContainerId:ne.current.containerId,transition:c,wasDragging:ne.current.activeId!=null}),le=NS({disabled:!J,index:E,node:S,rect:N});return v.useEffect(()=>{U&&ne.current.newIndex!==Z&&(ne.current.newIndex=Z),u!==ne.current.containerId&&(ne.current.containerId=u),d!==ne.current.items&&(ne.current.items=d)},[U,Z,u,d]),v.useEffect(()=>{if(ee===ne.current.activeId)return;if(ee!=null&&ne.current.activeId==null){ne.current.activeId=ee;return}const ae=setTimeout(()=>{ne.current.activeId=ee},50);return()=>clearTimeout(ae)},[ee]),{active:T,activeIndex:f,attributes:$,data:C,rect:N,index:E,newIndex:Z,items:d,isOver:w,isSorting:U,isDragging:O,listeners:D,node:S,overIndex:y,over:z,setNodeRef:W,setActivatorNodeRef:H,setDroppableNodeRef:M,setDraggableNodeRef:A,transform:le??X,transition:ue()};function ue(){if(le||he&&ne.current.newIndex===E)return ES;if(!(G&&!wi(P)||!c)&&(U||J))return Pn.Transition.toString({...c,property:jh})}}function OS(t,e){var n,r;return typeof t=="boolean"?{draggable:t,droppable:!1}:{draggable:(n=t?.draggable)!=null?n:e.draggable,droppable:(r=t?.droppable)!=null?r:e.droppable}}function Js(t){if(!t)return!1;const e=t.data.current;return!!(e&&"sortable"in e&&typeof e.sortable=="object"&&"containerId"in e.sortable&&"items"in e.sortable&&"index"in e.sortable)}const TS=[Ke.Down,Ke.Right,Ke.Up,Ke.Left],Li=(t,e)=>{let{context:{active:n,collisionRect:r,droppableRects:s,droppableContainers:o,over:i,scrollableAncestors:a}}=e;if(TS.includes(t.code)){if(t.preventDefault(),!n||!r)return;const l=[];o.getEnabled().forEach(u=>{if(!u||u!=null&&u.disabled)return;const f=s.get(u.id);if(f)switch(t.code){case Ke.Down:r.topf.top&&l.push(u);break;case Ke.Left:r.left>f.left&&l.push(u);break;case Ke.Right:r.left1&&(d=c[1].id),d!=null){const u=o.get(n.id),f=o.get(d),g=f?s.get(f.id):null,m=f?.node.current;if(m&&g&&u&&f){const y=Si(m).some((k,N)=>a[N]!==k),b=Wh(u,f),_=AS(u,f),x=y||!b?{x:0,y:0}:{x:_?r.width-g.width:0,y:_?r.height-g.height:0},E={x:g.left,y:g.top};return x.x&&x.y?E:Ws(E,x)}}}};function Wh(t,e){return!Js(t)||!Js(e)?!1:t.data.current.sortable.containerId===e.data.current.sortable.containerId}function AS(t,e){return!Js(t)||!Js(e)||!Wh(t,e)?!1:t.data.current.sortable.indexwindow.G7Core,Gh=({item:t,isSelected:e=!1,isExpanded:n=!1,level:r=0,onClick:s,onToggle:o,onExpandToggle:i,className:a="",selectedClassName:l="bg-blue-50 dark:bg-blue-900/30 border-blue-500",defaultClassName:c="bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600",dragHandleClassName:d="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300",iconContainerClassName:u="bg-gray-100 dark:bg-gray-700",contentClassName:f="",enableDrag:g=!0,toggleDisabled:m=!1})=>{const{attributes:p,listeners:y,setNodeRef:b,transform:_,transition:x,isDragging:E}=Ii({id:t.id}),C={transform:Pn.Transform.toString(_),transition:x,opacity:E?.5:1},k=r*24+12,S=`flex items-center gap-2 cursor-pointer transition-colors ${e?l:c}`,w=IS()?.style?.mergeClasses?.(S,a)??`${S} ${a}`.trim();return h.jsxs(I,{ref:b,style:C,className:w,onClick:s,children:[g&&h.jsx(I,{...p,...y,className:`flex-shrink-0 cursor-grab active:cursor-grabbing p-1 ${d}`,style:{paddingLeft:`${k}px`},children:h.jsx(re,{name:"grip-vertical",className:"w-4 h-4"})}),!g&&r>0&&h.jsx(I,{style:{paddingLeft:`${k}px`}}),g&&h.jsx(h.Fragment,{children:t.hasChildren?h.jsx(oe,{type:"button",onClick:M=>{M.stopPropagation(),i?.()},className:`flex-shrink-0 p-1 ${d}`,children:h.jsx(re,{name:n?"chevron-down":"chevron-right",className:"w-4 h-4"})}):h.jsx(I,{className:"flex-shrink-0 w-6"})}),h.jsx(I,{className:`flex-shrink-0 w-8 h-8 flex items-center justify-center rounded ${u}`,children:h.jsx(re,{name:t.icon||"file",className:"w-4 h-4 text-gray-600 dark:text-gray-400"})}),h.jsxs(I,{className:`flex-1 min-w-0 ${f}`,children:[h.jsxs(I,{className:"flex items-center gap-1",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-900 dark:text-white truncate",children:t.name}),t.isModuleMenu&&h.jsx(re,{name:"cube",className:"w-2 h-2 text-gray-300 dark:text-gray-500 ml-2",title:t.moduleName})]}),h.jsx(Q,{className:"text-xs text-gray-500 dark:text-gray-400 truncate block",children:t.url})]}),h.jsx(I,{className:"flex-shrink-0",onClick:M=>M.stopPropagation(),children:h.jsx(Ua,{checked:t.isActive,onChange:()=>o?.(new MouseEvent("click")),size:"sm",disabled:m})})]})},LS=()=>window.G7Core;function Yh(t,e,n=0,r=null){const s=[];for(const o of t)s.push({item:o,depth:n,parentId:r}),o.children?.length>0&&e.has(o.id)&&s.push(...Yh(o.children,e,n+1,o.id));return s}function rc(t,e){for(const n of t){if(n.id===e)return n;if(n.children){const r=rc(n.children,e);if(r)return r}}return null}function Xh(t){const e=[];if(t.children)for(const n of t.children)e.push(n.id),e.push(...Xh(n));return e}function Kh(t,e){return t.filter(n=>n.id!==e).map(n=>n.children?.length>0?{...n,children:Kh(n.children,e)}:n)}function qh(t,e,n,r){if(n===null){const s=[...t];return s.splice(r,0,e),s}return t.map(s=>{if(s.id===n){const o=[...s.children||[]];return o.splice(r,0,e),{...s,children:o}}return s.children?.length>0?{...s,children:qh(s.children,e,n,r)}:s})}function Zh(t,e,n,r){if(e===null){const s=t.findIndex(i=>i.id===n),o=t.findIndex(i=>i.id===r);return s===-1||o===-1?t:Qr(t,s,o)}return t.map(s=>{if(s.id===e&&s.children){const o=s.children.findIndex(a=>a.id===n),i=s.children.findIndex(a=>a.id===r);return o===-1||i===-1?s:{...s,children:Qr(s.children,o,i)}}return s.children?.length>0?{...s,children:Zh(s.children,e,n,r)}:s})}function Qh(t){const e=t.map((s,o)=>({id:s.id,order:o+1})),n={},r=s=>{s.forEach(o=>{o.children?.length>0&&(n[o.id]=o.children.map((i,a)=>({id:i.id,order:a+1})),r(o.children))})};return r(t),{parent_menus:e,child_menus:n}}const PS=({items:t=[],selectedId:e,onSelect:n,onOrderChange:r,onToggleStatus:s,title:o,className:i="",listClassName:a="",childrenContainerClassName:l="bg-gray-50 dark:bg-gray-900/50",itemClassName:c="py-2.5 px-3 mx-4 my-2 rounded-lg border",itemSelectedClassName:d,itemDefaultClassName:u,titleClassName:f="px-4 py-3 border-b border-gray-200 dark:border-gray-700",enableDrag:g=!0,toggleDisabled:m=!1,id:p,editorAttrs:y})=>{const[b,_]=v.useState(new Set),[x,E]=v.useState(null),[C,k]=v.useState(t),N=v.useRef(!1),S=v.useRef(""),w=v.useRef(""),M=v.useCallback(B=>JSON.stringify(B),[]),T=v.useCallback(B=>B.map(G=>{const V=G.children?.map(K=>K.id).join(",")||"";return`${G.id}:${V}`}).join("|"),[]);v.useEffect(()=>{const B=T(t),G=M(t);if(N.current){const V=T(C);(B===V||B!==S.current)&&(N.current=!1,S.current=B,w.current=G,(B!==V||G!==M(C))&&k(t));return}G!==w.current&&(S.current=B,w.current=G,k(t))},[t,T,M,C]);const P=Wl(qr(Xs,{activationConstraint:{distance:8}}),qr(Ys,{coordinateGetter:Li})),L=v.useCallback(B=>{if(typeof B=="string")return B;const V=LS()?.locale?.current?.()||"ko";return B[V]||B.en||Object.values(B)[0]||""},[]),$=v.useCallback(B=>{_(G=>{const V=new Set(G);return V.has(B)?V.delete(B):V.add(B),V})},[]),A=v.useCallback(B=>{n?.(B)},[n]),D=v.useCallback((B,G,V)=>{V.stopPropagation(),s?.(B,G)},[s]),O=B=>{E(B.active.id)},z=B=>{const{active:G,over:V}=B;if(E(null),!V||G.id===V.id)return;const K=j.find(Z=>Z.item.id===Number(G.id)),X=j.find(Z=>Z.item.id===Number(V.id));if(!(!K||!X))if(K.parentId===X.parentId){const Z=Zh(C,K.parentId,Number(G.id),Number(V.id));k(Z),N.current=!0,r?.(Qh(Z))}else{const Z=K.item;if(Xh(Z).includes(Number(V.id)))return;let ne=Kh(C,Z.id);const he=j.findIndex(ye=>ye.item.id===Number(G.id)),J=j.findIndex(ye=>ye.item.id===Number(V.id)),le=he>J,ue=X.parentId;let ae;if(ue===null)ae=ne.findIndex(ye=>ye.id===Number(V.id)),ae===-1?ae=ne.length:le||(ae+=1);else{const we=rc(ne,ue)?.children||[];ae=we.findIndex(He=>He.id===Number(V.id)),ae===-1?ae=we.length:le||(ae+=1)}const ve={...Z,parent_id:ue,children:Z.children||[]};ne=qh(ne,ve,ue,ae),k(ne),N.current=!0;const fe=Qh(ne);fe.moved_items=[{id:Z.id,new_parent_id:ue}],r?.(fe)}},H=B=>({id:B.id,name:L(B.name),slug:B.slug,url:B.url,icon:B.icon,isActive:B.is_active,isModuleMenu:!!B.module_id,moduleName:B.module?L(B.module.name):void 0,hasChildren:B.children&&B.children.length>0}),j=v.useMemo(()=>Yh(C,b),[C,b]),W=v.useMemo(()=>j.map(B=>B.item.id),[j]),U=x?rc(C,Number(x)):null;return h.jsxs(I,{className:i,id:p,...y,children:[o&&h.jsx(I,{className:f,children:h.jsx(Ir,{className:"text-sm font-medium text-gray-900 dark:text-white",children:o})}),h.jsxs(tc,{sensors:P,collisionDetection:Gl,onDragStart:O,onDragEnd:z,children:[h.jsx(Di,{items:W,strategy:nc,children:h.jsx(I,{className:a,children:j.map(B=>h.jsx(I,{className:B.depth>0?l:"",children:h.jsx(Gh,{item:H(B.item),isSelected:e===B.item.id,isExpanded:b.has(B.item.id),level:B.depth,onClick:()=>A(B.item),onToggle:G=>D(B.item.id,B.item.is_active,G),onExpandToggle:()=>$(B.item.id),className:c,selectedClassName:d,defaultClassName:u,enableDrag:g,toggleDisabled:m})},B.item.id))})}),h.jsx(xS,{children:U?h.jsx(I,{className:"bg-white dark:bg-gray-800 shadow-lg rounded-lg border border-blue-500 p-3",children:h.jsx(I,{className:"flex items-center gap-2",children:h.jsx(I,{className:"text-sm font-medium text-gray-900 dark:text-white",children:L(U.name)})})}):null})]})]})},$S=[{value:"LayoutDashboard",label:"LayoutDashboard",faIcon:"tachometer-alt"},{value:"Settings",label:"Settings",faIcon:"cog"},{value:"Menu",label:"Menu",faIcon:"bars"},{value:"Users",label:"Users",faIcon:"users"},{value:"Package",label:"Package",faIcon:"cube"},{value:"Puzzle",label:"Puzzle",faIcon:"puzzle-piece"},{value:"FileText",label:"FileText",faIcon:"file-alt"},{value:"ShoppingCart",label:"ShoppingCart",faIcon:"shopping-cart"},{value:"MessageSquare",label:"MessageSquare",faIcon:"comment"},{value:"Home",label:"Home",faIcon:"home"},{value:"User",label:"User",faIcon:"user"},{value:"Search",label:"Search",faIcon:"search"},{value:"Bell",label:"Bell",faIcon:"bell"},{value:"Mail",label:"Mail",faIcon:"envelope"},{value:"Calendar",label:"Calendar",faIcon:"calendar"},{value:"Chart",label:"Chart",faIcon:"chart-bar"},{value:"Database",label:"Database",faIcon:"database"},{value:"Lock",label:"Lock",faIcon:"lock"},{value:"Globe",label:"Globe",faIcon:"globe"},{value:"Image",label:"Image",faIcon:"image"}],FS=({value:t,onChange:e,options:n=$S,placeholder:r,searchPlaceholder:s,noResultsText:o,className:i="",disabled:a=!1,name:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(!1),[g,m]=v.useState(""),p=v.useRef(null),y=n.find(x=>{if(!t)return!1;if(t.includes("fa-")){const E=t.replace(/^(fas|far|fab|fal|fad)\s+fa-/,"");return x.faIcon===E||x.faIcon===t}return x.value===t||x.faIcon===t}),b=n.filter(x=>x.label.toLowerCase().includes(g.toLowerCase()));v.useEffect(()=>{const x=E=>{p.current&&!p.current.contains(E.target)&&(f(!1),m(""))};if(u)return document.addEventListener("mousedown",x),()=>document.removeEventListener("mousedown",x)},[u]);const _=x=>{const E=`fas fa-${x.faIcon}`;e?.(E),f(!1),m("")};return h.jsxs(I,{ref:p,className:`relative ${i}`,id:c,...d,children:[h.jsxs(oe,{type:"button",onClick:()=>!a&&f(!u),disabled:a,className:` w-full flex items-center justify-between px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-left text-sm ${a?"opacity-50 cursor-not-allowed":"hover:border-gray-400 dark:hover:border-gray-500"} focus:outline-none focus:ring-2 focus:ring-blue-500 - `,children:[h.jsx(I,{className:"flex items-center gap-2",children:y?h.jsxs(h.Fragment,{children:[h.jsx(se,{name:y.faIcon,className:"w-4 h-4 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white",children:y.label})]}):h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:r})}),h.jsx(se,{name:"chevron-down",className:`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${u?"rotate-180":""}`})]}),l&&h.jsx("input",{type:"hidden",name:l,value:e||""}),u&&h.jsxs(I,{className:"absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-72 overflow-hidden",children:[h.jsx(I,{className:"p-2 border-b border-gray-200 dark:border-gray-700",children:h.jsx(_t,{type:"text",value:g,onChange:x=>m(x.target.value),placeholder:s,className:"w-full px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white",autoFocus:!0})}),h.jsx(I,{className:"max-h-56 overflow-y-auto",children:b.length>0?b.map(x=>h.jsxs(ie,{type:"button",onClick:()=>_(x),className:` + `,children:[h.jsx(I,{className:"flex items-center gap-2",children:y?h.jsxs(h.Fragment,{children:[h.jsx(re,{name:y.faIcon,className:"w-4 h-4 text-gray-600 dark:text-gray-400"}),h.jsx(Q,{className:"text-gray-900 dark:text-white",children:y.label})]}):h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:r})}),h.jsx(re,{name:"chevron-down",className:`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform ${u?"rotate-180":""}`})]}),l&&h.jsx("input",{type:"hidden",name:l,value:t||""}),u&&h.jsxs(I,{className:"absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg max-h-72 overflow-hidden",children:[h.jsx(I,{className:"p-2 border-b border-gray-200 dark:border-gray-700",children:h.jsx(kt,{type:"text",value:g,onChange:x=>m(x.target.value),placeholder:s,className:"w-full px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-700 text-gray-900 dark:text-white",autoFocus:!0})}),h.jsx(I,{className:"max-h-56 overflow-y-auto",children:b.length>0?b.map(x=>h.jsxs(oe,{type:"button",onClick:()=>_(x),className:` w-full flex items-center gap-3 px-3 py-2 text-left text-sm ${y?.faIcon===x.faIcon?"bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700"} - `,children:[h.jsx(se,{name:x.faIcon,className:"w-4 h-4"}),h.jsx(Q,{children:x.label}),y?.faIcon===x.faIcon&&h.jsx(se,{name:"check",className:"w-4 h-4 ml-auto text-blue-600"})]},x.value)):h.jsx(I,{className:"px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-center",children:o})})]})]})},fc=({defaultOpen:e=!1,isOpen:t,onToggle:n,className:r="",style:s,children:o,disabled:i=!1,id:a,editorAttrs:l})=>{const[c,d]=v.useState(e),u=t!==void 0,f=u?t:c,g=v.useRef(null),[m,p]=v.useState(e?void 0:0),y=()=>{if(i)return;const _=!f;u||d(_),n?.(_)};v.useEffect(()=>{if(g.current)if(f){p(g.current.scrollHeight);const _=setTimeout(()=>{p(void 0)},300);return()=>clearTimeout(_)}else p(g.current.scrollHeight),requestAnimationFrame(()=>{p(0)})},[f]);const b={};if(v.Children.forEach(o,_=>{if(v.isValidElement(_)){const x=_.props?.slot;x==="trigger"?b.trigger=_:x==="content"&&(b.content=_)}}),!b.trigger&&!b.content){const _=v.Children.toArray(o);_.length>=2?(b.trigger=_[0],b.content=_.slice(1)):_.length===1&&(b.content=_[0])}return h.jsxs(I,{className:`border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${r}`,style:s,id:a,...l,children:[h.jsxs(ie,{type:"button",onClick:y,disabled:i,className:`w-full flex items-center justify-between p-3 text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${i?"opacity-50 cursor-not-allowed":""} ${f?"border-b border-gray-200 dark:border-gray-700":""}`,"aria-expanded":f,children:[h.jsx(I,{className:"flex-1",children:b.trigger}),!b.trigger&&h.jsx("svg",{className:`w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),h.jsx(I,{ref:g,className:"overflow-hidden transition-all duration-300 ease-in-out",style:{height:m!==void 0?`${m}px`:"auto"},children:h.jsx(I,{className:"bg-white dark:bg-gray-800",children:b.content})})]})},go=window.G7Core,dg=go?.useControllableState,ug=(e,t)=>window.G7Core?.t?.(e,t)??e,r_={"":"common.scope_type_all_desc",self:"common.scope_type_self_desc",role:"common.scope_type_role_desc"},s_=({options:e,value:t,onChange:n,disabled:r=!1})=>{const[s,o]=v.useState(!1),[i,a]=v.useState({}),l=v.useRef(null),c=e.find(u=>u.value===t)??e[0];v.useEffect(()=>{if(!s)return;const u=g=>{l.current&&!l.current.contains(g.target)&&o(!1)},f=()=>{if(!l.current)return;const g=l.current.getBoundingClientRect();a({position:"fixed",top:g.bottom+4,right:window.innerWidth-g.right,zIndex:9999})};return document.addEventListener("mousedown",u),window.addEventListener("scroll",f,!0),window.addEventListener("resize",f),()=>{document.removeEventListener("mousedown",u),window.removeEventListener("scroll",f,!0),window.removeEventListener("resize",f)}},[s]);const d=v.useCallback(()=>{if(r||!l.current)return;const u=l.current.getBoundingClientRect();a({position:"fixed",top:u.bottom+4,right:window.innerWidth-u.right,zIndex:9999}),o(f=>!f)},[r]);return h.jsxs(I,{ref:l,className:"relative flex-shrink-0",children:[h.jsxs(I,{role:"button",tabIndex:0,onClick:u=>{u.stopPropagation(),u.preventDefault(),d()},onMouseDown:u=>u.stopPropagation(),onKeyDown:u=>{(u.key==="Enter"||u.key===" ")&&(u.stopPropagation(),u.preventDefault(),d()),u.key==="Escape"&&o(!1)},className:`flex items-center gap-1 text-xs px-2 py-1 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 cursor-pointer select-none ${r?"opacity-50 cursor-not-allowed":"hover:border-blue-400 dark:hover:border-blue-500"}`,children:[h.jsx(Q,{children:c?.label??""}),h.jsx(se,{name:ce.ChevronDown,className:"w-3 h-3 text-gray-400 dark:text-gray-500"})]}),s&&h.jsx(I,{style:i,className:"w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1",children:e.map(u=>{const f=u.value??"",g=r_[f],m=g?ug(g):"",p=u.value===t;return h.jsxs(I,{role:"option","aria-selected":p,onClick:y=>{y.stopPropagation(),y.preventDefault(),n(u.value),o(!1)},onMouseDown:y=>y.stopPropagation(),className:`px-3 py-2 cursor-pointer transition-colors ${p?"bg-blue-50 dark:bg-blue-900/30":"hover:bg-gray-50 dark:hover:bg-gray-700/50"}`,children:[h.jsxs(I,{className:"flex items-center justify-between",children:[h.jsx(Q,{className:`text-xs font-medium ${p?"text-blue-700 dark:text-blue-300":"text-gray-900 dark:text-white"}`,children:u.label}),p&&h.jsx(se,{name:ce.Check,className:"w-3.5 h-3.5 text-blue-600 dark:text-blue-400"})]}),m&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-0.5",children:m})]},f||"__null__")})})]})},o_=(e,t)=>{if(e.length!==t.length)return!1;const n=[...e].sort((s,o)=>s.id-o.id),r=[...t].sort((s,o)=>s.id-o.id);return n.every((s,o)=>s.id===r[o].id&&s.scope_type===r[o].scope_type)},i_=({data:e=[],value:t=[],onChange:n,scopeOptions:r=[],className:s="",disabled:o=!1,desktopColumns:i=2,installedModules:a=[],installedPlugins:l=[],id:c,editorAttrs:d})=>{const u=v.useCallback(E=>{if(n){const D=go?.createChangeEvent?go.createChangeEvent({value:E}):{target:{value:E}};n(D)}},[n]),[f,g]=dg?dg(t,[],u,{isEqual:o_}):(()=>{const[E,D]=v.useState(t),P=v.useRef(n);P.current=n;const L=v.useRef(!1);v.useEffect(()=>{if(L.current){L.current=!1;return}D(t)},[t]);const $=v.useCallback(A=>{L.current=!0,D(O=>{const T=typeof A=="function"?A(O):A;if(P.current){const z=go?.createChangeEvent?go.createChangeEvent({value:T}):{target:{value:T}};P.current(z)}return T})},[]);return[E,$]})(),m=v.useMemo(()=>new Set(f.map(E=>E.id)),[f]),p=v.useCallback(E=>E.is_assignable?[E.id]:E.children.flatMap(D=>p(D)),[]),y=v.useCallback(E=>{const D=p(E);return D.length>0&&D.every(P=>m.has(P))},[m,p]),b=v.useCallback(E=>{const D=p(E),P=D.filter(L=>m.has(L)).length;return P>0&&Pp(E).filter(P=>m.has(P)).length,[m,p]),x=v.useCallback((E,D)=>{o||g(P=>D?[...P,{id:E,scope_type:null}]:P.filter(L=>L.id!==E))},[o,g]),M=v.useCallback((E,D)=>{if(o)return;const P=p(E),L=new Set(P);g($=>{if(D){const A=new Set($.map(T=>T.id)),O=P.filter(T=>!A.has(T)).map(T=>({id:T,scope_type:null}));return[...$,...O]}else return $.filter(A=>!L.has(A.id))})},[o,p,g]),C=v.useCallback((E,D)=>{o||g(P=>P.map(L=>L.id===E?{...L,scope_type:D}:L))},[o,g]),k=E=>{const D=m.has(E.id),P=!!E.resource_route_key,L=D&&P&&r.length>0,$=f.find(A=>A.id===E.id)?.scope_type??null;return h.jsxs(dn,{className:`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${D?"border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900/20":"border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50"} ${o?"opacity-50 cursor-not-allowed":""}`,children:[h.jsx(_t,{type:"checkbox",checked:D,onChange:A=>{A.stopPropagation(),x(E.id,A.target.checked)},disabled:o,className:"w-4 h-4 mt-0.5 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsxs(I,{className:"flex items-center justify-between gap-2",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-900 dark:text-white",children:E.name}),L&&h.jsx(s_,{options:r,value:$,onChange:A=>C(E.id,A),disabled:o})]}),E.description&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-0.5",children:E.description})]})]},E.id)},N=(E,D)=>{const P=y(E),L=b(E),$=_(E),A=E.children.filter(z=>z.is_assignable);if(A.length===0&&E.children.length===0)return null;const O=E.children.some(z=>!z.is_assignable),T=i===1?"grid-cols-1":i===3?"grid-cols-1 md:grid-cols-3":"grid-cols-1 md:grid-cols-2";return h.jsxs(fc,{defaultOpen:!1,className:"border-0 rounded-none",children:[h.jsxs(I,{slot:"trigger",className:"flex items-center gap-3 w-full py-2",children:[h.jsx("input",{type:"checkbox",checked:P,ref:z=>{z&&(z.indeterminate=L)},onChange:z=>{z.stopPropagation(),M(E,z.target.checked)},onClick:z=>z.stopPropagation(),onMouseDown:z=>z.stopPropagation(),disabled:o,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(se,{name:ce.Folder,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:E.name}),h.jsxs(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 ml-1",children:[$,"/",E.leaf_count]}),h.jsx(se,{name:ce.ChevronDown,className:"w-4 h-4 text-gray-400 dark:text-gray-500 ml-auto"})]}),h.jsx(I,{slot:"content",className:`pl-8 grid ${T} gap-2 py-2`,children:O?E.children.map(z=>z.is_assignable?k(z):N(z)):A.map(z=>k(z))})]},E.id)},S=E=>{if(E.extension_type==="core")return"core";if(E.extension_type==="module")return"module";if(E.extension_type==="plugin")return"plugin";const D=E.extension_identifier??E.module??"";return D==="core"||!D?"core":l?.some(L=>L.identifier===D)?"plugin":"module"},w=E=>{const D=y(E),P=b(E),L=_(E),$=S(E);return h.jsxs(fc,{defaultOpen:!1,className:"mb-2",children:[h.jsxs(I,{slot:"trigger",className:"flex items-center gap-3 w-full py-2",children:[h.jsx("input",{type:"checkbox",checked:D,ref:A=>{A&&(A.indeterminate=P)},onChange:A=>{A.stopPropagation(),M(E,A.target.checked)},onClick:A=>A.stopPropagation(),onMouseDown:A=>A.stopPropagation(),disabled:o,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(se,{name:$==="core"?ce.Cog:ce.Cube,className:"w-5 h-5 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"font-medium text-gray-900 dark:text-white",children:E.name}),$!=="core"&&h.jsx(gh,{type:$,identifier:E.extension_identifier??E.module,installedModules:a,installedPlugins:l}),h.jsxs(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 ml-2",children:[L,"/",E.leaf_count]}),h.jsx(se,{name:ce.ChevronDown,className:"w-4 h-4 text-gray-400 dark:text-gray-500 ml-auto"})]}),h.jsx(I,{slot:"content",className:"pl-8 space-y-2 pb-2",children:E.children.map(A=>A.is_assignable?k(A):N(A))})]},E.id)};return e.length===0?h.jsx(I,{className:`text-sm text-gray-400 dark:text-gray-500 py-4 text-center ${s}`,id:c,...d,children:ug("common.no_data")??"데이터가 없습니다."}):h.jsx(I,{className:`space-y-2 ${s}`,id:c,...d,children:e.map(E=>w(E))})};function a_({text:e,keyword:t}){if(!t||!e)return h.jsx(h.Fragment,{children:e});const n=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=e.split(new RegExp(`(${n})`,"gi"));return h.jsx(h.Fragment,{children:r.map((s,o)=>s.toLowerCase()===t.toLowerCase()?h.jsx(Q,{className:"bg-yellow-200 dark:bg-yellow-700 rounded px-0.5",children:s},o):s)})}const l_=({data:e=[],expandedIds:t=[],selectedIds:n=[],searchKeyword:r="",showProductCount:s=!1,selectable:o=!1,className:i="",onToggle:a,onSelectionChange:l,id:c,editorAttrs:d})=>{const u=v.useCallback(y=>{if(!a)return;const b=t.includes(y)?t.filter(_=>_!==y):[...t,y];a(b)},[t,a]),f=v.useCallback((y,b)=>{if(!l)return;const _=b?[...n,y]:n.filter(x=>x!==y);l(_)},[n,l]),g=v.useMemo(()=>{if(!r)return null;const y=new Set,b=_=>{for(const x of _)(x.localized_name||x.name).toLowerCase().includes(r.toLowerCase())&&y.add(x.id),x.children&&b(x.children)};return b(e),y},[e,r]),m=v.useCallback(y=>!g||g.has(y.id)?!0:(y.children??[]).some(b=>m(b)),[g]),p=(y,b=0)=>{const _=(y.children??[]).length>0,x=t.includes(y.id),M=n.includes(y.id),C=y.localized_name||y.name;return r&&!m(y)?null:h.jsxs(I,{"data-testid":`category-node-${y.id}`,children:[h.jsxs(I,{className:`flex items-center gap-2 py-1.5 px-2 rounded cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors ${b>0?"ml-"+Math.min(b*5,20):""}`,style:b>0?{marginLeft:`${b*20}px`}:void 0,children:[_?h.jsx(I,{className:"flex-shrink-0 w-5 h-5 flex items-center justify-center text-gray-400 dark:text-gray-500",onClick:()=>u(y.id),"data-testid":`toggle-${y.id}`,children:h.jsx(se,{name:x?ce.CaretDown:ce.CaretRight,className:"text-xs"})}):h.jsx(I,{className:"flex-shrink-0 w-5 h-5"}),o&&h.jsx(_n,{checked:M,onChange:k=>{k.stopPropagation(),f(y.id,k.target.checked)},className:"flex-shrink-0","data-testid":`checkbox-${y.id}`}),h.jsx(se,{name:ce.Folder,className:`flex-shrink-0 text-sm ${_?"text-yellow-500 dark:text-yellow-400":"text-gray-400 dark:text-gray-500"}`}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300 select-none",onClick:()=>_&&u(y.id),children:h.jsx(a_,{text:C,keyword:r})}),s&&y.products_count!==void 0&&h.jsx(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400 ml-1",children:y.products_count})]}),_&&x&&h.jsx(I,{"data-testid":`children-${y.id}`,children:(y.children??[]).map(k=>p(k,b+1))})]},y.id)};return e.length===0?h.jsx(I,{className:`text-sm text-gray-400 dark:text-gray-500 py-4 text-center ${i}`,id:c,...d,children:"데이터가 없습니다."}):h.jsx(I,{className:`border border-gray-200 dark:border-gray-700 rounded-lg p-2 max-h-80 overflow-y-auto ${i}`,id:c,...d,children:e.map(y=>p(y,0))})},fg="carousel",po="controller",c_="navigation",d_="no-scroll",hc="portal",u_="root",hg="toolbar",f_="counter",mo="fullscreen",gg="slideshow",bo="thumbnails",pg="zoom",ms="loading",gc="playing",yo="error",vo="complete",h_="placeholder",xo=e=>`active-slide-${e}`,g_=xo(ms),p_=xo(gc),m_=xo(yo),b_=xo(vo),mg="fullsize",bs="flex_center",y_="no_scroll",bg="no_scroll_padding",pc="slide",yg="slide_wrapper",v_="slide_wrapper_interactive",fr="prev",hr="next",mc="swipe",ys="close",vg="onPointerDown",xg="onPointerMove",wg="onPointerUp",kg="onPointerLeave",Cg="onPointerCancel",bc="onKeyDown",x_="onKeyUp",yc="onWheel",w_="Escape",k_="ArrowLeft",C_="ArrowRight",S_="button",Wi="icon",Sg="contain",_g="cover",Eg="Unknown action type",Mg="yarl__";function Wt(...e){return e.filter(Boolean).join(" ")}function Te(e){return`${Mg}${e}`}function gt(e){return`--${Mg}${e}`}function Er(e,t){return`${e}${t?`_${t}`:""}`}function Mr(e){return t=>Er(e,t)}function Nr(e,t){var n;return(n=e?.[t])!==null&&n!==void 0?n:t}function Ng(e,t,n){return Nr(e,"{index} of {total}").replace(/\{index}/g,`${kc(n,t.length)+1}`).replace(/\{total}/g,`${t.length}`)}function vs(...e){return()=>{e.forEach(t=>{t()})}}function hn(e,t,n){return()=>{const r=F.useContext(n);if(!r)throw new Error(`${e} must be used within a ${t}.Provider`);return r}}function vc(){return typeof window<"u"}function xc(e,t=0){const n=10**t;return Math.round((e+Number.EPSILON)*n)/n}function gr(e){return e.type===void 0||e.type==="image"}function wc(e,t){return e.imageFit===_g||e.imageFit!==Sg&&t===_g}function Gi(e){return typeof e=="string"?Number.parseInt(e,10):e}function Yi(e){if(typeof e=="number")return{pixel:e};if(typeof e=="string"){const t=Gi(e);return e.endsWith("%")?{percent:t}:{pixel:t}}return{pixel:0}}function __(e,t){const n=Yi(t),r=n.percent!==void 0?e.width/100*n.percent:n.pixel;return{width:Math.max(e.width-2*r,0),height:Math.max(e.height-2*r,0)}}function E_(){return(vc()?window?.devicePixelRatio:void 0)||1}function kc(e,t){return t>0?(e%t+t)%t:0}function Cc(e){return e.length>0}function Sc(e,t){return e[kc(t,e.length)]}function _c(e,t){return Cc(e)?Sc(e,t):void 0}function Tg(e){return gr(e)?e.src:void 0}function Xi(e,t,n){if(!n)return e;const{buttons:r,...s}=e,o=r.findIndex(a=>a===t),i=F.isValidElement(n)?F.cloneElement(n,{key:t},null):n;if(o>=0){const a=[...r];return a.splice(o,1,i),{buttons:a,...s}}return{buttons:[i,...r],...s}}function Og(e,t,n=0){return Math.min(e.preload,Math.max(e.finite?t.length-1:Math.floor(t.length/2),n))}const M_=Number(F.version.split(".")[0])>=19;function N_(e){return{inert:M_?e:e?"":void 0}}function T_(e){e.scrollTop}const Ec={open:!1,close:()=>{},index:0,slides:[],render:{},plugins:[],toolbar:{buttons:[ys]},labels:{},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},carousel:{finite:!1,preload:2,padding:"16px",spacing:"30%",imageFit:Sg,imageProps:{}},controller:{ref:null,focus:!0,aria:!1,touchAction:"none",closeOnPullUp:!1,closeOnPullDown:!1,closeOnBackdropClick:!1,closeOnEscape:!0,preventDefaultWheelX:!0,preventDefaultWheelY:!1,disableSwipeNavigation:!1},portal:{},noScroll:{disabled:!1},on:{},styles:{},className:""};function mn(e,t){return{name:e,component:t}}function Lt(e,t){return{module:e,children:t}}function Ag(e,t,n){return e.module.name===t?n(e):e.children?[Lt(e.module,e.children.flatMap(r=>{var s;return(s=Ag(r,t,n))!==null&&s!==void 0?s:[]}))]:[e]}function xs(e,t,n){return e.flatMap(r=>{var s;return(s=Ag(r,t,n))!==null&&s!==void 0?s:[]})}function O_(e,t=[],n=[]){let r=e;const s=g=>{const m=[...r];for(;m.length>0;){const p=m.pop();if(p?.module.name===g)return!0;p?.children&&m.push(...p.children)}return!1},o=(g,m)=>{if(g===""){r=[Lt(m,r)];return}r=xs(r,g,p=>[Lt(m,[p])])},i=(g,m)=>{r=xs(r,g,p=>[Lt(p.module,[Lt(m,p.children)])])},a=(g,m,p)=>{r=xs(r,g,y=>{var b;return[Lt(y.module,[...p?[Lt(m)]:[],...(b=y.children)!==null&&b!==void 0?b:[],...p?[]:[Lt(m)]])]})},l=(g,m,p)=>{r=xs(r,g,y=>[...p?[Lt(m)]:[],y,...p?[]:[Lt(m)]])},c=g=>{i(po,g)},d=(g,m)=>{r=xs(r,g,p=>[Lt(m,p.children)])},u=g=>{r=xs(r,g,m=>m.children)},f=g=>{n.push(g)};return t.forEach(g=>{g({contains:s,addParent:o,append:i,addChild:a,addSibling:l,addModule:c,replace:d,remove:u,augment:f})}),{config:r,augmentation:g=>n.reduce((m,p)=>p(m),g)}}const Dg=F.createContext(null),Mc=hn("useA11yContext","A11yContext",Dg);function A_({children:e}){const[t,n]=F.useState(!1),[r,s]=F.useState(!1),o=F.useMemo(()=>({focusWithin:t,trackFocusWithin:(a,l)=>{const c=d=>u=>{var f;u.currentTarget.contains(u.relatedTarget)||n(d),(f=d?a:l)===null||f===void 0||f(u)};return{onFocus:c(!0),onBlur:c(!1)}},autoPlaying:r,setAutoPlaying:s}),[t,r]);return F.createElement(Dg.Provider,{value:o},e)}const Ig=F.createContext(null),ws=hn("useDocument","DocumentContext",Ig);function D_({nodeRef:e,children:t}){const n=F.useMemo(()=>{const r=o=>{var i;return((i=o||e.current)===null||i===void 0?void 0:i.ownerDocument)||document};return{getOwnerDocument:r,getOwnerWindow:o=>{var i;return((i=r(o))===null||i===void 0?void 0:i.defaultView)||window}}},[e]);return F.createElement(Ig.Provider,{value:n},t)}const Lg=F.createContext(null),ks=hn("useEvents","EventsContext",Lg);function I_({children:e}){const[t]=F.useState({});F.useEffect(()=>()=>{Object.keys(t).forEach(r=>delete t[r])},[t]);const n=F.useMemo(()=>{const r=(i,a)=>{var l;(l=t[i])===null||l===void 0||l.splice(0,t[i].length,...t[i].filter(c=>c!==a))};return{publish:(...[i,a])=>{var l;(l=t[i])===null||l===void 0||l.forEach(c=>c(a))},subscribe:(i,a)=>(t[i]||(t[i]=[]),t[i].push(a),()=>r(i,a)),unsubscribe:r}},[t]);return F.createElement(Lg.Provider,{value:n},e)}const Pg=F.createContext(null),Pt=hn("useLightboxProps","LightboxPropsContext",Pg);function $g({children:e,...t}){return F.createElement(Pg.Provider,{value:t},e)}const Fg=F.createContext(null),bn=hn("useLightboxState","LightboxStateContext",Fg),zg=F.createContext(null),L_=hn("useLightboxDispatch","LightboxDispatchContext",zg);function P_(e,t){switch(t.type){case"swipe":{const{slides:n}=e,r=t?.increment||0,s=e.globalIndex+r,o=kc(s,n.length),i=_c(n,o),a=r||t.duration!==void 0?{increment:r,duration:t.duration,easing:t.easing}:void 0;return{slides:n,currentIndex:o,globalIndex:s,currentSlide:i,animation:a}}case"update":return t.slides!==e.slides||t.index!==e.currentIndex?{slides:t.slides,currentIndex:t.index,globalIndex:t.index,currentSlide:_c(t.slides,t.index)}:e;default:throw new Error(Eg)}}function $_({slides:e,index:t,children:n}){const[r,s]=F.useReducer(P_,{slides:e,currentIndex:t,globalIndex:t,currentSlide:_c(e,t)}),[o,i]=F.useState(e),[a,l]=F.useState(t);(e!==o||t!==a)&&(i(e),l(t),s({type:"update",slides:e,index:t}));const c=F.useMemo(()=>({...r,state:r,dispatch:s}),[r,s]);return F.createElement(zg.Provider,{value:s},F.createElement(Fg.Provider,{value:c},n))}const Vg=F.createContext(null),F_=hn("useRTLContext","RTLContext",Vg);function z_({isRTL:e,children:t}){const n=F.useMemo(()=>({isRTL:e}),[e]);return F.createElement(Vg.Provider,{value:n},t)}const Hg=F.createContext(null),wo=hn("useTimeouts","TimeoutsContext",Hg);function V_({children:e}){const[t]=F.useState([]);F.useEffect(()=>()=>{t.forEach(r=>window.clearTimeout(r)),t.splice(0,t.length)},[t]);const n=F.useMemo(()=>{const r=i=>{t.splice(0,t.length,...t.filter(a=>a!==i))};return{setTimeout:(i,a)=>{const l=window.setTimeout(()=>{r(l),i()},a);return t.push(l),l},clearTimeout:i=>{i!==void 0&&(r(i),window.clearTimeout(i))}}},[t]);return F.createElement(Hg.Provider,{value:n},e)}const Cs=F.forwardRef(function({label:t,className:n,icon:r,renderIcon:s,onClick:o,style:i,...a},l){const{styles:c,labels:d}=Pt(),u=Nr(d,t);return F.createElement("button",{ref:l,type:"button",title:u,"aria-label":u,className:Wt(Te(S_),n),onClick:o,style:{...i,...c.button},...a},s?s():F.createElement(r,{className:Te(Wi),style:c.icon}))});function Ug(e,t){const n=r=>F.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",width:"24",height:"24","aria-hidden":"true",focusable:"false",...r},t);return n.displayName=e,n}function tn(e,t){return Ug(e,F.createElement("g",{fill:"currentColor"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),t))}function H_(e,t){return Ug(e,F.createElement(F.Fragment,null,F.createElement("defs",null,F.createElement("mask",{id:"strike"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"white"}),F.createElement("path",{d:"M0 0L24 24",stroke:"black",strokeWidth:4}))),F.createElement("path",{d:"M0.70707 2.121320L21.878680 23.292883",stroke:"currentColor",strokeWidth:2}),F.createElement("g",{fill:"currentColor",mask:"url(#strike)"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),t)))}const U_=tn("Close",F.createElement("path",{d:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"})),B_=tn("Previous",F.createElement("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"})),j_=tn("Next",F.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),W_=tn("Loading",F.createElement(F.Fragment,null,Array.from({length:8}).map((e,t,n)=>F.createElement("line",{key:t,x1:"12",y1:"6.5",x2:"12",y2:"1.8",strokeLinecap:"round",strokeWidth:"2.6",stroke:"currentColor",strokeOpacity:1/n.length*(t+1),transform:`rotate(${360/n.length*t}, 12, 12)`})))),G_=tn("Error",F.createElement("path",{d:"M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z"})),Nn=vc()?F.useLayoutEffect:F.useEffect;function Nc(){const[e,t]=F.useState(!1);return F.useEffect(()=>{var n,r;const s=(n=window.matchMedia)===null||n===void 0?void 0:n.call(window,"(prefers-reduced-motion: reduce)");t(s?.matches);const o=i=>t(i.matches);return(r=s?.addEventListener)===null||r===void 0||r.call(s,"change",o),()=>{var i;return(i=s?.removeEventListener)===null||i===void 0?void 0:i.call(s,"change",o)}},[]),e}function Y_(e){let t=0,n=0,r=0;const o=window.getComputedStyle(e).transform.match(/matrix.*\((.+)\)/);if(o){const i=o[1].split(",").map(Gi);i.length===6?(t=i[4],n=i[5]):i.length===16&&(t=i[12],n=i[13],r=i[14])}return{x:t,y:n,z:r}}function Tc(e,t){const n=F.useRef(void 0),r=F.useRef(void 0),s=Nc();return Nn(()=>{var o,i,a;if(e.current&&n.current!==void 0&&!s){const{keyframes:l,duration:c,easing:d,onfinish:u}=t(n.current,e.current.getBoundingClientRect(),Y_(e.current))||{};if(l&&c){(o=r.current)===null||o===void 0||o.cancel(),r.current=void 0;try{r.current=(a=(i=e.current).animate)===null||a===void 0?void 0:a.call(i,l,{duration:c,easing:d})}catch(f){console.error(f)}r.current&&(r.current.onfinish=()=>{r.current=void 0,u?.()})}}n.current=void 0}),{prepareAnimation:o=>{n.current=o},isAnimationPlaying:()=>{var o;return((o=r.current)===null||o===void 0?void 0:o.playState)==="running"}}}function Bg(){const e=F.useRef(null),t=F.useRef(void 0),[n,r]=F.useState();return{setContainerRef:F.useCallback(o=>{e.current=o,t.current&&(t.current.disconnect(),t.current=void 0);const i=()=>{if(o){const a=window.getComputedStyle(o),l=c=>parseFloat(c)||0;r({width:Math.round(o.clientWidth-l(a.paddingLeft)-l(a.paddingRight)),height:Math.round(o.clientHeight-l(a.paddingTop)-l(a.paddingBottom))})}else r(void 0)};i(),o&&typeof ResizeObserver<"u"&&(t.current=new ResizeObserver(i),t.current.observe(o))},[]),containerRef:e,containerRect:n}}function Ki(){const e=F.useRef(void 0),{setTimeout:t,clearTimeout:n}=wo();return F.useCallback((r,s)=>{n(e.current),e.current=t(r,s>0?s:0)},[t,n])}function Xe(e){const t=F.useRef(e);return Nn(()=>{t.current=e}),F.useCallback((...n)=>{var r;return(r=t.current)===null||r===void 0?void 0:r.call(t,...n)},[])}function jg(e,t){typeof e=="function"?e(t):e&&(e.current=t)}function Oc(e,t){return F.useMemo(()=>e==null&&t==null?null:n=>{jg(e,n),jg(t,n)},[e,t])}function Wg(e,t=!1){const n=F.useRef(!1);Nn(()=>{t&&n.current&&(n.current=!1,e())},[t,e]);const r=F.useCallback(()=>{n.current=!0},[]),s=F.useCallback(()=>{n.current=!1},[]);return{onFocus:r,onBlur:s}}function qi(){return F_().isRTL}function Gg(){const[e]=F.useState({}),t=F.useCallback((s,o)=>{var i;(i=e[s])===null||i===void 0||i.forEach(a=>{o.isPropagationStopped()||a(o)})},[e]),n=F.useMemo(()=>({onPointerDown:s=>t(vg,s),onPointerMove:s=>t(xg,s),onPointerUp:s=>t(wg,s),onPointerLeave:s=>t(kg,s),onPointerCancel:s=>t(Cg,s),onKeyDown:s=>t(bc,s),onKeyUp:s=>t(x_,s),onWheel:s=>t(yc,s)}),[t]),r=F.useCallback((s,o)=>(e[s]||(e[s]=[]),e[s].unshift(o),()=>{const i=e[s];i&&i.splice(0,i.length,...i.filter(a=>a!==o))}),[e]);return{registerSensors:n,subscribeSensors:r}}function Yg(e,t){const n=F.useRef(0),r=Ki(),s=Xe((...o)=>{n.current=Date.now(),e(o)});return F.useCallback((...o)=>{r(()=>{s(o)},t-(Date.now()-n.current))},[t,s,r])}const Ac=Mr("slide"),Dc=Mr("slide_image");function Tr({slide:e,offset:t,render:n,rect:r,imageFit:s,imageProps:o,onClick:i,onLoad:a,onError:l,style:c}){var d,u,f,g,m,p,y,b;const[_,x]=F.useState(ms),{publish:M}=ks(),{setTimeout:C}=wo(),k=F.useRef(null);F.useEffect(()=>{t===0&&M(xo(_))},[t,_,M]);const N=Xe(U=>{("decode"in U?U.decode():Promise.resolve()).catch(()=>{}).then(()=>{U.parentNode&&(x(vo),C(()=>{a?.(U)},0))})}),S=F.useCallback(U=>{k.current=U,U?.complete&&N(U)},[N]),w=F.useCallback(U=>{N(U.currentTarget)},[N]),E=Xe(()=>{x(yo),l?.()}),D=wc(e,s),P=(U,W)=>Number.isFinite(U)?U:W,L=P(Math.max(...((u=(d=e.srcSet)===null||d===void 0?void 0:d.map(U=>U.width))!==null&&u!==void 0?u:[]).concat(e.width?[e.width]:[]).filter(Boolean)),((f=k.current)===null||f===void 0?void 0:f.naturalWidth)||0),$=P(Math.max(...((m=(g=e.srcSet)===null||g===void 0?void 0:g.map(U=>U.height))!==null&&m!==void 0?m:[]).concat(e.height?[e.height]:[]).filter(Boolean)),((p=k.current)===null||p===void 0?void 0:p.naturalHeight)||0),A=L&&$?{maxWidth:`min(${L}px, 100%)`,maxHeight:`min(${$}px, 100%)`}:{maxWidth:"100%",maxHeight:"100%"},O=(y=e.srcSet)===null||y===void 0?void 0:y.slice().sort((U,W)=>U.width-W.width).map(U=>`${U.src} ${U.width}w`).join(", "),T=()=>r&&!D&&e.width&&e.height?r.height/e.height*e.width:Number.MAX_VALUE,z=O&&r&&vc()?`${Math.round(Math.min(T(),r.width))}px`:void 0,{style:H,className:j,...G}=(typeof o=="function"?o(e):o)||{};return F.createElement(F.Fragment,null,F.createElement("img",{ref:S,onLoad:w,onError:E,onClick:i,draggable:!1,className:Wt(Te(Dc()),D&&Te(Dc("cover")),_!==vo&&Te(Dc("loading")),j),style:{...A,...c,...H},...G,alt:(b=e.alt)!==null&&b!==void 0?b:"",sizes:z,srcSet:O,src:e.src}),_!==vo&&F.createElement("div",{className:Te(Ac(h_))},_===ms&&(n?.iconLoading?n.iconLoading():F.createElement(W_,{className:Wt(Te(Wi),Te(Ac(ms)))})),_===yo&&(n?.iconError?n.iconError():F.createElement(G_,{className:Wt(Te(Wi),Te(Ac(yo)))}))))}const X_=F.forwardRef(function({className:t,children:n,onFocus:r,onBlur:s,...o},i){const a=F.useRef(null),[l,c]=F.useState(!1),{trackFocusWithin:d}=Mc(),u=Xe(()=>{if(a.current){const f=window.getComputedStyle(a.current).direction==="rtl";f!==l&&c(f)}});return F.useEffect(u),F.createElement(D_,{nodeRef:a},F.createElement(z_,{isRTL:l},F.createElement("div",{ref:Oc(i,a),className:Wt(Te("root"),t),...d(r,s),...o},n)))});var Gt;(function(e){e[e.NONE=0]="NONE",e[e.SWIPE=1]="SWIPE",e[e.PULL=2]="PULL",e[e.ANIMATION=3]="ANIMATION"})(Gt||(Gt={}));function Xg(e,t,n,r,s){F.useEffect(()=>s?()=>{}:vs(e(vg,t),e(xg,n),e(wg,r),e(kg,r),e(Cg,r)),[e,t,n,r,s])}var Tn;(function(e){e[e.NONE=0]="NONE",e[e.SWIPE=1]="SWIPE",e[e.PULL=2]="PULL"})(Tn||(Tn={}));const Ic=30;function K_({disableSwipeNavigation:e,closeOnBackdropClick:t},n,r,s,o,i,a,l,c,d,u,f,g,m,p,y){const b=F.useRef(0),_=F.useRef([]),x=F.useRef(void 0),M=F.useRef(0),C=F.useRef(Tn.NONE),k=F.useCallback(L=>{x.current===L.pointerId&&(x.current=void 0,C.current=Tn.NONE);const $=_.current;$.splice(0,$.length,...$.filter(A=>A.pointerId!==L.pointerId))},[]),N=F.useCallback(L=>{k(L),L.persist(),_.current.push(L)},[k]),S=F.useCallback(L=>_.current.find(({pointerId:$})=>L.pointerId===$),[]),w=Xe(L=>{N(L)}),E=(L,$)=>u&&L>$||d&&L<-$,D=Xe(L=>{const $=S(L);if($)if(x.current===L.pointerId){const A=Date.now()-M.current,O=b.current;C.current===Tn.SWIPE?Math.abs(O)>.3*s||Math.abs(O)>5&&A{const $=S(L);if($){const A=x.current===L.pointerId;if(L.buttons===0){A&&b.current!==0?D(L):k($);return}const O=L.clientX-$.clientX,T=L.clientY-$.clientY;if(x.current===void 0){const z=H=>{N(L),x.current=L.pointerId,M.current=Date.now(),C.current=H};Math.abs(O)>Math.abs(T)&&Math.abs(O)>Ic&&r(O)?e||(z(Tn.SWIPE),i()):Math.abs(T)>Math.abs(O)&&E(T,Ic)&&(z(Tn.PULL),f())}else A&&(C.current===Tn.SWIPE?(b.current=O,a(O)):C.current===Tn.PULL&&(b.current=T,g(T)))}});Xg(n,w,P,D)}function q_({preventDefaultWheelX:e,preventDefaultWheelY:t}){const n=F.useRef(null),r=Xe(s=>{const o=Math.abs(s.deltaX)>Math.abs(s.deltaY);(o&&e||!o&&t||s.ctrlKey)&&s.preventDefault()});return F.useCallback(s=>{var o;s?s.addEventListener("wheel",r,{passive:!1}):(o=n.current)===null||o===void 0||o.removeEventListener("wheel",r),n.current=s},[r])}function Z_(e,t,n,r,s,o,i,a,l){const c=F.useRef(0),d=F.useRef(0),u=F.useRef(void 0),f=F.useRef(void 0),g=F.useRef(0),m=F.useRef(void 0),p=F.useRef(0),{setTimeout:y,clearTimeout:b}=wo(),_=F.useCallback(()=>{u.current&&(b(u.current),u.current=void 0)},[b]),x=F.useCallback(()=>{f.current&&(b(f.current),f.current=void 0)},[b]),M=Xe(()=>{e!==Gt.SWIPE&&(c.current=0,p.current=0,_(),x())});F.useEffect(M,[e,M]);const C=Xe(N=>{f.current=void 0,c.current===N&&l(c.current)}),k=Xe(N=>{if(N.ctrlKey||Math.abs(N.deltaY)>Math.abs(N.deltaX))return;const S=w=>{g.current=w,b(m.current),m.current=w>0?y(()=>{g.current=0,m.current=void 0},300):void 0};if(e===Gt.NONE){if(Math.abs(N.deltaX)<=1.2*Math.abs(g.current)){S(N.deltaX);return}if(!n(-N.deltaX))return;if(d.current+=N.deltaX,_(),Math.abs(d.current)>30)d.current=0,S(0),p.current=Date.now(),o();else{const w=d.current;u.current=y(()=>{u.current=void 0,w===d.current&&(d.current=0)},s)}}else if(e===Gt.SWIPE){let w=c.current-N.deltaX;if(w=Math.min(Math.abs(w),r)*Math.sign(w),c.current=w,i(w),x(),Math.abs(w)>.2*r){S(N.deltaX),a(w,Date.now()-p.current);return}f.current=y(()=>C(w),2*s)}else S(N.deltaX)});F.useEffect(()=>t(yc,k),[t,k])}const Kg=Mr("container"),qg=F.createContext(null),On=hn("useController","ControllerContext",qg);function Q_({children:e,...t}){var n;const{carousel:r,animation:s,controller:o,on:i,styles:a,render:l}=t,{closeOnPullUp:c,closeOnPullDown:d,preventDefaultWheelX:u,preventDefaultWheelY:f}=o,[g,m]=F.useState(),p=bn(),y=L_(),[b,_]=F.useState(Gt.NONE),x=F.useRef(0),M=F.useRef(0),C=F.useRef(1),{registerSensors:k,subscribeSensors:N}=Gg(),{subscribe:S,publish:w}=ks(),E=Ki(),D=Ki(),P=Ki(),{containerRef:L,setContainerRef:$,containerRect:A}=Bg(),O=Oc(q_({preventDefaultWheelX:u,preventDefaultWheelY:f}),$),T=F.useRef(null),z=Oc(T,void 0),{getOwnerDocument:H}=ws(),j=qi(),G=oe=>(j?-1:1)*(typeof oe=="number"?oe:1),U=Xe(()=>{var oe;return(oe=L.current)===null||oe===void 0?void 0:oe.focus()}),W=Xe(()=>t),Y=Xe(()=>p),V=F.useCallback(oe=>w(fr,oe),[w]),X=F.useCallback(oe=>w(hr,oe),[w]),K=F.useCallback(()=>w(ys),[w]),q=oe=>!(r.finite&&(G(oe)>0&&p.currentIndex===0||G(oe)<0&&p.currentIndex===p.slides.length-1)),ne=oe=>{var Me;x.current=oe,(Me=L.current)===null||Me===void 0||Me.style.setProperty(gt("swipe_offset"),`${Math.round(oe)}px`)},ee=oe=>{var Me,Ae;M.current=oe,C.current=(()=>{const pt=d&&oe>0?oe:c&&oe<0?-oe:0;return Math.min(Math.max(xc(1-pt/60*(1-.5),2),.5),1)})(),(Me=L.current)===null||Me===void 0||Me.style.setProperty(gt("pull_offset"),`${Math.round(oe)}px`),(Ae=L.current)===null||Ae===void 0||Ae.style.setProperty(gt("pull_opacity"),`${C.current}`)},{prepareAnimation:de}=Tc(T,(oe,Me,Ae)=>{if(T.current&&A)return{keyframes:[{transform:`translate(0, ${oe.rect.y-Me.y+Ae.y}px)`,opacity:oe.opacity},{transform:"translate(0, 0)",opacity:1}],duration:oe.duration,easing:s.easing.fade}}),J=(oe,Me)=>{if(c||d){ee(oe);let Ae=0;T.current&&(Ae=s.fade*(Me?2:1),de({rect:T.current.getBoundingClientRect(),opacity:C.current,duration:Ae})),P(()=>{ee(0),_(Gt.NONE)},Ae),_(Gt.ANIMATION),Me||K()}},{prepareAnimation:le,isAnimationPlaying:pe}=Tc(T,(oe,Me,Ae)=>{var De;if(T.current&&A&&(!((De=p.animation)===null||De===void 0)&&De.duration)){const Ke=Yi(r.spacing),pt=(Ke.percent?Ke.percent*A.width/100:Ke.pixel)||0;return{keyframes:[{transform:`translate(${G(p.globalIndex-oe.index)*(A.width+pt)+oe.rect.x-Me.x+Ae.x}px, 0)`},{transform:"translate(0, 0)"}],duration:p.animation.duration,easing:p.animation.easing}}}),ae=Xe(oe=>{var Me,Ae;const De=oe.offset||0,Ke=De?s.swipe:(Me=s.navigation)!==null&&Me!==void 0?Me:s.swipe,pt=!De&&!pe()?s.easing.navigation:s.easing.swipe;let{direction:mt}=oe;const je=(Ae=oe.count)!==null&&Ae!==void 0?Ae:1;let tt=Gt.ANIMATION,We=Ke*je;if(!mt){const ue=A?.width,me=oe.duration||0,we=ue?Ke/ue*Math.abs(De):Ke;je!==0?(me0?fr:hr):We=Ke/2}let re=0;mt===fr?q(G(1))?re=-je:(tt=Gt.NONE,We=Ke):mt===hr&&(q(G(-1))?re=je:(tt=Gt.NONE,We=Ke)),We=Math.round(We),D(()=>{ne(0),_(Gt.NONE)},We),T.current&&le({rect:T.current.getBoundingClientRect(),index:p.globalIndex}),_(tt),w(mc,{type:"swipe",increment:re,duration:We,easing:pt})});F.useEffect(()=>{var oe,Me;!((oe=p.animation)===null||oe===void 0)&&oe.increment&&(!((Me=p.animation)===null||Me===void 0)&&Me.duration)&&E(()=>y({type:"swipe",increment:0}),p.animation.duration)},[p.animation,y,E]);const ye=[N,q,A?.width||0,s.swipe,()=>_(Gt.SWIPE),oe=>ne(oe),(oe,Me)=>ae({offset:oe,duration:Me,count:1}),oe=>ae({offset:oe,count:0})],he=[()=>{d&&_(Gt.PULL)},oe=>ee(oe),oe=>J(oe),oe=>J(oe,!0)];K_(o,...ye,c,d,...he,K),Z_(b,...ye);const ge=Xe(()=>{o.focus&&H().querySelector(`.${Te(hc)} .${Te(Kg())}`)&&U()});F.useEffect(ge,[ge]);const ke=Xe(()=>{var oe;(oe=i.view)===null||oe===void 0||oe.call(i,{index:p.currentIndex})});F.useEffect(ke,[p.globalIndex,ke]),F.useEffect(()=>vs(S(fr,oe=>ae({direction:fr,...oe})),S(hr,oe=>ae({direction:hr,...oe})),S(mc,oe=>y(oe))),[S,ae,y]);const Fe=F.useMemo(()=>({prev:V,next:X,close:K,focus:U,slideRect:A?__(A,r.padding):{width:0,height:0},containerRect:A||{width:0,height:0},subscribeSensors:N,containerRef:L,setCarouselRef:z,toolbarWidth:g,setToolbarWidth:m}),[V,X,K,U,N,A,L,z,g,m,r.padding]);return F.useImperativeHandle(o.ref,()=>({prev:V,next:X,close:K,focus:U,getLightboxProps:W,getLightboxState:Y}),[V,X,K,U,W,Y]),F.createElement("div",{ref:O,className:Wt(Te(Kg()),Te(bs)),style:{...b===Gt.SWIPE?{[gt("swipe_offset")]:`${Math.round(x.current)}px`}:null,...b===Gt.PULL?{[gt("pull_offset")]:`${Math.round(M.current)}px`,[gt("pull_opacity")]:`${C.current}`}:null,...o.touchAction!=="none"?{[gt("controller_touch_action")]:o.touchAction}:null,...a.container},tabIndex:-1,...k},A&&F.createElement(qg.Provider,{value:Fe},e,(n=l.controls)===null||n===void 0?void 0:n.call(l)))}const J_=mn(po,Q_);function Or(e){return Er(fg,e)}function Zg(e){return Er(pc,e)}function R_({slide:e,offset:t}){const n=F.useRef(null),{currentIndex:r,slides:s}=bn(),{slideRect:o,focus:i}=On(),{render:a,carousel:{imageFit:l,imageProps:c},on:{click:d},styles:{slide:u},labels:f}=Pt(),{getOwnerDocument:g}=ws(),m=t!==0;F.useEffect(()=>{var y;m&&(!((y=n.current)===null||y===void 0)&&y.contains(g().activeElement))&&i()},[m,i,g]);const p=()=>{var y,b,_,x;let M=(y=a.slide)===null||y===void 0?void 0:y.call(a,{slide:e,offset:t,rect:o});return!M&&gr(e)&&(M=F.createElement(Tr,{slide:e,offset:t,render:a,rect:o,imageFit:l,imageProps:c,onClick:m?void 0:()=>d?.({index:r})})),M?F.createElement(F.Fragment,null,(b=a.slideHeader)===null||b===void 0?void 0:b.call(a,{slide:e}),((_=a.slideContainer)!==null&&_!==void 0?_:(({children:C})=>C))({slide:e,children:M}),(x=a.slideFooter)===null||x===void 0?void 0:x.call(a,{slide:e})):null};return F.createElement("div",{ref:n,className:Wt(Te(Zg()),!m&&Te(Zg("current")),Te(bs)),...N_(m),style:u,role:"group","aria-roledescription":Nr(f,"Slide"),"aria-label":Ng(f,s,r+t)},p())}function e2(){const e=Pt().styles.slide;return F.createElement("div",{className:Te(pc),style:e})}function t2({carousel:e,labels:t}){const{slides:n,currentIndex:r,globalIndex:s}=bn(),{setCarouselRef:o}=On(),{autoPlaying:i,focusWithin:a}=Mc(),l=Yi(e.spacing),c=Yi(e.padding),d=Og(e,n,1),u=[];if(Cc(n))for(let f=r-d;f<=r+d;f+=1){const g=Sc(n,f),m=s-r+f,p=e.finite&&(f<0||f>n.length-1);u.push(p?{key:m}:{key:[`${m}`,Tg(g)].filter(Boolean).join("|"),offset:f-r,slide:g})}return F.createElement("div",{ref:o,className:Wt(Te(Or()),u.length>0&&Te(Or("with_slides"))),style:{[`${gt(Or("slides_count"))}`]:u.length,[`${gt(Or("spacing_px"))}`]:l.pixel||0,[`${gt(Or("spacing_percent"))}`]:l.percent||0,[`${gt(Or("padding_px"))}`]:c.pixel||0,[`${gt(Or("padding_percent"))}`]:c.percent||0},role:"region","aria-live":i&&!a?"off":"polite","aria-roledescription":Nr(t,"Carousel"),"aria-label":Nr(t,"Photo gallery")},u.map(({key:f,slide:g,offset:m})=>g?F.createElement(R_,{key:f,slide:g,offset:m}):F.createElement(e2,{key:f})))}const n2=mn(fg,t2);function Qg(){const{carousel:e}=Pt(),{slides:t,currentIndex:n}=bn(),r=t.length===0||e.finite&&n===0,s=t.length===0||e.finite&&n===t.length-1;return{prevDisabled:r,nextDisabled:s}}function Jg(e){var t;const n=qi(),{publish:r}=ks(),{animation:s,controller:o}=Pt(),{prevDisabled:i,nextDisabled:a}=Qg(),l=((t=s.navigation)!==null&&t!==void 0?t:s.swipe)/2,c=Yg(()=>r(fr),l),d=Yg(()=>r(hr),l),u=Xe(f=>{switch(f.key){case w_:if(!o.closeOnEscape)return;r(ys);break;case k_:(n?a:i)||(n?d:c)();break;case C_:(n?i:a)||(n?c:d)();break;default:return}f.stopPropagation()});F.useEffect(()=>e(bc,u),[e,u])}function Rg({label:e,icon:t,renderIcon:n,action:r,onClick:s,disabled:o,style:i}){return F.createElement(Cs,{label:e,icon:t,renderIcon:n,className:Te(`navigation_${r}`),disabled:o,onClick:s,style:i,...Wg(On().focus,o)})}function r2({render:{buttonPrev:e,buttonNext:t,iconPrev:n,iconNext:r},styles:s}){const{prev:o,next:i,subscribeSensors:a}=On(),{prevDisabled:l,nextDisabled:c}=Qg();return Jg(a),F.createElement(F.Fragment,null,e?e():F.createElement(Rg,{label:"Previous",action:fr,icon:B_,renderIcon:n,style:s.navigationPrev,disabled:l,onClick:o}),t?t():F.createElement(Rg,{label:"Next",action:hr,icon:j_,renderIcon:r,style:s.navigationNext,disabled:c,onClick:i}))}const s2=mn(c_,r2),ep=Te(y_),o2=Te(bg);function i2(e){return"style"in e}function tp(e,t,n){const r=window.getComputedStyle(e),s=n?"padding-left":"padding-right",o=n?r.paddingLeft:r.paddingRight,i=e.style.getPropertyValue(s);return e.style.setProperty(s,`${(Gi(o)||0)+t}px`),()=>{i?e.style.setProperty(s,i):e.style.removeProperty(s)}}function a2({noScroll:{disabled:e},children:t}){const n=qi(),{getOwnerDocument:r,getOwnerWindow:s}=ws();return F.useEffect(()=>{if(e)return()=>{};const o=[],i=s(),{body:a,documentElement:l}=r(),c=Math.round(i.innerWidth-l.clientWidth);if(c>0){o.push(tp(a,c,n));const d=a.getElementsByTagName("*");for(let u=0;u{a.classList.remove(ep),o.forEach(d=>d())}},[n,e,r,s]),F.createElement(F.Fragment,null,t)}const l2=mn(d_,a2);function np(e){return Er(hc,e)}function rp(e,t,n){const r=e.getAttribute(t);return e.setAttribute(t,n),()=>{r?e.setAttribute(t,r):e.removeAttribute(t)}}function c2({portal:{root:e,container:{className:t,style:n,...r}={}},animation:s,styles:o,className:i,on:a,close:l,labels:c,children:d}){const[u,f]=F.useState(!1),[g,m]=F.useState(!1),p=F.useRef([]),y=F.useRef(null),{setTimeout:b}=wo(),{subscribe:_}=ks(),M=Nc()?0:s.fade;F.useEffect(()=>(f(!0),()=>{f(!1),m(!1)}),[]);const C=Xe(()=>{p.current.forEach(w=>w()),p.current=[]}),k=Xe(()=>{var w;m(!1),C(),(w=a.exiting)===null||w===void 0||w.call(a),b(()=>{var E;(E=a.exited)===null||E===void 0||E.call(a),l()},M)});F.useEffect(()=>_(ys,k),[_,k]);const N=Xe(w=>{var E,D,P;T_(w),m(!0),(E=a.entering)===null||E===void 0||E.call(a);const L=(P=(D=w.parentNode)===null||D===void 0?void 0:D.children)!==null&&P!==void 0?P:[];for(let $=0;${var $,A;(A=($=y.current)===null||$===void 0?void 0:$.focus)===null||A===void 0||A.call($)}),b(()=>{var $;($=a.entered)===null||$===void 0||$.call(a)},M)}),S=F.useCallback(w=>{w?N(w):C()},[N,C]);return u?$n.createPortal(F.createElement(X_,{ref:S,className:Wt(i,t,Te(np()),Te(bg),g&&Te(np("open"))),"aria-modal":!0,role:"dialog","aria-label":Nr(c,"Lightbox"),style:{...s.fade!==Ec.animation.fade?{[gt("fade_animation_duration")]:`${M}ms`}:null,...s.easing.fade!==Ec.animation.easing.fade?{[gt("fade_animation_timing_function")]:s.easing.fade}:null,...o.root,...n},onFocus:w=>{y.current||(y.current=w.relatedTarget)},...r},d),(typeof e=="function"?e():e)||document.body):null}const d2=mn(hc,c2);function u2({children:e}){return F.createElement(F.Fragment,null,e)}const f2=mn(u_,u2);function h2(e){return Er(hg,e)}function g2({toolbar:{buttons:e},render:{buttonClose:t,iconClose:n},styles:r}){const{close:s,setToolbarWidth:o}=On(),{setContainerRef:i,containerRect:a}=Bg();Nn(()=>{o(a?.width)},[o,a?.width]);const l=()=>t?t():F.createElement(Cs,{key:ys,label:"Close",icon:U_,renderIcon:n,onClick:s});return F.createElement("div",{ref:i,style:r.toolbar,className:Te(h2())},e?.map(c=>c===ys?l():c))}const p2=mn(hg,g2);function sp(e,t){var n;return F.createElement(e.module.component,{key:e.module.name,...t},(n=e.children)===null||n===void 0?void 0:n.map(r=>sp(r,t)))}function m2(e,t={}){const{easing:n,...r}=e,{easing:s,...o}=t;return{easing:{...n,...s},...r,...o}}function b2({carousel:e,animation:t,render:n,toolbar:r,controller:s,noScroll:o,on:i,plugins:a,slides:l,index:c,...d}){const{animation:u,carousel:f,render:g,toolbar:m,controller:p,noScroll:y,on:b,slides:_,index:x,plugins:M,...C}=Ec,{config:k,augmentation:N}=O_([Lt(d2,[Lt(l2,[Lt(J_,[Lt(n2),Lt(p2),Lt(s2)])])])],a||M),S=N({animation:m2(u,t),carousel:{...f,...e},render:{...g,...n},toolbar:{...m,...r},controller:{...p,...s},noScroll:{...y,...o},on:{...b,...i},...C,...d});return S.open?F.createElement($g,{...S},F.createElement($_,{slides:l||_,index:Gi(c||x)},F.createElement(V_,null,F.createElement(I_,null,F.createElement(A_,null,sp(Lt(f2,k),S)))))):null}const op={minZoom:1,maxZoomPixelRatio:1,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:500,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,pinchZoomV4:!1,scrollToZoom:!1,maxZoom:8};function y2(e){return Math.min(Math.max(e,Number.EPSILON),1)}function ip(e){const{minZoom:t,...n}={...op,...e};return{minZoom:y2(t),...n}}function v2(e,t,n,r){const s=F.useRef(void 0),o=F.useRef(void 0),{zoom:i}=Pt().animation,a=Nc(),l=Xe(()=>{var c,d,u;if((c=s.current)===null||c===void 0||c.cancel(),s.current=void 0,o.current&&r?.current){try{s.current=(u=(d=r.current).animate)===null||u===void 0?void 0:u.call(d,[{transform:o.current},{transform:`scale(${e}) translateX(${t}px) translateY(${n}px)`}],{duration:a?0:i??500,easing:s.current?"ease-out":"ease-in-out"})}catch(f){console.error(f)}o.current=void 0,s.current&&(s.current.onfinish=()=>{s.current=void 0})}});return Nn(l,[e,t,n,l]),F.useCallback(()=>{o.current=r?.current?window.getComputedStyle(r.current).transform:void 0},[r])}function x2(e,t){const{on:n}=Pt(),r=Xe(()=>{var s;t||(s=n.zoom)===null||s===void 0||s.call(n,{zoom:e})});F.useEffect(r,[e,r])}function Zi(){const{zoom:e}=Pt();return ip(e)}function w2(e,t){var n;const r=typeof e=="function"?(n=e(t))!==null&&n!==void 0?n:op.maxZoom:e;return Math.max(r,1)}function k2(e,t){var n,r;let s={width:0,height:0},o={width:0,height:0};const{currentSlide:i}=bn(),{imageFit:a}=Pt().carousel,{maxZoomPixelRatio:l,maxZoom:c}=Zi();if(e&&i){const u={...i,...t};if(gr(u)){const f=wc(u,a),g=Math.max(...(((n=u.srcSet)===null||n===void 0?void 0:n.map(p=>p.width))||[]).concat(u.width?[u.width]:[])),m=Math.max(...(((r=u.srcSet)===null||r===void 0?void 0:r.map(p=>p.height))||[]).concat(u.height?[u.height]:[]));g>0&&m>0&&e.width>0&&e.height>0&&(o=f?{width:Math.round(Math.min(g,e.width/e.height*m)),height:Math.round(Math.min(m,e.height/e.width*g))}:{width:g,height:m},o={width:o.width*l,height:o.height*l},s=f?{width:Math.min(e.width,o.width,g),height:Math.min(e.height,o.height,m)}:{width:Math.round(Math.min(e.width,e.height/m*g,g)),height:Math.round(Math.min(e.height,e.width/g*m,m))})}else e.width>0&&e.height>0&&(t&&t.width>0&&t.height>0?s={width:Math.min(e.width,t.width),height:Math.min(e.height,t.height)}:s={width:e.width,height:e.height})}const d=i&&s.width?gr(i)?Math.max(xc(o.width/s.width,5),1):w2(c,i):1;return{imageRect:s,maxZoom:d}}function ap(e,t){return Math.hypot(e.clientX-t.clientX,e.clientY-t.clientY)}function lp(e,t,n=100,r=2){return e*Math.min(1+Math.abs(t/n),r)**Math.sign(t)}function C2(e,t,n,r,s,o,i,a,l){const c=F.useRef([]),d=F.useRef(0),u=F.useRef(void 0),{globalIndex:f}=bn(),{getOwnerWindow:g}=ws(),{containerRef:m,subscribeSensors:p}=On(),{keyboardMoveDistance:y,zoomInMultiplier:b,wheelZoomDistanceFactor:_,scrollToZoom:x,doubleTapDelay:M,doubleClickDelay:C,doubleClickMaxStops:k,pinchZoomDistanceFactor:N,pinchZoomV4:S}=Zi(),w=F.useCallback(z=>{if(m.current){const{pageX:H,pageY:j}=z,{scrollX:G,scrollY:U}=g(),{left:W,top:Y,width:V,height:X}=m.current.getBoundingClientRect();return[H-W-G-V/2,j-Y-U-X/2]}return[]},[m,g]),E=Xe(z=>{const{key:H,metaKey:j,ctrlKey:G}=z,U=j||G,W=()=>{z.preventDefault(),z.stopPropagation()};if(e>1){const Y=(V,X)=>{W(),a(V,X)};H==="ArrowDown"?Y(0,y):H==="ArrowUp"?Y(0,-y):H==="ArrowLeft"?Y(-y,0):H==="ArrowRight"&&Y(y,0)}H==="+"||U&&H==="="?(W(),s()):H==="-"||U&&H==="_"?(W(),o()):U&&H==="0"&&(W(),i(1))}),D=Xe(z=>{if((z.ctrlKey||x)&&Math.abs(z.deltaY)>Math.abs(z.deltaX)){z.stopPropagation(),i(lp(e,-z.deltaY,_),!0,...w(z));return}e>1&&(z.stopPropagation(),x||a(z.deltaX,z.deltaY))}),P=F.useCallback(z=>{const H=c.current;H.splice(0,H.length,...H.filter(j=>j.pointerId!==z.pointerId))},[]),L=F.useCallback(z=>{P(z),z.persist(),c.current.push(z)},[P]),$=Xe(z=>{var H;const j=c.current;if(z.pointerType==="mouse"&&z.buttons>1||!(!((H=l?.current)===null||H===void 0)&&H.contains(z.target)))return;e>1&&z.stopPropagation();const{timeStamp:G}=z;if(j.length===0&&G-d.current<(z.pointerType==="touch"?M:C)){d.current=0;const U=e>=1?e!==n?e*Math.max(n**(1/k),b):1:e!==t?e/Math.max(t**(-1/k),b):1;i(U,!1,...w(z))}else d.current=G;if(L(z),j.length===2){const U=ap(j[0],j[1]);u.current={previousDistance:U,initialDistance:Math.max(U,1),initialZoom:e}}}),A=Xe(z=>{const H=c.current,j=H.find(G=>G.pointerId===z.pointerId);if(H.length===2&&u.current){z.stopPropagation(),L(z);const G=ap(H[0],H[1]),U=S?u.current.initialZoom/u.current.initialDistance*G:lp(e,G-u.current.previousDistance,N);i(U,!0,...H.map(W=>w(W)).reduce((W,Y)=>Y.map((V,X)=>W[X]+V/2))),u.current.previousDistance=G;return}e>1&&(z.stopPropagation(),j&&(H.length===1&&a((j.clientX-z.clientX)/e,(j.clientY-z.clientY)/e),L(z)))}),O=F.useCallback(z=>{const H=c.current;H.length===2&&H.find(j=>j.pointerId===z.pointerId)&&(u.current=void 0),P(z)},[P]),T=F.useCallback(()=>{const z=c.current;z.splice(0,z.length),d.current=0,u.current=void 0},[]);Xg(p,$,A,O,r),F.useEffect(T,[f,T]),F.useEffect(()=>r?()=>{}:vs(T,p(bc,E),p(yc,D)),[r,p,T,E,D])}function S2(e,t,n){const[r,s]=F.useState(1),[o,i]=F.useState(0),[a,l]=F.useState(0),c=v2(r,o,a,n),{currentSlide:d,globalIndex:u}=bn(),{containerRect:f,slideRect:g}=On(),{minZoom:m,zoomInMultiplier:p}=Zi(),y=d&&gr(d)?d.src:void 0,b=!n?.current;Nn(()=>{s(1),i(0),l(0)},[u,y]);const _=F.useCallback((N,S,w)=>{const E=w||r,D=o-(N||0),P=a-(S||0),L=(e.width*E-g.width)/2/E,$=(e.height*E-g.height)/2/E;i(Math.min(Math.abs(D),Math.max(L,0))*Math.sign(D)),l(Math.min(Math.abs(P),Math.max($,0))*Math.sign(P))},[r,o,a,g,e.width,e.height]),x=F.useCallback((N,S,w,E)=>{const D=xc(N+.01m?N:m:t,5);S||c(),_(w?w*(1/r-1/D):0,E?E*(1/r-1/D):0,D),s(D)},[r,m,t,_,c]),M=Xe(()=>{r>1&&(r>t&&x(t,!0),_())});Nn(M,[f.width,f.height,M]);const C=F.useCallback(()=>{const N=r*p;x(r<1&&N>1?1:N)},[r,p,x]),k=F.useCallback(()=>{const N=r/p;x(r>1&&N<1?1:N)},[r,p,x]);return{zoom:r,offsetX:o,offsetY:a,disabled:b,changeOffsets:_,changeZoom:x,zoomIn:C,zoomOut:k}}const cp=F.createContext(null),Lc=hn("useZoom","ZoomControllerContext",cp);function _2({children:e}){const[t,n]=F.useState(),{slideRect:r}=On(),{ref:s,minZoom:o}=Zi(),{imageRect:i,maxZoom:a}=k2(r,t?.imageDimensions),{zoom:l,offsetX:c,offsetY:d,disabled:u,changeZoom:f,changeOffsets:g,zoomIn:m,zoomOut:p}=S2(i,a,t?.zoomWrapperRef);x2(l,u),C2(l,o,a,u,m,p,f,g,t?.zoomWrapperRef);const y=F.useMemo(()=>({zoom:l,minZoom:o,maxZoom:a,offsetX:c,offsetY:d,disabled:u,zoomIn:m,zoomOut:p,changeZoom:f}),[l,o,a,c,d,u,m,p,f]);F.useImperativeHandle(s,()=>y,[y]);const b=F.useMemo(()=>({...y,setZoomWrapper:n}),[y,n]);return F.createElement(cp.Provider,{value:b},e)}const E2=tn("ZoomIn",F.createElement(F.Fragment,null,F.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"}),F.createElement("path",{d:"M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"}))),M2=tn("ZoomOut",F.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"})),dp=F.forwardRef(function({zoomIn:t,onLoseFocus:n},r){const s=F.useRef(!1),o=F.useRef(!1),{zoom:i,minZoom:a,maxZoom:l,zoomIn:c,zoomOut:d,disabled:u}=Lc(),{render:f}=Pt(),g=u||(t?i>=l:i<=a);return F.useEffect(()=>{g&&s.current&&o.current&&n(),g||(s.current=!0)},[g,n]),F.createElement(Cs,{ref:r,disabled:g,label:t?"Zoom in":"Zoom out",icon:t?E2:M2,renderIcon:t?f.iconZoomIn:f.iconZoomOut,onClick:t?c:d,onFocus:()=>{o.current=!0},onBlur:()=>{o.current=!1}})});function N2(){const e=F.useRef(null),t=F.useRef(null),{focus:n}=On(),r=F.useCallback(i=>{var a,l;!((a=i.current)===null||a===void 0)&&a.disabled?n():(l=i.current)===null||l===void 0||l.focus()},[n]),s=F.useCallback(()=>r(e),[r]),o=F.useCallback(()=>r(t),[r]);return F.createElement(F.Fragment,null,F.createElement(dp,{zoomIn:!0,ref:e,onLoseFocus:o}),F.createElement(dp,{ref:t,onLoseFocus:s}))}function T2(){const{render:e}=Pt(),t=Lc();return e.buttonZoom?F.createElement(F.Fragment,null,e.buttonZoom(t)):F.createElement(N2,null)}function O2(e){var t;return(((t=e.srcSet)===null||t===void 0?void 0:t.length)||0)>0}function A2({current:e,preload:t},{type:n,source:r}){switch(n){case"fetch":return e?{current:e,preload:r}:{current:r};case"done":return r===t?{current:r}:{current:e,preload:t};default:throw new Error(Eg)}}function D2(e){var t,n;const[{current:r,preload:s},o]=F.useReducer(A2,{}),{slide:i,rect:a,imageFit:l,render:c,interactive:d}=e,u=i.srcSet.sort((C,k)=>C.width-k.width),f=(t=i.width)!==null&&t!==void 0?t:u[u.length-1].width,g=(n=i.height)!==null&&n!==void 0?n:u[u.length-1].height,m=wc(i,l),p=Math.max(...u.map(C=>C.width)),y=Math.min((m?Math.max:Math.min)(a.width,f*(a.height/g)),p),b=E_(),_=Xe(()=>{var C;const k=(C=u.find(N=>N.width>=y*b))!==null&&C!==void 0?C:u[u.length-1];(!r||u.findIndex(N=>N.src===r)N===k))&&o({type:"fetch",source:k.src})});Nn(_,[a.width,a.height,b,_]);const x=Xe(C=>o({type:"done",source:C})),M={WebkitTransform:d?"initial":"translateZ(0)"};return m||Object.assign(M,a.width/a.heightx(s),render:{...c,iconLoading:()=>null,iconError:()=>null}}),r&&F.createElement(Tr,{key:"current",...e,slide:{...i,src:r,srcSet:void 0},style:M}))}function I2({render:e,slide:t,offset:n,rect:r}){var s;const[o,i]=F.useState(),a=F.useRef(null),l=gr(t),{zoom:c,maxZoom:d,offsetX:u,offsetY:f,setZoomWrapper:g}=Lc(),m=c>1,{carousel:p,on:y}=Pt(),{currentIndex:b}=bn();Nn(()=>{if(n!==0||l||!a.current)return()=>{};const x=()=>{const C=a.current;if(!C)return;let k=0,N=0;for(const S of C.children)S instanceof HTMLElement&&(k=Math.max(k,S.offsetWidth),N=Math.max(N,S.offsetHeight));i(S=>S&&S.width===k&&S.height===N?S:{width:k,height:N})};if(x(),typeof ResizeObserver>"u")return()=>{};const M=new ResizeObserver(x);for(const C of a.current.children)M.observe(C);return()=>M.disconnect()},[n,l,r]),Nn(()=>n===0?(g({zoomWrapperRef:a,imageDimensions:o}),()=>g(void 0)):()=>{},[n,o,g]);let _=(s=e.slide)===null||s===void 0?void 0:s.call(e,{slide:t,offset:n,rect:r,zoom:c,maxZoom:d});if(!_&&l){const x={slide:t,offset:n,rect:r,render:e,imageFit:p.imageFit,imageProps:p.imageProps,onClick:n===0?()=>{var M;return(M=y.click)===null||M===void 0?void 0:M.call(y,{index:b})}:void 0};_=O2(t)?F.createElement(D2,{...x,slide:t,interactive:m,rect:n===0?{width:r.width*c,height:r.height*c}:r}):F.createElement(Tr,{onLoad:M=>i({width:M.naturalWidth,height:M.naturalHeight}),...x})}return _?F.createElement("div",{ref:a,className:Wt(Te(mg),Te(bs),Te(yg),m&&Te(v_)),style:n===0?{transform:`scale(${c}) translateX(${u}px) translateY(${f}px)`}:void 0},_):null}const L2=({augment:e,addModule:t})=>{e(({zoom:n,toolbar:r,render:s,controller:o,...i})=>{const a=ip(n);return{zoom:a,toolbar:Xi(r,pg,F.createElement(T2,null)),render:{...s,slide:l=>{var c,d;return gr(l.slide)||l.slide.type!=null&&(!((c=a.supports)===null||c===void 0)&&c.includes(l.slide.type))?F.createElement(I2,{render:s,...l}):(d=s.slide)===null||d===void 0?void 0:d.call(s,l)}},controller:{...o,preventDefaultWheelY:a.scrollToZoom},...i}}),t(mn(pg,_2))},P2={separator:"/",container:{}},up=e=>({...P2,...e});function $2({counter:e}){const{slides:t,currentIndex:n}=bn(),{separator:r,container:{className:s,...o},className:i,...a}=up(e);return t.length===0?null:F.createElement("div",{className:Wt(Te("counter"),s||i),...a,...o,"aria-hidden":!0},n+1," ",r," ",t.length)}function F2({augment:e,addChild:t}){e(({counter:n,...r})=>({counter:up(n),...r})),t(po,mn(f_,$2))}const z2={autoplay:!1,delay:3e3,ref:null},fp=e=>({...z2,...e}),hp=F.createContext(null),V2=hn("useSlideshow","SlideshowContext",hp);function H2({slideshow:e,carousel:{finite:t},on:n,children:r}){const{autoplay:s,delay:o,ref:i}=fp(e),a=F.useRef(s),[l,c]=F.useState(s),d=F.useRef(void 0),u=F.useRef(void 0),{slides:f,currentIndex:g}=bn(),{setTimeout:m,clearTimeout:p}=wo(),{subscribe:y}=ks(),{next:b}=On(),{setAutoPlaying:_}=Mc();F.useEffect(()=>_(l),[l,_]);const x=f.length===0||t&&g===f.length-1,M=F.useCallback(()=>{!l&&!x&&c(!0)},[l,x]),C=F.useCallback(()=>{l&&c(!1)},[l]),k=F.useCallback(()=>{p(d.current),d.current=void 0},[p]),N=Xe(()=>{k(),!(!l||x||u.current===ms||u.current===gc)&&(d.current=m(()=>{l&&(u.current=void 0,b())},o))});F.useEffect(N,[g,l,N]),F.useEffect(()=>{l&&x&&c(!1)},[g,l,x]);const S=Xe(()=>{var D;return(D=n.slideshowStart)===null||D===void 0?void 0:D.call(n)}),w=Xe(()=>{var D;return(D=n.slideshowStop)===null||D===void 0?void 0:D.call(n)});F.useEffect(()=>{l?S():a.current&&w(),a.current=l},[l,S,w]),F.useEffect(()=>vs(k,y(g_,()=>{u.current=ms,k()}),y(p_,()=>{u.current=gc,k()}),y(m_,()=>{u.current=yo,N()}),y(b_,()=>{u.current=vo,N()})),[y,k,N]);const E=F.useMemo(()=>({playing:l,disabled:x,play:M,pause:C}),[l,x,M,C]);return F.useImperativeHandle(i,()=>E,[E]),F.createElement(hp.Provider,{value:E},r)}const U2=tn("Play",F.createElement("path",{d:"M8 5v14l11-7z"})),B2=tn("Pause",F.createElement("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"}));function j2(){const{playing:e,disabled:t,play:n,pause:r}=V2(),{render:s}=Pt(),o=Wg(On().focus,t);return s.buttonSlideshow?F.createElement(F.Fragment,null,s.buttonSlideshow({playing:e,disabled:t,play:n,pause:r})):F.createElement(Cs,{label:e?"Pause":"Play",icon:e?B2:U2,renderIcon:e?s.iconSlideshowPause:s.iconSlideshowPlay,onClick:e?r:n,disabled:t,...o})}function W2({augment:e,addModule:t}){e(({slideshow:n,toolbar:r,...s})=>({toolbar:Xi(r,gg,F.createElement(j2,null)),slideshow:fp(n),...s})),t(mn(gg,H2))}const G2={auto:!1,ref:null},gp=e=>({...G2,...e}),pp=F.createContext(null),Y2=hn("useFullscreen","FullscreenContext",pp);function X2({fullscreen:e,on:t,children:n}){const{auto:r,ref:s}=gp(e),o=F.useRef(null),[i,a]=F.useState(),[l,c]=F.useState(!1),d=F.useRef(!1),{getOwnerDocument:u}=ws();Nn(()=>{var x,M,C,k;const N=u();a(!((k=(C=(M=(x=N.fullscreenEnabled)!==null&&x!==void 0?x:N.webkitFullscreenEnabled)!==null&&M!==void 0?M:N.mozFullScreenEnabled)!==null&&C!==void 0?C:N.msFullscreenEnabled)!==null&&k!==void 0&&k))},[u]);const f=F.useCallback(()=>{var x;const M=u(),C=M.fullscreenElement||M.webkitFullscreenElement||M.mozFullScreenElement||M.msFullscreenElement;return((x=C?.shadowRoot)===null||x===void 0?void 0:x.fullscreenElement)||C},[u]),g=F.useCallback(()=>{const x=o.current;try{x.requestFullscreen?x.requestFullscreen().catch(()=>{}):x.webkitRequestFullscreen?x.webkitRequestFullscreen():x.mozRequestFullScreen?x.mozRequestFullScreen():x.msRequestFullscreen&&x.msRequestFullscreen()}catch{}},[]),m=F.useCallback(()=>{if(!f())return;const x=u();try{x.exitFullscreen?x.exitFullscreen().catch(()=>{}):x.webkitExitFullscreen?x.webkitExitFullscreen():x.mozCancelFullScreen?x.mozCancelFullScreen():x.msExitFullscreen&&x.msExitFullscreen()}catch{}},[f,u]);F.useEffect(()=>{const x=u(),M=()=>{c(f()===o.current)};return vs(...["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"].map(C=>(x.addEventListener(C,M),()=>x.removeEventListener(C,M))))},[f,u]);const p=Xe(()=>{var x;return(x=t.enterFullscreen)===null||x===void 0?void 0:x.call(t)}),y=Xe(()=>{var x;return(x=t.exitFullscreen)===null||x===void 0?void 0:x.call(t)});F.useEffect(()=>{l&&(d.current=!0),d.current&&(l?p:y)()},[l,p,y]);const b=Xe(()=>{var x;return(x=r?g:null)===null||x===void 0||x(),m});F.useEffect(b,[b]);const _=F.useMemo(()=>({fullscreen:l,disabled:i,enter:g,exit:m}),[l,i,g,m]);return F.useImperativeHandle(s,()=>_,[_]),F.createElement("div",{ref:o,className:Wt(Te(mo),Te(mg))},F.createElement(pp.Provider,{value:_},n))}const K2=tn("EnterFullscreen",F.createElement("path",{d:"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"})),q2=tn("ExitFullscreen",F.createElement("path",{d:"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"}));function Z2(){var e;const{fullscreen:t,disabled:n,enter:r,exit:s}=Y2(),{render:o}=Pt();return n?null:o.buttonFullscreen?F.createElement(F.Fragment,null,(e=o.buttonFullscreen)===null||e===void 0?void 0:e.call(o,{fullscreen:t,disabled:n,enter:r,exit:s})):F.createElement(Cs,{disabled:n,label:t?"Exit Fullscreen":"Enter Fullscreen",icon:t?q2:K2,renderIcon:t?o.iconExitFullscreen:o.iconEnterFullscreen,onClick:t?s:r})}function Q2({augment:e,contains:t,addParent:n}){e(({fullscreen:r,toolbar:s,...o})=>({toolbar:Xi(s,mo,F.createElement(Z2,null)),fullscreen:gp(r),...o})),n(t(bo)?bo:po,mn(mo,X2))}const Ar={ref:null,position:"bottom",width:120,height:80,border:1,borderRadius:4,padding:4,gap:16,imageFit:"contain",vignette:!0,hidden:!1,showToggle:!1},Pc=e=>({...Ar,...e});function mp(){const{thumbnails:e}=Pt();return Pc(e)}const Dr=e=>Er(bo,e),$t=e=>Dr(Er("thumbnail",e)),J2=tn("VideoThumbnail",F.createElement("path",{d:"M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"})),R2=tn("UnknownThumbnail",F.createElement("path",{d:"M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z"}));function eE({slide:e,render:t,rect:n,imageFit:r}){var s;const o=(s=t.thumbnail)===null||s===void 0?void 0:s.call(t,{slide:e,render:t,rect:n,imageFit:r});if(o)return o;const i={render:t,rect:n,imageFit:r};if(e.thumbnail)return F.createElement(Tr,{slide:{src:e.thumbnail},...i});if(gr(e))return F.createElement(Tr,{slide:e,...i});const a=Te($t(Wi));return e.type==="video"?F.createElement(F.Fragment,null,e.poster&&F.createElement(Tr,{slide:{src:e.poster},...i}),F.createElement(J2,{className:a})):F.createElement(R2,{className:a})}const tE=Mr("active"),$c=Mr("fadein"),Fc=Mr("fadeout"),nE=Mr("placeholder"),bp="delay",yp="duration";function rE({slide:e,index:t,onClick:n,fadeIn:r,fadeOut:s,placeholder:o,onLoseFocus:i}){const a=F.useRef(null),{render:l,styles:c,labels:d}=Pt(),{slides:u,globalIndex:f}=bn(),{getOwnerDocument:g}=ws(),{width:m,height:p,imageFit:y}=mp(),b={width:m,height:p},_=t===f,x=Xe(i);return F.useEffect(()=>{s&&g().activeElement===a.current&&x()},[s,x,g]),F.createElement("button",{ref:a,type:"button",className:Wt(Te(bs),Te($t()),_&&Te($t(tE())),r&&Te($t($c())),s&&Te($t(Fc())),o&&Te($t(nE()))),style:{...r?{[gt($t($c(yp)))]:`${r.duration}ms`,[gt($t($c(bp)))]:`${r.delay}ms`}:null,...s?{[gt($t(Fc(yp)))]:`${s.duration}ms`,[gt($t(Fc(bp)))]:`${s.delay}ms`}:null,...c.thumbnail},onClick:n,"aria-current":_?!0:void 0,"aria-label":Ng(d,u,t)},e&&eE({slide:e,render:l,rect:b,imageFit:y}))}function vp(e){return["top","bottom"].includes(e)}function xp(e,t){return t+2*(e.border+e.padding)+e.gap}function sE(e){const{thumbnail:t,poster:n}=e||{thumbnail:"placeholder"};return typeof t=="string"&&t||typeof n=="string"&&n||e&&Tg(e)||void 0}function wp({visible:e,containerRef:t}){const n=F.useRef(null),r=qi(),{publish:s,subscribe:o}=ks(),{carousel:i,styles:a,labels:l}=Pt(),{slides:c,globalIndex:d,animation:u}=bn(),{registerSensors:f,subscribeSensors:g}=Gg();Jg(g);const m=mp(),{position:p,width:y,height:b,border:_,borderStyle:x,borderColor:M,borderRadius:C,padding:k,gap:N,vignette:S}=m,w=u?.duration!==void 0&&u?.increment||0,E=u?.duration||0,{prepareAnimation:D}=Tc(n,O=>({keyframes:vp(p)?[{transform:`translateX(${(r?-1:1)*xp(m,y)*w+O}px)`},{transform:"translateX(0)"}]:[{transform:`translateY(${xp(m,b)*w+O}px)`},{transform:"translateY(0)"}],duration:E,easing:u?.easing})),P=Xe(()=>{let O=0;if(t.current&&n.current){const T=t.current.getBoundingClientRect(),z=n.current.getBoundingClientRect();O=vp(p)?z.left-T.left-(T.width-z.width)/2:z.top-T.top-(T.height-z.height)/2}D(O)});F.useEffect(()=>vs(o(mc,P)),[o,P]);const L=Og(i,c),$=[];if(Cc(c))for(let O=d-L-Math.abs(w);O<=d+L+Math.abs(w);O+=1){const z=i.finite&&(O<0||O>c.length-1)||w<0&&O0&&O>d+L?null:Sc(c,O),H=[`${O}`,sE(z)].filter(Boolean).join("|");$.push({key:H,index:O,slide:z})}const A=O=>()=>{O>d?s(hr,{count:O-d}):O{const H=E/Math.abs(w||1),j=w>0&&T>d+L-w&&T<=d+L||w<0&&T=d-L?{duration:H,delay:((w>0?T-(d+L-w):d-L-w-T)-1)*H}:void 0,G=w>0&&Td+L?{duration:H,delay:(w>0?w-(d-L-T):-w-(T-(d+L)))*H}:void 0;return F.createElement(rE,{key:O,index:T,slide:z,fadeIn:j,fadeOut:G,placeholder:!z,onClick:A(T),onLoseFocus:()=>{var U;return(U=n.current)===null||U===void 0?void 0:U.focus()}})})),S&&F.createElement("div",{className:Te(Dr("vignette"))}))}const kp=F.createContext(null),oE=hn("useThumbnails","ThumbnailsContext",kp);function iE({children:e,...t}){const{ref:n,position:r,hidden:s}=Pc(t.thumbnails),[o,i]=F.useState(!s),a=F.useRef(null),l=F.useMemo(()=>({visible:o,show:()=>i(!0),hide:()=>i(!1)}),[o]);return F.useImperativeHandle(n,()=>l,[l]),F.createElement($g,{...t},F.createElement(kp.Provider,{value:l},F.createElement("div",{ref:a,className:Wt(Te(Dr()),Te(Dr(`${r}`)))},["start","top"].includes(r)&&F.createElement(wp,{containerRef:a,visible:o}),F.createElement("div",{className:Te(Dr("wrapper"))},e),["end","bottom"].includes(r)&&F.createElement(wp,{containerRef:a,visible:o}))))}const Cp=()=>F.createElement(F.Fragment,null,F.createElement("path",{strokeWidth:2,stroke:"currentColor",strokeLinejoin:"round",fill:"none",d:"M3 5l18 0l0 14l-18 0l0-14z"}),F.createElement("path",{d:"M5 14h4v3h-4zM10 14h4v3h-4zM15 14h4v3h-4z"})),aE=tn("ThumbnailsVisible",Cp()),lE=H_("ThumbnailsHidden",Cp());function cE(){const{visible:e,show:t,hide:n}=oE(),{render:r}=Pt();return r.buttonThumbnails?F.createElement(F.Fragment,null,r.buttonThumbnails({visible:e,show:t,hide:n})):F.createElement(Cs,{label:e?"Hide thumbnails":"Show thumbnails",icon:e?aE:lE,renderIcon:e?r.iconThumbnailsVisible:r.iconThumbnailsHidden,onClick:e?n:t})}function dE({augment:e,contains:t,append:n,addParent:r}){e(({thumbnails:o,toolbar:i,...a})=>{const l=Pc(o);return{toolbar:Xi(i,bo,l.showToggle?F.createElement(cE,null):null),thumbnails:l,...a}});const s=mn(bo,iE);t(mo)?n(mo,s):r(po,s)}const uE=window.G7Core?.createLogger?.("Comp:ImageGallery")??{log:(...e)=>console.log("[Comp:ImageGallery]",...e),warn:(...e)=>console.warn("[Comp:ImageGallery]",...e),error:(...e)=>console.error("[Comp:ImageGallery]",...e)},zc=window.G7Core,Qi=(e,t)=>zc?.t?.(e,t)??e,fE=async(e,t)=>{try{const n=await zc.api.get(e,{responseType:"blob"});if(n){const r=URL.createObjectURL(n),s=document.createElement("a");s.href=r,s.download=t,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(r)}}catch(n){uE.error("Failed to download file:",n),zc?.toast?.error?.(Qi("common.download_failed"))}},hE=(e,t)=>{const n=document.createElement("a");n.href=e,n.download=t,n.target="_blank",document.body.appendChild(n),n.click(),document.body.removeChild(n)},Vc=async e=>{const t=e.downloadUrl||e.src,n=e.filename||e.title||"image";e.downloadRequiresAuth?await fE(t,n):hE(t,n)},gE=({image:e,index:t,onDownload:n})=>{const[r,s]=v.useState(!1),o=async i=>{if(i.stopPropagation(),n){n(e,t);return}s(!0);try{await Vc(e)}finally{s(!1)}};return h.jsx(ie,{type:"button",onClick:o,disabled:r,className:"yarl__button flex items-center justify-center","aria-label":Qi("common.download"),title:Qi("common.download"),children:r?h.jsx(yt,{className:"fa-solid fa-spinner fa-spin text-white"}):h.jsx(yt,{className:"fa-solid fa-download text-white"})})},Ji=({images:e,isOpen:t,onClose:n,startIndex:r=0,enableZoom:s=!0,enableSlideshow:o=!1,enableFullscreen:i=!0,showCounter:a=!0,showDownload:l=!0,showThumbnails:c=!0,onDownload:d,editorAttrs:u,id:f})=>{const[g,m]=v.useState(r),p=v.useRef(r),y=e.map(x=>({src:x.src,title:x.title,description:x.description})),b=[];s&&b.push(L2),o&&b.push(W2),i&&b.push(Q2),a&&b.push(F2),c&&b.push(dE);const _=e[g];if(v.useEffect(()=>{p.current=g},[g]),u){const x=(e??[]).slice(0,4);return h.jsx(I,{id:f,className:"grid grid-cols-2 gap-2 p-3 rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800",...u,children:x.length>0?x.map((M,C)=>h.jsx(Sn,{src:M.thumbnail||M.src,alt:M.title||"",className:"w-full aspect-square object-cover rounded bg-gray-200 dark:bg-gray-700"},C)):h.jsxs(I,{className:"col-span-2 flex items-center justify-center py-6 text-sm text-gray-400 dark:text-gray-500",children:[h.jsx(yt,{className:"fa-regular fa-images mr-2"}),Qi("editor.component.image_gallery")]})})}return h.jsx(b2,{open:t,close:n,slides:y,index:g,plugins:b,on:{view:({index:x})=>{x!==p.current&&m(x)}},zoom:{maxZoomPixelRatio:3,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:300,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,scrollToZoom:!0},carousel:{finite:!0,preload:2,padding:"16px",spacing:"30%"},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},controller:{closeOnBackdropClick:!0,closeOnPullDown:!0,closeOnPullUp:!0},thumbnails:{position:"bottom",width:120,height:80,border:2,borderRadius:4,padding:4,gap:16,showToggle:!1,vignette:!0},toolbar:{buttons:[l&&_&&h.jsx(gE,{image:_,index:g,onDownload:d},"download"),"close"].filter(Boolean)},styles:{container:{backgroundColor:"rgba(0, 0, 0, 0.9)"}}})},Sp=()=>{const[e,t]=v.useState(!1),[n,r]=v.useState([]),[s,o]=v.useState(0),i=v.useCallback((l,c=0)=>{r(l),o(c),t(!0)},[]),a=v.useCallback(()=>{t(!1)},[]);return{isOpen:e,openGallery:i,closeGallery:a,galleryProps:{images:n,isOpen:e,onClose:a,startIndex:s}}};Ji.displayName="ImageGallery";const pE=Object.freeze(Object.defineProperty({__proto__:null,ImageGallery:Ji,executeImageDownload:Vc,useImageGallery:Sp},Symbol.toStringTag,{value:"Module"})),mE=v.createContext(null),bE=({value:e,children:t})=>h.jsx(mE.Provider,{value:e,children:t});function yE(e,t){return t.forEach((function(n){n&&typeof n!="string"&&!Array.isArray(n)&&Object.keys(n).forEach((function(r){if(r!=="default"&&!(r in e)){var s=Object.getOwnPropertyDescriptor(n,r);Object.defineProperty(e,r,s.get?s:{enumerable:!0,get:function(){return n[r]}})}}))})),Object.freeze(e)}function _p(e,t){return new Promise((function(n,r){let s;return vE(e).then((function(o){try{return s=o,n(new Blob([t.slice(0,2),s,t.slice(2)],{type:"image/jpeg"}))}catch(i){return r(i)}}),r)}))}const vE=e=>new Promise(((t,n)=>{const r=new FileReader;r.addEventListener("load",(({target:{result:s}})=>{const o=new DataView(s);let i=0;if(o.getUint16(i)!==65496)return n("not a valid JPEG");for(i+=2;;){const a=o.getUint16(i);if(a===65498)break;const l=o.getUint16(i+2);if(a===65505&&o.getUint32(i+4)===1165519206){const c=i+10;let d;switch(o.getUint16(c)){case 18761:d=!0;break;case 19789:d=!1;break;default:return n("TIFF header contains invalid endian")}if(o.getUint16(c+2,d)!==42)return n("TIFF header contains invalid version");const u=o.getUint32(c+4,d),f=c+u+2+12*o.getUint16(c+u,d);for(let g=c+u+2;g>>24&255,a[i+1]=l>>>16&255,a[i+2]=l>>>8&255,a[i+3]=l>>>0&255,new Uint8Array(a.buffer,0,i+4)},r.deflateRaw=function(s,o){o==null&&(o={level:6});var i=new Uint8Array(50+Math.floor(1.1*s.length)),a=r.F.deflateRaw(s,i,a,o.level);return new Uint8Array(i.buffer,0,a)},r.encode=function(s,o){o==null&&(o=!1);var i=0,a=r.bin.writeUint,l=r.bin.writeUshort,c={};for(var d in s){var u=!r._noNeed(d)&&!o,f=s[d],g=r.crc.crc(f,0,f.length);c[d]={cpr:u,usize:f.length,crc:g,file:u?r.deflateRaw(f):f}}for(var d in c)i+=c[d].file.length+30+46+2*r.bin.sizeUTF8(d);i+=22;var m=new Uint8Array(i),p=0,y=[];for(var d in c){var b=c[d];y.push(p),p=r._writeHeader(m,p,d,b,0)}var _=0,x=p;for(var d in c)b=c[d],y.push(p),p=r._writeHeader(m,p,d,b,1,y[_++]);var M=p-x;return a(m,p,101010256),p+=4,l(m,p+=4,_),l(m,p+=2,_),a(m,p+=2,M),a(m,p+=4,x),p+=4,p+=2,m.buffer},r._noNeed=function(s){var o=s.split(".").pop().toLowerCase();return"png,jpg,jpeg,zip".indexOf(o)!=-1},r._writeHeader=function(s,o,i,a,l,c){var d=r.bin.writeUint,u=r.bin.writeUshort,f=a.file;return d(s,o,l==0?67324752:33639248),o+=4,l==1&&(o+=2),u(s,o,20),u(s,o+=2,0),u(s,o+=2,a.cpr?8:0),d(s,o+=2,0),d(s,o+=4,a.crc),d(s,o+=4,f.length),d(s,o+=4,a.usize),u(s,o+=4,r.bin.sizeUTF8(i)),u(s,o+=2,0),o+=2,l==1&&(o+=2,o+=2,d(s,o+=6,c),o+=4),o+=r.bin.writeUTF8(s,o,i),l==0&&(s.set(f,o),o+=f.length),o},r.crc={table:(function(){for(var s=new Uint32Array(256),o=0;o<256;o++){for(var i=o,a=0;a<8;a++)1&i?i=3988292384^i>>>1:i>>>=1;s[o]=i}return s})(),update:function(s,o,i,a){for(var l=0;l>>8;return s},crc:function(s,o,i){return 4294967295^r.crc.update(4294967295,s,o,i)}},r.adler=function(s,o,i){for(var a=1,l=0,c=o,d=o+i;c>8&255},readUint:function(s,o){return 16777216*s[o+3]+(s[o+2]<<16|s[o+1]<<8|s[o])},writeUint:function(s,o,i){s[o]=255&i,s[o+1]=i>>8&255,s[o+2]=i>>16&255,s[o+3]=i>>24&255},readASCII:function(s,o,i){for(var a="",l=0;l>6,s[o+l+1]=128|d>>0&63,l+=2;else if((4294901760&d)==0)s[o+l]=224|d>>12,s[o+l+1]=128|d>>6&63,s[o+l+2]=128|d>>0&63,l+=3;else{if((4292870144&d)!=0)throw"e";s[o+l]=240|d>>18,s[o+l+1]=128|d>>12&63,s[o+l+2]=128|d>>6&63,s[o+l+3]=128|d>>0&63,l+=4}}return l},sizeUTF8:function(s){for(var o=s.length,i=0,a=0;a>>3}var y=c.lits,b=c.strt,_=c.prev,x=0,M=0,C=0,k=0,N=0,S=0;for(p>2&&(b[S=r.F._hash(s,0)]=0),f=0;f14e3||M>26697)&&p-f>100&&(m>>16,P=65535&E;if(E!=0){P=65535&E;var L=d(D=E>>>16,c.of0);c.lhst[257+L]++;var $=d(P,c.df0);c.dhst[$]++,k+=c.exb[L]+c.dxb[$],y[x]=D<<23|f-m,y[x+1]=P<<16|L<<8|$,x+=2,m=f+D}else c.lhst[s[f]]++;M++}}for(C==f&&s.length!=0||(m>>3},r.F._bestMatch=function(s,o,i,a,l,c){var d=32767&o,u=i[d],f=d-u+32768&32767;if(u==d||a!=r.F._hash(s,o-f))return 0;for(var g=0,m=0,p=Math.min(32767,o);f<=p&&--c!=0&&u!=d;){if(g==0||s[o+g]==s[o+g-f]){var y=r.F._howLong(s,o,f);if(y>g){if(m=f,(g=y)>=l)break;f+2b&&(b=M,u=x)}}}f+=(d=u)-(u=i[d])+32768&32767}return g<<16|m},r.F._howLong=function(s,o,i){if(s[o]!=s[o-i]||s[o+1]!=s[o+1-i]||s[o+2]!=s[o+2-i])return 0;var a=o,l=Math.min(s.length,o+258);for(o+=3;o>>23,G=T+(8388607&H);T>16,Y=U>>8&255,V=255&U;S(u,f=r.F._writeLit(257+Y,$,u,f),j-k.of0[Y]),f+=k.exb[Y],N(u,f=r.F._writeLit(V,A,u,f),W-k.df0[V]),f+=k.dxb[V],T+=j}}f=r.F._writeLit(256,$,u,f)}return f},r.F._copyExact=function(s,o,i,a,l){var c=l>>>3;return a[c]=i,a[c+1]=i>>>8,a[c+2]=255-a[c],a[c+3]=255-a[c+1],c+=4,a.set(new Uint8Array(s.buffer,o,i),c),l+(i+4<<3)},r.F.getTrees=function(){for(var s=r.F.U,o=r.F._hufTree(s.lhst,s.ltree,15),i=r.F._hufTree(s.dhst,s.dtree,15),a=[],l=r.F._lenCodes(s.ltree,a),c=[],d=r.F._lenCodes(s.dtree,c),u=0;u4&&s.itree[1+(s.ordr[g-1]<<1)]==0;)g--;return[o,i,f,l,d,g,a,c]},r.F.getSecond=function(s){for(var o=[],i=0;i>1)+",");return o},r.F.contSize=function(s,o){for(var i=0,a=0;a15&&(r.F._putsE(i,a,d,u),a+=u)}return a},r.F._lenCodes=function(s,o){for(var i=s.length;i!=2&&s[i-1]==0;)i-=2;for(var a=0;a>>1,138))<11?o.push(17,g-3):o.push(18,g-11),a+=2*g-2}else if(l==u&&c==l&&d==l){for(f=a+5;f+2>>1,6);o.push(16,g-3),a+=2*g-2}else o.push(l,0)}return i>>>1},r.F._hufTree=function(s,o,i){var a=[],l=s.length,c=o.length,d=0;for(d=0;di&&(r.F.restrictDepth(f,i,x),x=i),d=0;do;a++){var d=s[a].d;s[a].d=o,c+=l-(1<>>=i-o;c>0;)(d=s[a].d)=0;a--)s[a].d==o&&c<0&&(s[a].d--,c++);c!=0&&console.log("debt left")},r.F._goodIndex=function(s,o){var i=0;return o[16|i]<=s&&(i|=16),o[8|i]<=s&&(i|=8),o[4|i]<=s&&(i|=4),o[2|i]<=s&&(i|=2),o[1|i]<=s&&(i|=1),i},r.F._writeLit=function(s,o,i,a){return r.F._putsF(i,a,o[s<<1]),a+o[1+(s<<1)]},r.F.inflate=function(s,o){var i=Uint8Array;if(s[0]==3&&s[1]==0)return o||new i(0);var a=r.F,l=a._bitsF,c=a._bitsE,d=a._decodeTiny,u=a.makeCodes,f=a.codes2map,g=a._get17,m=a.U,p=o==null;p&&(o=new i(s.length>>>2<<3));for(var y,b,_=0,x=0,M=0,C=0,k=0,N=0,S=0,w=0,E=0;_==0;)if(_=l(s,E,1),x=l(s,E+1,2),E+=3,x!=0){if(p&&(o=r.F._check(o,w+131072)),x==1&&(y=m.flmap,b=m.fdmap,N=511,S=31),x==2){M=c(s,E,5)+257,C=c(s,E+5,5)+1,k=c(s,E+10,4)+4,E+=14;for(var D=0;D<38;D+=2)m.itree[D]=0,m.itree[D+1]=0;var P=1;for(D=0;DP&&(P=L)}E+=3*k,u(m.itree,P),f(m.itree,P,m.imap),y=m.lmap,b=m.dmap,E=d(m.imap,(1<>>4;if(!(T>>>8))o[w++]=T;else{if(T==256)break;var z=w+T-254;if(T>264){var H=m.ldef[T-257];z=w+(H>>>3)+c(s,E,7&H),E+=7&H}var j=b[g(s,E)&S];E+=15&j;var G=j>>>4,U=m.ddef[G],W=(U>>>4)+l(s,E,15&U);for(E+=15&U,p&&(o=r.F._check(o,w+131072));w>>3),V=s[Y-4]|s[Y-3]<<8;p&&(o=r.F._check(o,w+V)),o.set(new i(s.buffer,s.byteOffset+Y,V),w),E=Y+V<<3,w+=V}return o.length==w?o:o.slice(0,w)},r.F._check=function(s,o){var i=s.length;if(o<=i)return s;var a=new Uint8Array(Math.max(i<<1,o));return a.set(s,0),a},r.F._decodeTiny=function(s,o,i,a,l,c){for(var d=r.F._bitsE,u=r.F._get17,f=0;f>>4;if(m<=15)c[f]=m,f++;else{var p=0,y=0;m==16?(y=3+d(a,l,2),l+=2,p=c[f-1]):m==17?(y=3+d(a,l,3),l+=3):m==18&&(y=11+d(a,l,7),l+=7);for(var b=f+y;f>>1;cl&&(l=u),c++}for(;c>1,u=s[c+1],f=d<<4|u,g=o-u,m=s[c]<>>15-o]=f,m++},r.F.revCodes=function(s,o){for(var i=r.F.U.rev15,a=15-o,l=0;l>>a}},r.F._putsE=function(s,o,i){i<<=7&o;var a=o>>>3;s[a]|=i,s[a+1]|=i>>>8},r.F._putsF=function(s,o,i){i<<=7&o;var a=o>>>3;s[a]|=i,s[a+1]|=i>>>8,s[a+2]|=i>>>16},r.F._bitsE=function(s,o,i){return(s[o>>>3]|s[1+(o>>>3)]<<8)>>>(7&o)&(1<>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16)>>>(7&o)&(1<>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16)>>>(7&o)},r.F._get25=function(s,o){return(s[o>>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16|s[3+(o>>>3)]<<24)>>>(7&o)},r.F.U=(t=Uint16Array,n=Uint32Array,{next_code:new t(16),bl_count:new t(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new t(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new n(32),flmap:new t(512),fltree:[],fdmap:new t(32),fdtree:[],lmap:new t(32768),ltree:[],ttree:[],dmap:new t(32768),dtree:[],imap:new t(512),itree:[],rev15:new t(32768),lhst:new n(286),dhst:new n(30),ihst:new n(19),lits:new n(15e3),strt:new t(65536),prev:new t(32768)}),(function(){for(var s=r.F.U,o=0;o<32768;o++){var i=o;i=(4278255360&(i=(4042322160&(i=(3435973836&(i=(2863311530&i)>>>1|(1431655765&i)<<1))>>>2|(858993459&i)<<2))>>>4|(252645135&i)<<4))>>>8|(16711935&i)<<8,s.rev15[o]=(i>>>16|i<<16)>>>17}function a(l,c,d){for(;c--!=0;)l.push(0,d)}for(o=0;o<32;o++)s.ldef[o]=s.of0[o]<<3|s.exb[o],s.ddef[o]=s.df0[o]<<4|s.dxb[o];a(s.fltree,144,8),a(s.fltree,112,9),a(s.fltree,24,7),a(s.fltree,8,8),r.F.makeCodes(s.fltree,9),r.F.codes2map(s.fltree,9,s.flmap),r.F.revCodes(s.fltree,9),a(s.fdtree,32,5),r.F.makeCodes(s.fdtree,5),r.F.codes2map(s.fdtree,5,s.fdmap),r.F.revCodes(s.fdtree,5),a(s.itree,19,0),a(s.ltree,286,0),a(s.dtree,30,0),a(s.ttree,320,0)})()})();var wE=yE({__proto__:null,default:Ri},[Ri]);const An=(function(){var e={nextZero(d,u){for(;d[u]!=0;)u++;return u},readUshort:(d,u)=>d[u]<<8|d[u+1],writeUshort(d,u,f){d[u]=f>>8&255,d[u+1]=255&f},readUint:(d,u)=>16777216*d[u]+(d[u+1]<<16|d[u+2]<<8|d[u+3]),writeUint(d,u,f){d[u]=f>>24&255,d[u+1]=f>>16&255,d[u+2]=f>>8&255,d[u+3]=255&f},readASCII(d,u,f){let g="";for(let m=0;md.length<2?`0${d}`:d,readUTF8(d,u,f){let g,m="";for(let p=0;p>3)]>>7-((7&k)<<0)&1);b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>2)]>>6-((3&k)<<1)&3),b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>1)]>>4-((1&k)<<2)&15),b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>>3)]>>>7-(7&T)&1))==255*S?0:255;_[j+T]=z<<24|A<<16|A<<8|A}else if(M==2)for(T=0;T>>2)]>>>6-((3&T)<<1)&3))==85*S?0:255,_[j+T]=z<<24|A<<16|A<<8|A;else if(M==4)for(T=0;T>>1)]>>>4-((1&T)<<2)&15))==17*S?0:255,_[j+T]=z<<24|A<<16|A<<8|A;else if(M==8)for(T=0;T>3,S=Math.ceil(M*k/8),w=new Uint8Array(C*S);let E=0;const D=[0,0,4,0,2,0,1],P=[0,4,0,2,0,1,0],L=[8,8,8,4,4,2,2],$=[8,8,4,4,2,2,1];let A=0;for(;A<7;){const T=L[A],z=$[A];let H=0,j=0,G=D[A];for(;G>3])>>7-(7&K)&1,w[V*S+(X>>3)]|=O<<7-((7&X)<<0)),k==2&&(O=(O=_[K>>3])>>6-(7&K)&3,w[V*S+(X>>2)]|=O<<6-((3&X)<<1)),k==4&&(O=(O=_[K>>3])>>4-(7&K)&15,w[V*S+(X>>1)]|=O<<4-((1&X)<<2)),k>=8){const q=V*S+X*N;for(let ne=0;ne>3)+ne]}K+=k,X+=z}Y++,V+=T}H*j!=0&&(E+=j*(1+W)),A+=1}return w})(u,d)),u}function r(d,u){return s(new Uint8Array(d.buffer,2,d.length-6),u)}var s=(function(){const d={H:{}};return d.H.N=function(u,f){const g=Uint8Array;let m,p,y=0,b=0,_=0,x=0,M=0,C=0,k=0,N=0,S=0;if(u[0]==3&&u[1]==0)return f||new g(0);const w=d.H,E=w.b,D=w.e,P=w.R,L=w.n,$=w.A,A=w.Z,O=w.m,T=f==null;for(T&&(f=new g(u.length>>>2<<5));y==0;)if(y=E(u,S,1),b=E(u,S+1,2),S+=3,b!=0){if(T&&(f=d.H.W(f,N+(1<<17))),b==1&&(m=O.J,p=O.h,C=511,k=31),b==2){_=D(u,S,5)+257,x=D(u,S+5,5)+1,M=D(u,S+10,4)+4,S+=14;let H=1;for(var z=0;z<38;z+=2)O.Q[z]=0,O.Q[z+1]=0;for(z=0;zH&&(H=U)}S+=3*M,L(O.Q,H),$(O.Q,H,O.u),m=O.w,p=O.d,S=P(O.u,(1<>>4;if(!(j>>>8))f[N++]=j;else{if(j==256)break;{let G=N+j-254;if(j>264){const X=O.q[j-257];G=N+(X>>>3)+D(u,S,7&X),S+=7&X}const U=p[A(u,S)&k];S+=15&U;const W=U>>>4,Y=O.c[W],V=(Y>>>4)+E(u,S,15&Y);for(S+=15&Y;N>>3),j=u[H-4]|u[H-3]<<8;T&&(f=d.H.W(f,N+j)),f.set(new g(u.buffer,u.byteOffset+H,j),N),S=H+j<<3,N+=j}return f.length==N?f:f.slice(0,N)},d.H.W=function(u,f){const g=u.length;if(f<=g)return u;const m=new Uint8Array(g<<1);return m.set(u,0),m},d.H.R=function(u,f,g,m,p,y){const b=d.H.e,_=d.H.Z;let x=0;for(;x>>4;if(C<=15)y[x]=C,x++;else{let k=0,N=0;C==16?(N=3+b(m,p,2),p+=2,k=y[x-1]):C==17?(N=3+b(m,p,3),p+=3):C==18&&(N=11+b(m,p,7),p+=7);const S=x+N;for(;x>>1;for(;yp&&(p=_),y++}for(;y>1,_=u[y+1],x=b<<4|_,M=f-_;let C=u[y]<>>15-f]=x,C++}},d.H.l=function(u,f){const g=d.H.m.r,m=15-f;for(let p=0;p>>m}},d.H.M=function(u,f,g){g<<=7&f;const m=f>>>3;u[m]|=g,u[m+1]|=g>>>8},d.H.I=function(u,f,g){g<<=7&f;const m=f>>>3;u[m]|=g,u[m+1]|=g>>>8,u[m+2]|=g>>>16},d.H.e=function(u,f,g){return(u[f>>>3]|u[1+(f>>>3)]<<8)>>>(7&f)&(1<>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16)>>>(7&f)&(1<>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16)>>>(7&f)},d.H.i=function(u,f){return(u[f>>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16|u[3+(f>>>3)]<<24)>>>(7&f)},d.H.m=(function(){const u=Uint16Array,f=Uint32Array;return{K:new u(16),j:new u(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new u(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new f(32),J:new u(512),_:[],h:new u(32),$:[],w:new u(32768),C:[],v:[],d:new u(32768),D:[],u:new u(512),Q:[],r:new u(32768),s:new f(286),Y:new f(30),a:new f(19),t:new f(15e3),k:new u(65536),g:new u(32768)}})(),(function(){const u=d.H.m;for(var f=0;f<32768;f++){let m=f;m=(2863311530&m)>>>1|(1431655765&m)<<1,m=(3435973836&m)>>>2|(858993459&m)<<2,m=(4042322160&m)>>>4|(252645135&m)<<4,m=(4278255360&m)>>>8|(16711935&m)<<8,u.r[f]=(m>>>16|m<<16)>>>17}function g(m,p,y){for(;p--!=0;)m.push(0,y)}for(f=0;f<32;f++)u.q[f]=u.S[f]<<3|u.T[f],u.c[f]=u.p[f]<<4|u.z[f];g(u._,144,8),g(u._,112,9),g(u._,24,7),g(u._,8,8),d.H.n(u._,9),d.H.A(u._,9,u.J),d.H.l(u._,9),g(u.$,32,5),d.H.n(u.$,5),d.H.A(u.$,5,u.h),d.H.l(u.$,5),g(u.Q,19,0),g(u.C,286,0),g(u.D,30,0),g(u.v,320,0)})(),d.H.N})();function o(d){return[1,null,3,1,2,null,4][d.ctype]*d.depth}function i(d,u,f,g,m){let p=o(u);const y=Math.ceil(g*p/8);let b,_;p=Math.ceil(p/8);let x=d[f],M=0;if(x>1&&(d[f]=[0,0,1][x-2]),x==3)for(M=p;M>>1)&255;for(let C=0;C>>1);for(;M>>1)}else{for(;M=0&&b>=0?(C=A*u+O<<2,k=(b+A)*m+y+O<<2):(C=(-b+A)*u-y+O<<2,k=A*m+O<<2),_==0)g[k]=d[C],g[k+1]=d[C+1],g[k+2]=d[C+2],g[k+3]=d[C+3];else if(_==1){var N=d[C+3]*.00392156862745098,S=d[C]*N,w=d[C+1]*N,E=d[C+2]*N,D=g[k+3]*(1/255),P=g[k]*D,L=g[k+1]*D,$=g[k+2]*D;const T=1-N,z=N+D*T,H=z==0?0:1/z;g[k+3]=255*z,g[k+0]=(S+P*T)*H,g[k+1]=(w+L*T)*H,g[k+2]=(E+$*T)*H}else if(_==2)N=d[C+3],S=d[C],w=d[C+1],E=d[C+2],D=g[k+3],P=g[k],L=g[k+1],$=g[k+2],N==D&&S==P&&w==L&&E==$?(g[k]=0,g[k+1]=0,g[k+2]=0,g[k+3]=0):(g[k]=S,g[k+1]=w,g[k+2]=E,g[k+3]=N);else if(_==3){if(N=d[C+3],S=d[C],w=d[C+1],E=d[C+2],D=g[k+3],P=g[k],L=g[k+1],$=g[k+2],N==D&&S==P&&w==L&&E==$)continue;if(N<220&&D>20)return!1}return!0}return{decode:function(u){const f=new Uint8Array(u);let g=8;const m=e,p=m.readUshort,y=m.readUint,b={tabs:{},frames:[]},_=new Uint8Array(f.length);let x,M=0,C=0;const k=[137,80,78,71,13,10,26,10];for(var N=0;N<8;N++)if(f[N]!=k[N])throw"The input is not a PNG file!";for(;g>>1:E>>>=1;S[w]=E}return S})(),update(S,w,E,D){for(let P=0;P>>8;return S},crc:(S,w,E)=>4294967295^r.update(4294967295,S,w,E)};function s(S,w,E,D){w[E]+=S[0]*D>>4,w[E+1]+=S[1]*D>>4,w[E+2]+=S[2]*D>>4,w[E+3]+=S[3]*D>>4}function o(S){return Math.max(0,Math.min(255,S))}function i(S,w){const E=S[0]-w[0],D=S[1]-w[1],P=S[2]-w[2],L=S[3]-w[3];return E*E+D*D+P*P+L*L}function a(S,w,E,D,P,L,$){$==null&&($=1);const A=D.length,O=[];for(var T=0;T>>0&255,V>>>8&255,V>>>16&255,V>>>24&255])}for(T=0;T>2]=z,G[T>>2]=D[z]}}function l(S,w,E,D,P){P==null&&(P={});const{crc:L}=r,$=t.writeUint,A=t.writeUshort,O=t.writeASCII;let T=8;const z=S.frames.length>1;let H,j=!1,G=33+(z?20:0);if(P.sRGB!=null&&(G+=13),P.pHYs!=null&&(G+=21),P.iCCP!=null&&(H=pako.deflate(P.iCCP),G+=21+H.length+4),S.ctype==3){for(var U=S.plte.length,W=0;W>>24!=255&&(j=!0);G+=8+3*U+4+(j?8+1*U+4:0)}for(var Y=0;Y>>8&255,le=ee>>>16&255;V[T+ne+0]=de,V[T+ne+1]=J,V[T+ne+2]=le}if(T+=3*U,$(V,T,L(V,T-3*U-4,3*U+4)),T+=4,j){for($(V,T,U),T+=4,O(V,T,"tRNS"),T+=4,W=0;W>>24&255;T+=U,$(V,T,L(V,T-U-4,U+4)),T+=4}}let K=0;for(Y=0;YVe&&(Ve=dt),ltqe&&(qe=lt));Ve==-1&&(ze=Ne=Ve=qe=0),oe&&((1&ze)==1&&ze--,(1&Ne)==1&&Ne--);const nt=(Ve-ze+1)*(qe-Ne+1);nt>2,Fe>>2);ne.push(ee);const oe=new Uint8Array(ge.abuf,ke,Fe);z&&a(de.img,de.rect.width,de.rect.height,q,oe,ee),de.img.set(oe),ke+=Fe}}else for(U=0;UJ&&he==ye[Y-J])ee[Y]=ee[Y-J];else{let ge=K[he];if(ge==null&&(K[he]=ge=q.length,q.push(he),q.length>=300))break;ee[Y]=ge}}}const le=q.length;for(le<=256&&T==0&&(j=le<=2?1:le<=4?2:le<=16?4:8,j=Math.max(j,O)),U=0;U>1)]|=Fe[Me+ae]<<4-4*(1&ae);else if(j==2)for(ae=0;ae>2)]|=Fe[Me+ae]<<6-2*(3&ae);else if(j==1)for(ae=0;ae>3)]|=Fe[Me+ae]<<7-1*(7&ae)}he=pe,H=3,ke=1}else if(V==0&&X.length==1){pe=new Uint8Array(J*ye*3);const Fe=J*ye;for(Y=0;YY&&(Y=ne),eeV&&(V=ee))}Y==-1&&(U=W=Y=V=0),$&&((1&U)==1&&U--,(1&W)==1&&W--),L={x:U,y:W,width:Y-U+1,height:V-W+1};const X=D[P];X.rect=L,X.blend=1,X.img=new Uint8Array(L.width*L.height*4),D[P-1].dispose==0?(e(T,w,E,X.img,L.width,L.height,-L.x,-L.y,0),f(j,w,E,X.img,L)):e(j,w,E,X.img,L.width,L.height,-L.x,-L.y,0)}function f(S,w,E,D,P){e(S,w,E,D,P.width,P.height,-P.x,-P.y,2)}function g(S,w,E,D,P,L,$){const A=[];let O,T=[0,1,2,3,4];L!=-1?T=[L]:(w*D>5e5||E==1)&&(T=[0]),$&&(O={level:0});const z=wE;for(var H=0;H>1)+256&255;if(L==4)for(O=P;O>1)&255;for(O=P;O>1)&255}if(L==4){for(O=0;O>2);let z;if(E.length<2e7)for(var H=0;H>2]=z.ind,P[H>>2]=z.est.rgba;else for(H=0;H>2]=z.ind,P[H>>2]=z.est.rgba}return{abuf:D.buffer,inds:T,plte:A}}function y(S,w,E){E==null&&(E=1e-4);const D=new Uint32Array(S.buffer),P={i0:0,i1:S.length,bst:null,est:null,tdst:0,left:null,right:null};P.bst=C(S,P.i0,P.i1),P.est=k(P.bst);const L=[P];for(;L.lengthA&&(A=L[$].est.L,O=$);if(A=z||T.i1<=z){T.est.L=0;continue}const H={i0:T.i0,i1:z,bst:null,est:null,tdst:0,left:null,right:null};H.bst=C(S,H.i0,H.i1),H.est=k(H.bst);const j={i0:z,i1:T.i1,bst:null,est:null,tdst:0,left:null,right:null};for(j.bst={R:[],m:[],N:T.bst.N-H.bst.N},$=0;$<16;$++)j.bst.R[$]=T.bst.R[$]-H.bst.R[$];for($=0;$<4;$++)j.bst.m[$]=T.bst.m[$]-H.bst.m[$];j.est=k(j.bst),T.left=H,T.right=j,L[O]=H,L.push(j)}for(L.sort(((A,O)=>O.bst.N-A.bst.N)),$=0;$0&&($=S.right,A=S.left);const O=b($,w,E,D,P);if(O.tdst<=L*L)return O;const T=b(A,w,E,D,P);return T.tdstL;)D-=4;if(E>=D)break;const $=w[E>>2];w[E>>2]=w[D>>2],w[D>>2]=$,E+=4,D-=4}for(;M(S,E,P)>L;)E-=4;return E+4}function M(S,w,E){return S[w]*E[0]+S[w+1]*E[1]+S[w+2]*E[2]+S[w+3]*E[3]}function C(S,w,E){const D=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],P=[0,0,0,0],L=E-w>>2;for(let $=w;$>>0}}var N={multVec:(S,w)=>[S[0]*w[0]+S[1]*w[1]+S[2]*w[2]+S[3]*w[3],S[4]*w[0]+S[5]*w[1]+S[6]*w[2]+S[7]*w[3],S[8]*w[0]+S[9]*w[1]+S[10]*w[2]+S[11]*w[3],S[12]*w[0]+S[13]*w[1]+S[14]*w[2]+S[15]*w[3]],dot:(S,w)=>S[0]*w[0]+S[1]*w[1]+S[2]*w[2]+S[3]*w[3],sml:(S,w)=>[S*w[0],S*w[1],S*w[2],S*w[3]]};An.encode=function(w,E,D,P,L,$,A){P==null&&(P=0),A==null&&(A=!1);const O=d(w,E,D,P,[!1,!1,!1,0,A,!1]);return c(O,-1),l(O,E,D,L,$)},An.encodeLL=function(w,E,D,P,L,$,A,O){const T={ctype:0+(P==1?0:2)+(L==0?0:4),depth:$,frames:[]},z=(P+L)*$,H=z*E;for(let j=0;j>>0),C(1),C(32),k(3),k(l),k(2835),k(2835),N(8),k(16711680),k(65280),k(255),k(4278190080),k(1466527264),(function S(){for(;_0;){for(y=122+_*a,g=0;g>>24,u.setUint32(y+g,m<<8|p),g+=4;_++}M{t(new Blob([n],{type:"image/bmp"}))}))},_dly:9};var nn={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},kE={[nn.CHROME]:16384,[nn.FIREFOX]:11180,[nn.DESKTOP_SAFARI]:16384,[nn.IE]:8192,[nn.IOS]:4096,[nn.ETC]:8192};const Hc=typeof window<"u",Mp=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,ea=Hc&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),CE=(Hc||Mp)&&(ea&&ea.getOriginalSymbol(window,"File")||typeof File<"u"&&File),Np=(Hc||Mp)&&(ea&&ea.getOriginalSymbol(window,"FileReader")||typeof FileReader<"u"&&FileReader);function Uc(e,t,n=Date.now()){return new Promise((r=>{const s=e.split(","),o=s[0].match(/:(.*?);/)[1],i=globalThis.atob(s[1]);let a=i.length;const l=new Uint8Array(a);for(;a--;)l[a]=i.charCodeAt(a);const c=new Blob([l],{type:o});c.name=t,c.lastModified=n,r(c)}))}function Tp(e){return new Promise(((t,n)=>{const r=new Np;r.onload=()=>t(r.result),r.onerror=s=>n(s),r.readAsDataURL(e)}))}function Op(e){return new Promise(((t,n)=>{const r=new Image;r.onload=()=>t(r),r.onerror=s=>n(s),r.src=e}))}function Ir(){if(Ir.cachedResult!==void 0)return Ir.cachedResult;let e=nn.ETC;const{userAgent:t}=navigator;return/Chrom(e|ium)/i.test(t)?e=nn.CHROME:/iP(ad|od|hone)/i.test(t)&&/WebKit/i.test(t)?e=nn.IOS:/Safari/i.test(t)?e=nn.DESKTOP_SAFARI:/Firefox/i.test(t)?e=nn.FIREFOX:(/MSIE/i.test(t)||document.documentMode)&&(e=nn.IE),Ir.cachedResult=e,Ir.cachedResult}function Ap(e,t){const n=Ir(),r=kE[n];let s=e,o=t,i=s*o;const a=s>o?o/s:s/o;for(;i>r*r;){const l=(r+s)/2,c=(r+o)/2;lEp.toBlob(e,d))).then(function(d){try{return a=d,a.name=n,a.lastModified=r,c.call(this)}catch(u){return i(u)}}.bind(this),i);{let d=function(){return c.call(this)};if(typeof OffscreenCanvas=="function"&&e instanceof OffscreenCanvas)return e.convertToBlob({type:t,quality:s}).then(function(u){try{return a=u,a.name=n,a.lastModified=r,d.call(this)}catch(f){return i(f)}}.bind(this),i);{let u;return u=e.toDataURL(t,s),Uc(u,n,r).then(function(f){try{return a=f,d.call(this)}catch(g){return i(g)}}.bind(this),i)}}}function l(){return o(a)}}))}function Wn(e){e.width=0,e.height=0}function Ss(){return new Promise((function(e,t){let n,r,s,o;return Ss.cachedResult!==void 0?e(Ss.cachedResult):Uc("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(i){try{return n=i,ra(n).then((function(a){try{return r=a[1],sa(r,n.type,n.name,n.lastModified).then((function(l){try{return s=l,Wn(r),ra(s).then((function(c){try{return o=c[0],Ss.cachedResult=o.width===1&&o.height===2,e(Ss.cachedResult)}catch(d){return t(d)}}),t)}catch(c){return t(c)}}),t)}catch(l){return t(l)}}),t)}catch(a){return t(a)}}),t)}))}function Ip(e){return new Promise(((t,n)=>{const r=new Np;r.onload=s=>{const o=new DataView(s.target.result);if(o.getUint16(0,!1)!=65496)return t(-2);const i=o.byteLength;let a=2;for(;an(s),r.readAsArrayBuffer(e)}))}function Lp(e,t){const{width:n}=e,{height:r}=e,{maxWidthOrHeight:s}=t;let o,i=e;return isFinite(s)&&(n>s||r>s)&&([i,o]=ta(n,r),n>r?(i.width=s,i.height=r/n*s):(i.width=n/r*s,i.height=s),o.drawImage(e,0,0,i.width,i.height),Wn(e)),i}function Pp(e,t){const{width:n}=e,{height:r}=e,[s,o]=ta(n,r);switch(t>4&&t<9?(s.width=r,s.height=n):(s.width=n,s.height=r),t){case 2:o.transform(-1,0,0,1,n,0);break;case 3:o.transform(-1,0,0,-1,n,r);break;case 4:o.transform(1,0,0,-1,0,r);break;case 5:o.transform(0,1,1,0,0,0);break;case 6:o.transform(0,1,-1,0,r,0);break;case 7:o.transform(0,-1,-1,0,r,n);break;case 8:o.transform(0,-1,1,0,0,n)}return o.drawImage(e,0,0,n,r),Wn(e),s}function $p(e,t,n=0){return new Promise((function(r,s){let o,i,a,l,c,d,u,f,g,m,p,y,b,_,x,M,C,k,N,S;function w(D=5){if(t.signal&&t.signal.aborted)throw t.signal.reason;o+=D,t.onProgress(Math.min(o,100))}function E(D){if(t.signal&&t.signal.aborted)throw t.signal.reason;o=Math.min(Math.max(D,o),100),t.onProgress(o)}return o=n,i=t.maxIteration||10,a=1024*t.maxSizeMB*1024,w(),ra(e,t).then(function(D){try{return[,l]=D,w(),c=Lp(l,t),w(),new Promise((function(P,L){var $;if(!($=t.exifOrientation))return Ip(e).then(function(O){try{return $=O,A.call(this)}catch(T){return L(T)}}.bind(this),L);function A(){return P($)}return A.call(this)})).then(function(P){try{return d=P,w(),Ss().then(function(L){try{return u=L?c:Pp(c,d),w(),f=t.initialQuality||1,g=t.fileType||e.type,sa(u,g,e.name,e.lastModified,f).then(function($){try{{let O=function(){if(i--&&(x>a||x>b)){let z,H;return z=S?.95*N.width:N.width,H=S?.95*N.height:N.height,[C,k]=ta(z,H),k.drawImage(N,0,0,z,H),f*=g==="image/png"?.85:.95,sa(C,g,e.name,e.lastModified,f).then((function(j){try{return M=j,Wn(N),N=C,x=M.size,E(Math.min(99,Math.floor((_-x)/(_-a)*100))),O}catch(G){return s(G)}}),s)}return[1]},T=function(){return Wn(N),Wn(C),Wn(c),Wn(u),Wn(l),E(100),r(M)};if(m=$,w(),p=m.size>a,y=m.size>e.size,!p&&!y)return E(100),r(m);var A;return b=e.size,_=m.size,x=_,N=u,S=!t.alwaysKeepResolution&&p,(A=function(z){for(;z;){if(z.then)return void z.then(A,s);try{if(z.pop){if(z.length)return z.pop()?T.call(this):z;z=O}else z=z.call(this)}catch(H){return s(H)}}}.bind(this))(O)}}catch(O){return s(O)}}.bind(this),s)}catch($){return s($)}}.bind(this),s)}catch(L){return s(L)}}.bind(this),s)}catch(P){return s(P)}}.bind(this),s)}))}const SE=` + `,children:[h.jsx(re,{name:x.faIcon,className:"w-4 h-4"}),h.jsx(Q,{children:x.label}),y?.faIcon===x.faIcon&&h.jsx(re,{name:"check",className:"w-4 h-4 ml-auto text-blue-600"})]},x.value)):h.jsx(I,{className:"px-3 py-4 text-sm text-gray-500 dark:text-gray-400 text-center",children:o})})]})]})},sc=({defaultOpen:t=!1,isOpen:e,onToggle:n,className:r="",style:s,children:o,disabled:i=!1,id:a,editorAttrs:l})=>{const[c,d]=v.useState(t),u=e!==void 0,f=u?e:c,g=v.useRef(null),[m,p]=v.useState(t?void 0:0),y=()=>{if(i)return;const _=!f;u||d(_),n?.(_)};v.useEffect(()=>{if(g.current)if(f){p(g.current.scrollHeight);const _=setTimeout(()=>{p(void 0)},300);return()=>clearTimeout(_)}else p(g.current.scrollHeight),requestAnimationFrame(()=>{p(0)})},[f]);const b={};if(v.Children.forEach(o,_=>{if(v.isValidElement(_)){const x=_.props?.slot;x==="trigger"?b.trigger=_:x==="content"&&(b.content=_)}}),!b.trigger&&!b.content){const _=v.Children.toArray(o);_.length>=2?(b.trigger=_[0],b.content=_.slice(1)):_.length===1&&(b.content=_[0])}return h.jsxs(I,{className:`border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${r}`,style:s,id:a,...l,children:[h.jsxs(oe,{type:"button",onClick:y,disabled:i,className:`w-full flex items-center justify-between p-3 text-left bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${i?"opacity-50 cursor-not-allowed":""} ${f?"border-b border-gray-200 dark:border-gray-700":""}`,"aria-expanded":f,children:[h.jsx(I,{className:"flex-1",children:b.trigger}),!b.trigger&&h.jsx("svg",{className:`w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),h.jsx(I,{ref:g,className:"overflow-hidden transition-all duration-300 ease-in-out",style:{height:m!==void 0?`${m}px`:"auto"},children:h.jsx(I,{className:"bg-white dark:bg-gray-800",children:b.content})})]})},Rs=window.G7Core,Jh=Rs?.useControllableState,Rh=(t,e)=>window.G7Core?.t?.(t,e)??t,zS={"":"common.scope_type_all_desc",self:"common.scope_type_self_desc",role:"common.scope_type_role_desc"},VS=({options:t,value:e,onChange:n,disabled:r=!1})=>{const[s,o]=v.useState(!1),[i,a]=v.useState({}),l=v.useRef(null),c=t.find(u=>u.value===e)??t[0];v.useEffect(()=>{if(!s)return;const u=g=>{l.current&&!l.current.contains(g.target)&&o(!1)},f=()=>{if(!l.current)return;const g=l.current.getBoundingClientRect();a({position:"fixed",top:g.bottom+4,right:window.innerWidth-g.right,zIndex:9999})};return document.addEventListener("mousedown",u),window.addEventListener("scroll",f,!0),window.addEventListener("resize",f),()=>{document.removeEventListener("mousedown",u),window.removeEventListener("scroll",f,!0),window.removeEventListener("resize",f)}},[s]);const d=v.useCallback(()=>{if(r||!l.current)return;const u=l.current.getBoundingClientRect();a({position:"fixed",top:u.bottom+4,right:window.innerWidth-u.right,zIndex:9999}),o(f=>!f)},[r]);return h.jsxs(I,{ref:l,className:"relative flex-shrink-0",children:[h.jsxs(I,{role:"button",tabIndex:0,onClick:u=>{u.stopPropagation(),u.preventDefault(),d()},onMouseDown:u=>u.stopPropagation(),onKeyDown:u=>{(u.key==="Enter"||u.key===" ")&&(u.stopPropagation(),u.preventDefault(),d()),u.key==="Escape"&&o(!1)},className:`flex items-center gap-1 text-xs px-2 py-1 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 cursor-pointer select-none ${r?"opacity-50 cursor-not-allowed":"hover:border-blue-400 dark:hover:border-blue-500"}`,children:[h.jsx(Q,{children:c?.label??""}),h.jsx(re,{name:ce.ChevronDown,className:"w-3 h-3 text-gray-400 dark:text-gray-500"})]}),s&&h.jsx(I,{style:i,className:"w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-lg shadow-lg py-1",children:t.map(u=>{const f=u.value??"",g=zS[f],m=g?Rh(g):"",p=u.value===e;return h.jsxs(I,{role:"option","aria-selected":p,onClick:y=>{y.stopPropagation(),y.preventDefault(),n(u.value),o(!1)},onMouseDown:y=>y.stopPropagation(),className:`px-3 py-2 cursor-pointer transition-colors ${p?"bg-blue-50 dark:bg-blue-900/30":"hover:bg-gray-50 dark:hover:bg-gray-700/50"}`,children:[h.jsxs(I,{className:"flex items-center justify-between",children:[h.jsx(Q,{className:`text-xs font-medium ${p?"text-blue-700 dark:text-blue-300":"text-gray-900 dark:text-white"}`,children:u.label}),p&&h.jsx(re,{name:ce.Check,className:"w-3.5 h-3.5 text-blue-600 dark:text-blue-400"})]}),m&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-0.5",children:m})]},f||"__null__")})})]})},HS=(t,e)=>{if(t.length!==e.length)return!1;const n=[...t].sort((s,o)=>s.id-o.id),r=[...e].sort((s,o)=>s.id-o.id);return n.every((s,o)=>s.id===r[o].id&&s.scope_type===r[o].scope_type)},US=({data:t=[],value:e=[],onChange:n,scopeOptions:r=[],className:s="",disabled:o=!1,desktopColumns:i=2,installedModules:a=[],installedPlugins:l=[],id:c,editorAttrs:d})=>{const u=v.useCallback(M=>{if(n){const T=Rs?.createChangeEvent?Rs.createChangeEvent({value:M}):{target:{value:M}};n(T)}},[n]),[f,g]=Jh?Jh(e,[],u,{isEqual:HS}):(()=>{const[M,T]=v.useState(e),P=v.useRef(n);P.current=n;const L=v.useRef(!1);v.useEffect(()=>{if(L.current){L.current=!1;return}T(e)},[e]);const $=v.useCallback(A=>{L.current=!0,T(D=>{const O=typeof A=="function"?A(D):A;if(P.current){const z=Rs?.createChangeEvent?Rs.createChangeEvent({value:O}):{target:{value:O}};P.current(z)}return O})},[]);return[M,$]})(),m=v.useMemo(()=>new Set(f.map(M=>M.id)),[f]),p=v.useCallback(M=>M.is_assignable?[M.id]:M.children.flatMap(T=>p(T)),[]),y=v.useCallback(M=>{const T=p(M);return T.length>0&&T.every(P=>m.has(P))},[m,p]),b=v.useCallback(M=>{const T=p(M),P=T.filter(L=>m.has(L)).length;return P>0&&Pp(M).filter(P=>m.has(P)).length,[m,p]),x=v.useCallback((M,T)=>{o||g(P=>T?[...P,{id:M,scope_type:null}]:P.filter(L=>L.id!==M))},[o,g]),E=v.useCallback((M,T)=>{if(o)return;const P=p(M),L=new Set(P);g($=>{if(T){const A=new Set($.map(O=>O.id)),D=P.filter(O=>!A.has(O)).map(O=>({id:O,scope_type:null}));return[...$,...D]}else return $.filter(A=>!L.has(A.id))})},[o,p,g]),C=v.useCallback((M,T)=>{o||g(P=>P.map(L=>L.id===M?{...L,scope_type:T}:L))},[o,g]),k=M=>{const T=m.has(M.id),P=!!M.resource_route_key,L=T&&P&&r.length>0,$=f.find(A=>A.id===M.id)?.scope_type??null;return h.jsxs(sn,{className:`flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors ${T?"border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900/20":"border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700/50"} ${o?"opacity-50 cursor-not-allowed":""}`,children:[h.jsx(kt,{type:"checkbox",checked:T,onChange:A=>{A.stopPropagation(),x(M.id,A.target.checked)},disabled:o,className:"w-4 h-4 mt-0.5 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsxs(I,{className:"flex-1 min-w-0",children:[h.jsxs(I,{className:"flex items-center justify-between gap-2",children:[h.jsx(Q,{className:"text-sm font-medium text-gray-900 dark:text-white",children:M.name}),L&&h.jsx(VS,{options:r,value:$,onChange:A=>C(M.id,A),disabled:o})]}),M.description&&h.jsx(Q,{className:"block text-xs text-gray-500 dark:text-gray-400 mt-0.5",children:M.description})]})]},M.id)},N=(M,T)=>{const P=y(M),L=b(M),$=_(M),A=M.children.filter(z=>z.is_assignable);if(A.length===0&&M.children.length===0)return null;const D=M.children.some(z=>!z.is_assignable),O=i===1?"grid-cols-1":i===3?"grid-cols-1 md:grid-cols-3":"grid-cols-1 md:grid-cols-2";return h.jsxs(sc,{defaultOpen:!1,className:"border-0 rounded-none",children:[h.jsxs(I,{slot:"trigger",className:"flex items-center gap-3 w-full py-2",children:[h.jsx("input",{type:"checkbox",checked:P,ref:z=>{z&&(z.indeterminate=L)},onChange:z=>{z.stopPropagation(),E(M,z.target.checked)},onClick:z=>z.stopPropagation(),onMouseDown:z=>z.stopPropagation(),disabled:o,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(re,{name:ce.Folder,className:"w-4 h-4 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:M.name}),h.jsxs(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 ml-1",children:[$,"/",M.leaf_count]}),h.jsx(re,{name:ce.ChevronDown,className:"w-4 h-4 text-gray-400 dark:text-gray-500 ml-auto"})]}),h.jsx(I,{slot:"content",className:`pl-8 grid ${O} gap-2 py-2`,children:D?M.children.map(z=>z.is_assignable?k(z):N(z)):A.map(z=>k(z))})]},M.id)},S=M=>{if(M.extension_type==="core")return"core";if(M.extension_type==="module")return"module";if(M.extension_type==="plugin")return"plugin";const T=M.extension_identifier??M.module??"";return T==="core"||!T?"core":l?.some(L=>L.identifier===T)?"plugin":"module"},w=M=>{const T=y(M),P=b(M),L=_(M),$=S(M);return h.jsxs(sc,{defaultOpen:!1,className:"mb-2",children:[h.jsxs(I,{slot:"trigger",className:"flex items-center gap-3 w-full py-2",children:[h.jsx("input",{type:"checkbox",checked:T,ref:A=>{A&&(A.indeterminate=P)},onChange:A=>{A.stopPropagation(),E(M,A.target.checked)},onClick:A=>A.stopPropagation(),onMouseDown:A=>A.stopPropagation(),disabled:o,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(re,{name:$==="core"?ce.Cog:ce.Cube,className:"w-5 h-5 text-gray-400 dark:text-gray-500"}),h.jsx(Q,{className:"font-medium text-gray-900 dark:text-white",children:M.name}),$!=="core"&&h.jsx(nh,{type:$,identifier:M.extension_identifier??M.module,installedModules:a,installedPlugins:l}),h.jsxs(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 ml-2",children:[L,"/",M.leaf_count]}),h.jsx(re,{name:ce.ChevronDown,className:"w-4 h-4 text-gray-400 dark:text-gray-500 ml-auto"})]}),h.jsx(I,{slot:"content",className:"pl-8 space-y-2 pb-2",children:M.children.map(A=>A.is_assignable?k(A):N(A))})]},M.id)};return t.length===0?h.jsx(I,{className:`text-sm text-gray-400 dark:text-gray-500 py-4 text-center ${s}`,id:c,...d,children:Rh("common.no_data")??"데이터가 없습니다."}):h.jsx(I,{className:`space-y-2 ${s}`,id:c,...d,children:t.map(M=>w(M))})};function BS({text:t,keyword:e}){if(!e||!t)return h.jsx(h.Fragment,{children:t});const n=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=t.split(new RegExp(`(${n})`,"gi"));return h.jsx(h.Fragment,{children:r.map((s,o)=>s.toLowerCase()===e.toLowerCase()?h.jsx(Q,{className:"bg-yellow-200 dark:bg-yellow-700 rounded px-0.5",children:s},o):s)})}const jS=({data:t=[],expandedIds:e=[],selectedIds:n=[],searchKeyword:r="",showProductCount:s=!1,selectable:o=!1,className:i="",onToggle:a,onSelectionChange:l,id:c,editorAttrs:d})=>{const u=v.useCallback(y=>{if(!a)return;const b=e.includes(y)?e.filter(_=>_!==y):[...e,y];a(b)},[e,a]),f=v.useCallback((y,b)=>{if(!l)return;const _=b?[...n,y]:n.filter(x=>x!==y);l(_)},[n,l]),g=v.useMemo(()=>{if(!r)return null;const y=new Set,b=_=>{for(const x of _)(x.localized_name||x.name).toLowerCase().includes(r.toLowerCase())&&y.add(x.id),x.children&&b(x.children)};return b(t),y},[t,r]),m=v.useCallback(y=>!g||g.has(y.id)?!0:(y.children??[]).some(b=>m(b)),[g]),p=(y,b=0)=>{const _=(y.children??[]).length>0,x=e.includes(y.id),E=n.includes(y.id),C=y.localized_name||y.name;return r&&!m(y)?null:h.jsxs(I,{"data-testid":`category-node-${y.id}`,children:[h.jsxs(I,{className:`flex items-center gap-2 py-1.5 px-2 rounded cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors ${b>0?"ml-"+Math.min(b*5,20):""}`,style:b>0?{marginLeft:`${b*20}px`}:void 0,children:[_?h.jsx(I,{className:"flex-shrink-0 w-5 h-5 flex items-center justify-center text-gray-400 dark:text-gray-500",onClick:()=>u(y.id),"data-testid":`toggle-${y.id}`,children:h.jsx(re,{name:x?ce.CaretDown:ce.CaretRight,className:"text-xs"})}):h.jsx(I,{className:"flex-shrink-0 w-5 h-5"}),o&&h.jsx(vn,{checked:E,onChange:k=>{k.stopPropagation(),f(y.id,k.target.checked)},className:"flex-shrink-0","data-testid":`checkbox-${y.id}`}),h.jsx(re,{name:ce.Folder,className:`flex-shrink-0 text-sm ${_?"text-yellow-500 dark:text-yellow-400":"text-gray-400 dark:text-gray-500"}`}),h.jsx(Q,{className:"text-sm text-gray-700 dark:text-gray-300 select-none",onClick:()=>_&&u(y.id),children:h.jsx(BS,{text:C,keyword:r})}),s&&y.products_count!==void 0&&h.jsx(Q,{className:"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400 ml-1",children:y.products_count})]}),_&&x&&h.jsx(I,{"data-testid":`children-${y.id}`,children:(y.children??[]).map(k=>p(k,b+1))})]},y.id)};return t.length===0?h.jsx(I,{className:`text-sm text-gray-400 dark:text-gray-500 py-4 text-center ${i}`,id:c,...d,children:"데이터가 없습니다."}):h.jsx(I,{className:`border border-gray-200 dark:border-gray-700 rounded-lg p-2 max-h-80 overflow-y-auto ${i}`,id:c,...d,children:t.map(y=>p(y,0))})},eg="carousel",eo="controller",WS="navigation",GS="no-scroll",oc="portal",YS="root",tg="toolbar",XS="counter",to="fullscreen",ng="slideshow",no="thumbnails",rg="zoom",Jr="loading",ic="playing",ro="error",so="complete",KS="placeholder",oo=t=>`active-slide-${t}`,qS=oo(Jr),ZS=oo(ic),QS=oo(ro),JS=oo(so),sg="fullsize",Rr="flex_center",RS="no_scroll",og="no_scroll_padding",ac="slide",ig="slide_wrapper",e_="slide_wrapper_interactive",rr="prev",sr="next",lc="swipe",es="close",ag="onPointerDown",lg="onPointerMove",cg="onPointerUp",dg="onPointerLeave",ug="onPointerCancel",cc="onKeyDown",t_="onKeyUp",dc="onWheel",n_="Escape",r_="ArrowLeft",s_="ArrowRight",o_="button",Pi="icon",fg="contain",hg="cover",gg="Unknown action type",pg="yarl__";function zt(...t){return t.filter(Boolean).join(" ")}function Ne(t){return`${pg}${t}`}function dt(t){return`--${pg}${t}`}function pr(t,e){return`${t}${e?`_${e}`:""}`}function mr(t){return e=>pr(t,e)}function br(t,e){var n;return(n=t?.[e])!==null&&n!==void 0?n:e}function mg(t,e,n){return br(t,"{index} of {total}").replace(/\{index}/g,`${gc(n,e.length)+1}`).replace(/\{total}/g,`${e.length}`)}function ts(...t){return()=>{t.forEach(e=>{e()})}}function ln(t,e,n){return()=>{const r=F.useContext(n);if(!r)throw new Error(`${t} must be used within a ${e}.Provider`);return r}}function uc(){return typeof window<"u"}function fc(t,e=0){const n=10**e;return Math.round((t+Number.EPSILON)*n)/n}function or(t){return t.type===void 0||t.type==="image"}function hc(t,e){return t.imageFit===hg||t.imageFit!==fg&&e===hg}function $i(t){return typeof t=="string"?Number.parseInt(t,10):t}function Fi(t){if(typeof t=="number")return{pixel:t};if(typeof t=="string"){const e=$i(t);return t.endsWith("%")?{percent:e}:{pixel:e}}return{pixel:0}}function i_(t,e){const n=Fi(e),r=n.percent!==void 0?t.width/100*n.percent:n.pixel;return{width:Math.max(t.width-2*r,0),height:Math.max(t.height-2*r,0)}}function a_(){return(uc()?window?.devicePixelRatio:void 0)||1}function gc(t,e){return e>0?(t%e+e)%e:0}function pc(t){return t.length>0}function mc(t,e){return t[gc(e,t.length)]}function bc(t,e){return pc(t)?mc(t,e):void 0}function bg(t){return or(t)?t.src:void 0}function zi(t,e,n){if(!n)return t;const{buttons:r,...s}=t,o=r.findIndex(a=>a===e),i=F.isValidElement(n)?F.cloneElement(n,{key:e},null):n;if(o>=0){const a=[...r];return a.splice(o,1,i),{buttons:a,...s}}return{buttons:[i,...r],...s}}function yg(t,e,n=0){return Math.min(t.preload,Math.max(t.finite?e.length-1:Math.floor(e.length/2),n))}const l_=Number(F.version.split(".")[0])>=19;function c_(t){return{inert:l_?t:t?"":void 0}}function d_(t){t.scrollTop}const yc={open:!1,close:()=>{},index:0,slides:[],render:{},plugins:[],toolbar:{buttons:[es]},labels:{},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},carousel:{finite:!1,preload:2,padding:"16px",spacing:"30%",imageFit:fg,imageProps:{}},controller:{ref:null,focus:!0,aria:!1,touchAction:"none",closeOnPullUp:!1,closeOnPullDown:!1,closeOnBackdropClick:!1,closeOnEscape:!0,preventDefaultWheelX:!0,preventDefaultWheelY:!1,disableSwipeNavigation:!1},portal:{},noScroll:{disabled:!1},on:{},styles:{},className:""};function un(t,e){return{name:t,component:e}}function Tt(t,e){return{module:t,children:e}}function vg(t,e,n){return t.module.name===e?n(t):t.children?[Tt(t.module,t.children.flatMap(r=>{var s;return(s=vg(r,e,n))!==null&&s!==void 0?s:[]}))]:[t]}function ns(t,e,n){return t.flatMap(r=>{var s;return(s=vg(r,e,n))!==null&&s!==void 0?s:[]})}function u_(t,e=[],n=[]){let r=t;const s=g=>{const m=[...r];for(;m.length>0;){const p=m.pop();if(p?.module.name===g)return!0;p?.children&&m.push(...p.children)}return!1},o=(g,m)=>{if(g===""){r=[Tt(m,r)];return}r=ns(r,g,p=>[Tt(m,[p])])},i=(g,m)=>{r=ns(r,g,p=>[Tt(p.module,[Tt(m,p.children)])])},a=(g,m,p)=>{r=ns(r,g,y=>{var b;return[Tt(y.module,[...p?[Tt(m)]:[],...(b=y.children)!==null&&b!==void 0?b:[],...p?[]:[Tt(m)]])]})},l=(g,m,p)=>{r=ns(r,g,y=>[...p?[Tt(m)]:[],y,...p?[]:[Tt(m)]])},c=g=>{i(eo,g)},d=(g,m)=>{r=ns(r,g,p=>[Tt(m,p.children)])},u=g=>{r=ns(r,g,m=>m.children)},f=g=>{n.push(g)};return e.forEach(g=>{g({contains:s,addParent:o,append:i,addChild:a,addSibling:l,addModule:c,replace:d,remove:u,augment:f})}),{config:r,augmentation:g=>n.reduce((m,p)=>p(m),g)}}const xg=F.createContext(null),vc=ln("useA11yContext","A11yContext",xg);function f_({children:t}){const[e,n]=F.useState(!1),[r,s]=F.useState(!1),o=F.useMemo(()=>({focusWithin:e,trackFocusWithin:(a,l)=>{const c=d=>u=>{var f;u.currentTarget.contains(u.relatedTarget)||n(d),(f=d?a:l)===null||f===void 0||f(u)};return{onFocus:c(!0),onBlur:c(!1)}},autoPlaying:r,setAutoPlaying:s}),[e,r]);return F.createElement(xg.Provider,{value:o},t)}const wg=F.createContext(null),rs=ln("useDocument","DocumentContext",wg);function h_({nodeRef:t,children:e}){const n=F.useMemo(()=>{const r=o=>{var i;return((i=o||t.current)===null||i===void 0?void 0:i.ownerDocument)||document};return{getOwnerDocument:r,getOwnerWindow:o=>{var i;return((i=r(o))===null||i===void 0?void 0:i.defaultView)||window}}},[t]);return F.createElement(wg.Provider,{value:n},e)}const kg=F.createContext(null),ss=ln("useEvents","EventsContext",kg);function g_({children:t}){const[e]=F.useState({});F.useEffect(()=>()=>{Object.keys(e).forEach(r=>delete e[r])},[e]);const n=F.useMemo(()=>{const r=(i,a)=>{var l;(l=e[i])===null||l===void 0||l.splice(0,e[i].length,...e[i].filter(c=>c!==a))};return{publish:(...[i,a])=>{var l;(l=e[i])===null||l===void 0||l.forEach(c=>c(a))},subscribe:(i,a)=>(e[i]||(e[i]=[]),e[i].push(a),()=>r(i,a)),unsubscribe:r}},[e]);return F.createElement(kg.Provider,{value:n},t)}const Cg=F.createContext(null),At=ln("useLightboxProps","LightboxPropsContext",Cg);function Sg({children:t,...e}){return F.createElement(Cg.Provider,{value:e},t)}const _g=F.createContext(null),fn=ln("useLightboxState","LightboxStateContext",_g),Eg=F.createContext(null),p_=ln("useLightboxDispatch","LightboxDispatchContext",Eg);function m_(t,e){switch(e.type){case"swipe":{const{slides:n}=t,r=e?.increment||0,s=t.globalIndex+r,o=gc(s,n.length),i=bc(n,o),a=r||e.duration!==void 0?{increment:r,duration:e.duration,easing:e.easing}:void 0;return{slides:n,currentIndex:o,globalIndex:s,currentSlide:i,animation:a}}case"update":return e.slides!==t.slides||e.index!==t.currentIndex?{slides:e.slides,currentIndex:e.index,globalIndex:e.index,currentSlide:bc(e.slides,e.index)}:t;default:throw new Error(gg)}}function b_({slides:t,index:e,children:n}){const[r,s]=F.useReducer(m_,{slides:t,currentIndex:e,globalIndex:e,currentSlide:bc(t,e)}),[o,i]=F.useState(t),[a,l]=F.useState(e);(t!==o||e!==a)&&(i(t),l(e),s({type:"update",slides:t,index:e}));const c=F.useMemo(()=>({...r,state:r,dispatch:s}),[r,s]);return F.createElement(Eg.Provider,{value:s},F.createElement(_g.Provider,{value:c},n))}const Mg=F.createContext(null),y_=ln("useRTLContext","RTLContext",Mg);function v_({isRTL:t,children:e}){const n=F.useMemo(()=>({isRTL:t}),[t]);return F.createElement(Mg.Provider,{value:n},e)}const Ng=F.createContext(null),io=ln("useTimeouts","TimeoutsContext",Ng);function x_({children:t}){const[e]=F.useState([]);F.useEffect(()=>()=>{e.forEach(r=>window.clearTimeout(r)),e.splice(0,e.length)},[e]);const n=F.useMemo(()=>{const r=i=>{e.splice(0,e.length,...e.filter(a=>a!==i))};return{setTimeout:(i,a)=>{const l=window.setTimeout(()=>{r(l),i()},a);return e.push(l),l},clearTimeout:i=>{i!==void 0&&(r(i),window.clearTimeout(i))}}},[e]);return F.createElement(Ng.Provider,{value:n},t)}const os=F.forwardRef(function({label:e,className:n,icon:r,renderIcon:s,onClick:o,style:i,...a},l){const{styles:c,labels:d}=At(),u=br(d,e);return F.createElement("button",{ref:l,type:"button",title:u,"aria-label":u,className:zt(Ne(o_),n),onClick:o,style:{...i,...c.button},...a},s?s():F.createElement(r,{className:Ne(Pi),style:c.icon}))});function Og(t,e){const n=r=>F.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",width:"24",height:"24","aria-hidden":"true",focusable:"false",...r},e);return n.displayName=t,n}function Jt(t,e){return Og(t,F.createElement("g",{fill:"currentColor"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),e))}function w_(t,e){return Og(t,F.createElement(F.Fragment,null,F.createElement("defs",null,F.createElement("mask",{id:"strike"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"white"}),F.createElement("path",{d:"M0 0L24 24",stroke:"black",strokeWidth:4}))),F.createElement("path",{d:"M0.70707 2.121320L21.878680 23.292883",stroke:"currentColor",strokeWidth:2}),F.createElement("g",{fill:"currentColor",mask:"url(#strike)"},F.createElement("path",{d:"M0 0h24v24H0z",fill:"none"}),e)))}const k_=Jt("Close",F.createElement("path",{d:"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"})),C_=Jt("Previous",F.createElement("path",{d:"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"})),S_=Jt("Next",F.createElement("path",{d:"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"})),__=Jt("Loading",F.createElement(F.Fragment,null,Array.from({length:8}).map((t,e,n)=>F.createElement("line",{key:e,x1:"12",y1:"6.5",x2:"12",y2:"1.8",strokeLinecap:"round",strokeWidth:"2.6",stroke:"currentColor",strokeOpacity:1/n.length*(e+1),transform:`rotate(${360/n.length*e}, 12, 12)`})))),E_=Jt("Error",F.createElement("path",{d:"M21.9,21.9l-8.49-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1,0.9,2,2,2h13.17l2.31,2.31L21.9,21.9z M5,18 l3.5-4.5l2.5,3.01L12.17,15l3,3H5z M21,18.17L5.83,3H19c1.1,0,2,0.9,2,2V18.17z"})),Cn=uc()?F.useLayoutEffect:F.useEffect;function xc(){const[t,e]=F.useState(!1);return F.useEffect(()=>{var n,r;const s=(n=window.matchMedia)===null||n===void 0?void 0:n.call(window,"(prefers-reduced-motion: reduce)");e(s?.matches);const o=i=>e(i.matches);return(r=s?.addEventListener)===null||r===void 0||r.call(s,"change",o),()=>{var i;return(i=s?.removeEventListener)===null||i===void 0?void 0:i.call(s,"change",o)}},[]),t}function M_(t){let e=0,n=0,r=0;const o=window.getComputedStyle(t).transform.match(/matrix.*\((.+)\)/);if(o){const i=o[1].split(",").map($i);i.length===6?(e=i[4],n=i[5]):i.length===16&&(e=i[12],n=i[13],r=i[14])}return{x:e,y:n,z:r}}function wc(t,e){const n=F.useRef(void 0),r=F.useRef(void 0),s=xc();return Cn(()=>{var o,i,a;if(t.current&&n.current!==void 0&&!s){const{keyframes:l,duration:c,easing:d,onfinish:u}=e(n.current,t.current.getBoundingClientRect(),M_(t.current))||{};if(l&&c){(o=r.current)===null||o===void 0||o.cancel(),r.current=void 0;try{r.current=(a=(i=t.current).animate)===null||a===void 0?void 0:a.call(i,l,{duration:c,easing:d})}catch(f){console.error(f)}r.current&&(r.current.onfinish=()=>{r.current=void 0,u?.()})}}n.current=void 0}),{prepareAnimation:o=>{n.current=o},isAnimationPlaying:()=>{var o;return((o=r.current)===null||o===void 0?void 0:o.playState)==="running"}}}function Tg(){const t=F.useRef(null),e=F.useRef(void 0),[n,r]=F.useState();return{setContainerRef:F.useCallback(o=>{t.current=o,e.current&&(e.current.disconnect(),e.current=void 0);const i=()=>{if(o){const a=window.getComputedStyle(o),l=c=>parseFloat(c)||0;r({width:Math.round(o.clientWidth-l(a.paddingLeft)-l(a.paddingRight)),height:Math.round(o.clientHeight-l(a.paddingTop)-l(a.paddingBottom))})}else r(void 0)};i(),o&&typeof ResizeObserver<"u"&&(e.current=new ResizeObserver(i),e.current.observe(o))},[]),containerRef:t,containerRect:n}}function Vi(){const t=F.useRef(void 0),{setTimeout:e,clearTimeout:n}=io();return F.useCallback((r,s)=>{n(t.current),t.current=e(r,s>0?s:0)},[e,n])}function We(t){const e=F.useRef(t);return Cn(()=>{e.current=t}),F.useCallback((...n)=>{var r;return(r=e.current)===null||r===void 0?void 0:r.call(e,...n)},[])}function Ag(t,e){typeof t=="function"?t(e):t&&(t.current=e)}function kc(t,e){return F.useMemo(()=>t==null&&e==null?null:n=>{Ag(t,n),Ag(e,n)},[t,e])}function Dg(t,e=!1){const n=F.useRef(!1);Cn(()=>{e&&n.current&&(n.current=!1,t())},[e,t]);const r=F.useCallback(()=>{n.current=!0},[]),s=F.useCallback(()=>{n.current=!1},[]);return{onFocus:r,onBlur:s}}function Hi(){return y_().isRTL}function Ig(){const[t]=F.useState({}),e=F.useCallback((s,o)=>{var i;(i=t[s])===null||i===void 0||i.forEach(a=>{o.isPropagationStopped()||a(o)})},[t]),n=F.useMemo(()=>({onPointerDown:s=>e(ag,s),onPointerMove:s=>e(lg,s),onPointerUp:s=>e(cg,s),onPointerLeave:s=>e(dg,s),onPointerCancel:s=>e(ug,s),onKeyDown:s=>e(cc,s),onKeyUp:s=>e(t_,s),onWheel:s=>e(dc,s)}),[e]),r=F.useCallback((s,o)=>(t[s]||(t[s]=[]),t[s].unshift(o),()=>{const i=t[s];i&&i.splice(0,i.length,...i.filter(a=>a!==o))}),[t]);return{registerSensors:n,subscribeSensors:r}}function Lg(t,e){const n=F.useRef(0),r=Vi(),s=We((...o)=>{n.current=Date.now(),t(o)});return F.useCallback((...o)=>{r(()=>{s(o)},e-(Date.now()-n.current))},[e,s,r])}const Cc=mr("slide"),Sc=mr("slide_image");function yr({slide:t,offset:e,render:n,rect:r,imageFit:s,imageProps:o,onClick:i,onLoad:a,onError:l,style:c}){var d,u,f,g,m,p,y,b;const[_,x]=F.useState(Jr),{publish:E}=ss(),{setTimeout:C}=io(),k=F.useRef(null);F.useEffect(()=>{e===0&&E(oo(_))},[e,_,E]);const N=We(U=>{("decode"in U?U.decode():Promise.resolve()).catch(()=>{}).then(()=>{U.parentNode&&(x(so),C(()=>{a?.(U)},0))})}),S=F.useCallback(U=>{k.current=U,U?.complete&&N(U)},[N]),w=F.useCallback(U=>{N(U.currentTarget)},[N]),M=We(()=>{x(ro),l?.()}),T=hc(t,s),P=(U,B)=>Number.isFinite(U)?U:B,L=P(Math.max(...((u=(d=t.srcSet)===null||d===void 0?void 0:d.map(U=>U.width))!==null&&u!==void 0?u:[]).concat(t.width?[t.width]:[]).filter(Boolean)),((f=k.current)===null||f===void 0?void 0:f.naturalWidth)||0),$=P(Math.max(...((m=(g=t.srcSet)===null||g===void 0?void 0:g.map(U=>U.height))!==null&&m!==void 0?m:[]).concat(t.height?[t.height]:[]).filter(Boolean)),((p=k.current)===null||p===void 0?void 0:p.naturalHeight)||0),A=L&&$?{maxWidth:`min(${L}px, 100%)`,maxHeight:`min(${$}px, 100%)`}:{maxWidth:"100%",maxHeight:"100%"},D=(y=t.srcSet)===null||y===void 0?void 0:y.slice().sort((U,B)=>U.width-B.width).map(U=>`${U.src} ${U.width}w`).join(", "),O=()=>r&&!T&&t.width&&t.height?r.height/t.height*t.width:Number.MAX_VALUE,z=D&&r&&uc()?`${Math.round(Math.min(O(),r.width))}px`:void 0,{style:H,className:j,...W}=(typeof o=="function"?o(t):o)||{};return F.createElement(F.Fragment,null,F.createElement("img",{ref:S,onLoad:w,onError:M,onClick:i,draggable:!1,className:zt(Ne(Sc()),T&&Ne(Sc("cover")),_!==so&&Ne(Sc("loading")),j),style:{...A,...c,...H},...W,alt:(b=t.alt)!==null&&b!==void 0?b:"",sizes:z,srcSet:D,src:t.src}),_!==so&&F.createElement("div",{className:Ne(Cc(KS))},_===Jr&&(n?.iconLoading?n.iconLoading():F.createElement(__,{className:zt(Ne(Pi),Ne(Cc(Jr)))})),_===ro&&(n?.iconError?n.iconError():F.createElement(E_,{className:zt(Ne(Pi),Ne(Cc(ro)))}))))}const N_=F.forwardRef(function({className:e,children:n,onFocus:r,onBlur:s,...o},i){const a=F.useRef(null),[l,c]=F.useState(!1),{trackFocusWithin:d}=vc(),u=We(()=>{if(a.current){const f=window.getComputedStyle(a.current).direction==="rtl";f!==l&&c(f)}});return F.useEffect(u),F.createElement(h_,{nodeRef:a},F.createElement(v_,{isRTL:l},F.createElement("div",{ref:kc(i,a),className:zt(Ne("root"),e),...d(r,s),...o},n)))});var Vt;(function(t){t[t.NONE=0]="NONE",t[t.SWIPE=1]="SWIPE",t[t.PULL=2]="PULL",t[t.ANIMATION=3]="ANIMATION"})(Vt||(Vt={}));function Pg(t,e,n,r,s){F.useEffect(()=>s?()=>{}:ts(t(ag,e),t(lg,n),t(cg,r),t(dg,r),t(ug,r)),[t,e,n,r,s])}var Sn;(function(t){t[t.NONE=0]="NONE",t[t.SWIPE=1]="SWIPE",t[t.PULL=2]="PULL"})(Sn||(Sn={}));const _c=30;function O_({disableSwipeNavigation:t,closeOnBackdropClick:e},n,r,s,o,i,a,l,c,d,u,f,g,m,p,y){const b=F.useRef(0),_=F.useRef([]),x=F.useRef(void 0),E=F.useRef(0),C=F.useRef(Sn.NONE),k=F.useCallback(L=>{x.current===L.pointerId&&(x.current=void 0,C.current=Sn.NONE);const $=_.current;$.splice(0,$.length,...$.filter(A=>A.pointerId!==L.pointerId))},[]),N=F.useCallback(L=>{k(L),L.persist(),_.current.push(L)},[k]),S=F.useCallback(L=>_.current.find(({pointerId:$})=>L.pointerId===$),[]),w=We(L=>{N(L)}),M=(L,$)=>u&&L>$||d&&L<-$,T=We(L=>{const $=S(L);if($)if(x.current===L.pointerId){const A=Date.now()-E.current,D=b.current;C.current===Sn.SWIPE?Math.abs(D)>.3*s||Math.abs(D)>5&&A{const $=S(L);if($){const A=x.current===L.pointerId;if(L.buttons===0){A&&b.current!==0?T(L):k($);return}const D=L.clientX-$.clientX,O=L.clientY-$.clientY;if(x.current===void 0){const z=H=>{N(L),x.current=L.pointerId,E.current=Date.now(),C.current=H};Math.abs(D)>Math.abs(O)&&Math.abs(D)>_c&&r(D)?t||(z(Sn.SWIPE),i()):Math.abs(O)>Math.abs(D)&&M(O,_c)&&(z(Sn.PULL),f())}else A&&(C.current===Sn.SWIPE?(b.current=D,a(D)):C.current===Sn.PULL&&(b.current=O,g(O)))}});Pg(n,w,P,T)}function T_({preventDefaultWheelX:t,preventDefaultWheelY:e}){const n=F.useRef(null),r=We(s=>{const o=Math.abs(s.deltaX)>Math.abs(s.deltaY);(o&&t||!o&&e||s.ctrlKey)&&s.preventDefault()});return F.useCallback(s=>{var o;s?s.addEventListener("wheel",r,{passive:!1}):(o=n.current)===null||o===void 0||o.removeEventListener("wheel",r),n.current=s},[r])}function A_(t,e,n,r,s,o,i,a,l){const c=F.useRef(0),d=F.useRef(0),u=F.useRef(void 0),f=F.useRef(void 0),g=F.useRef(0),m=F.useRef(void 0),p=F.useRef(0),{setTimeout:y,clearTimeout:b}=io(),_=F.useCallback(()=>{u.current&&(b(u.current),u.current=void 0)},[b]),x=F.useCallback(()=>{f.current&&(b(f.current),f.current=void 0)},[b]),E=We(()=>{t!==Vt.SWIPE&&(c.current=0,p.current=0,_(),x())});F.useEffect(E,[t,E]);const C=We(N=>{f.current=void 0,c.current===N&&l(c.current)}),k=We(N=>{if(N.ctrlKey||Math.abs(N.deltaY)>Math.abs(N.deltaX))return;const S=w=>{g.current=w,b(m.current),m.current=w>0?y(()=>{g.current=0,m.current=void 0},300):void 0};if(t===Vt.NONE){if(Math.abs(N.deltaX)<=1.2*Math.abs(g.current)){S(N.deltaX);return}if(!n(-N.deltaX))return;if(d.current+=N.deltaX,_(),Math.abs(d.current)>30)d.current=0,S(0),p.current=Date.now(),o();else{const w=d.current;u.current=y(()=>{u.current=void 0,w===d.current&&(d.current=0)},s)}}else if(t===Vt.SWIPE){let w=c.current-N.deltaX;if(w=Math.min(Math.abs(w),r)*Math.sign(w),c.current=w,i(w),x(),Math.abs(w)>.2*r){S(N.deltaX),a(w,Date.now()-p.current);return}f.current=y(()=>C(w),2*s)}else S(N.deltaX)});F.useEffect(()=>e(dc,k),[e,k])}const $g=mr("container"),Fg=F.createContext(null),_n=ln("useController","ControllerContext",Fg);function D_({children:t,...e}){var n;const{carousel:r,animation:s,controller:o,on:i,styles:a,render:l}=e,{closeOnPullUp:c,closeOnPullDown:d,preventDefaultWheelX:u,preventDefaultWheelY:f}=o,[g,m]=F.useState(),p=fn(),y=p_(),[b,_]=F.useState(Vt.NONE),x=F.useRef(0),E=F.useRef(0),C=F.useRef(1),{registerSensors:k,subscribeSensors:N}=Ig(),{subscribe:S,publish:w}=ss(),M=Vi(),T=Vi(),P=Vi(),{containerRef:L,setContainerRef:$,containerRect:A}=Tg(),D=kc(T_({preventDefaultWheelX:u,preventDefaultWheelY:f}),$),O=F.useRef(null),z=kc(O,void 0),{getOwnerDocument:H}=rs(),j=Hi(),W=se=>(j?-1:1)*(typeof se=="number"?se:1),U=We(()=>{var se;return(se=L.current)===null||se===void 0?void 0:se.focus()}),B=We(()=>e),G=We(()=>p),V=F.useCallback(se=>w(rr,se),[w]),K=F.useCallback(se=>w(sr,se),[w]),X=F.useCallback(()=>w(es),[w]),Z=se=>!(r.finite&&(W(se)>0&&p.currentIndex===0||W(se)<0&&p.currentIndex===p.slides.length-1)),ee=se=>{var Oe;x.current=se,(Oe=L.current)===null||Oe===void 0||Oe.style.setProperty(dt("swipe_offset"),`${Math.round(se)}px`)},ne=se=>{var Oe,Le;E.current=se,C.current=(()=>{const st=d&&se>0?se:c&&se<0?-se:0;return Math.min(Math.max(fc(1-st/60*(1-.5),2),.5),1)})(),(Oe=L.current)===null||Oe===void 0||Oe.style.setProperty(dt("pull_offset"),`${Math.round(se)}px`),(Le=L.current)===null||Le===void 0||Le.style.setProperty(dt("pull_opacity"),`${C.current}`)},{prepareAnimation:he}=wc(O,(se,Oe,Le)=>{if(O.current&&A)return{keyframes:[{transform:`translate(0, ${se.rect.y-Oe.y+Le.y}px)`,opacity:se.opacity},{transform:"translate(0, 0)",opacity:1}],duration:se.duration,easing:s.easing.fade}}),J=(se,Oe)=>{if(c||d){ne(se);let Le=0;O.current&&(Le=s.fade*(Oe?2:1),he({rect:O.current.getBoundingClientRect(),opacity:C.current,duration:Le})),P(()=>{ne(0),_(Vt.NONE)},Le),_(Vt.ANIMATION),Oe||X()}},{prepareAnimation:le,isAnimationPlaying:ue}=wc(O,(se,Oe,Le)=>{var Ie;if(O.current&&A&&(!((Ie=p.animation)===null||Ie===void 0)&&Ie.duration)){const Be=Fi(r.spacing),st=(Be.percent?Be.percent*A.width/100:Be.pixel)||0;return{keyframes:[{transform:`translate(${W(p.globalIndex-se.index)*(A.width+st)+se.rect.x-Oe.x+Le.x}px, 0)`},{transform:"translate(0, 0)"}],duration:p.animation.duration,easing:p.animation.easing}}}),ae=We(se=>{var Oe,Le;const Ie=se.offset||0,Be=Ie?s.swipe:(Oe=s.navigation)!==null&&Oe!==void 0?Oe:s.swipe,st=!Ie&&!ue()?s.easing.navigation:s.easing.swipe;let{direction:ut}=se;const je=(Le=se.count)!==null&&Le!==void 0?Le:1;let nt=Vt.ANIMATION,Ye=Be*je;if(!ut){const ge=A?.width,me=se.duration||0,pe=ge?Be/ge*Math.abs(Ie):Be;je!==0?(me0?rr:sr):Ye=Be/2}let te=0;ut===rr?Z(W(1))?te=-je:(nt=Vt.NONE,Ye=Be):ut===sr&&(Z(W(-1))?te=je:(nt=Vt.NONE,Ye=Be)),Ye=Math.round(Ye),T(()=>{ee(0),_(Vt.NONE)},Ye),O.current&&le({rect:O.current.getBoundingClientRect(),index:p.globalIndex}),_(nt),w(lc,{type:"swipe",increment:te,duration:Ye,easing:st})});F.useEffect(()=>{var se,Oe;!((se=p.animation)===null||se===void 0)&&se.increment&&(!((Oe=p.animation)===null||Oe===void 0)&&Oe.duration)&&M(()=>y({type:"swipe",increment:0}),p.animation.duration)},[p.animation,y,M]);const ve=[N,Z,A?.width||0,s.swipe,()=>_(Vt.SWIPE),se=>ee(se),(se,Oe)=>ae({offset:se,duration:Oe,count:1}),se=>ae({offset:se,count:0})],fe=[()=>{d&&_(Vt.PULL)},se=>ne(se),se=>J(se),se=>J(se,!0)];O_(o,...ve,c,d,...fe,X),A_(b,...ve);const ye=We(()=>{o.focus&&H().querySelector(`.${Ne(oc)} .${Ne($g())}`)&&U()});F.useEffect(ye,[ye]);const we=We(()=>{var se;(se=i.view)===null||se===void 0||se.call(i,{index:p.currentIndex})});F.useEffect(we,[p.globalIndex,we]),F.useEffect(()=>ts(S(rr,se=>ae({direction:rr,...se})),S(sr,se=>ae({direction:sr,...se})),S(lc,se=>y(se))),[S,ae,y]);const He=F.useMemo(()=>({prev:V,next:K,close:X,focus:U,slideRect:A?i_(A,r.padding):{width:0,height:0},containerRect:A||{width:0,height:0},subscribeSensors:N,containerRef:L,setCarouselRef:z,toolbarWidth:g,setToolbarWidth:m}),[V,K,X,U,N,A,L,z,g,m,r.padding]);return F.useImperativeHandle(o.ref,()=>({prev:V,next:K,close:X,focus:U,getLightboxProps:B,getLightboxState:G}),[V,K,X,U,B,G]),F.createElement("div",{ref:D,className:zt(Ne($g()),Ne(Rr)),style:{...b===Vt.SWIPE?{[dt("swipe_offset")]:`${Math.round(x.current)}px`}:null,...b===Vt.PULL?{[dt("pull_offset")]:`${Math.round(E.current)}px`,[dt("pull_opacity")]:`${C.current}`}:null,...o.touchAction!=="none"?{[dt("controller_touch_action")]:o.touchAction}:null,...a.container},tabIndex:-1,...k},A&&F.createElement(Fg.Provider,{value:He},t,(n=l.controls)===null||n===void 0?void 0:n.call(l)))}const I_=un(eo,D_);function vr(t){return pr(eg,t)}function zg(t){return pr(ac,t)}function L_({slide:t,offset:e}){const n=F.useRef(null),{currentIndex:r,slides:s}=fn(),{slideRect:o,focus:i}=_n(),{render:a,carousel:{imageFit:l,imageProps:c},on:{click:d},styles:{slide:u},labels:f}=At(),{getOwnerDocument:g}=rs(),m=e!==0;F.useEffect(()=>{var y;m&&(!((y=n.current)===null||y===void 0)&&y.contains(g().activeElement))&&i()},[m,i,g]);const p=()=>{var y,b,_,x;let E=(y=a.slide)===null||y===void 0?void 0:y.call(a,{slide:t,offset:e,rect:o});return!E&&or(t)&&(E=F.createElement(yr,{slide:t,offset:e,render:a,rect:o,imageFit:l,imageProps:c,onClick:m?void 0:()=>d?.({index:r})})),E?F.createElement(F.Fragment,null,(b=a.slideHeader)===null||b===void 0?void 0:b.call(a,{slide:t}),((_=a.slideContainer)!==null&&_!==void 0?_:(({children:C})=>C))({slide:t,children:E}),(x=a.slideFooter)===null||x===void 0?void 0:x.call(a,{slide:t})):null};return F.createElement("div",{ref:n,className:zt(Ne(zg()),!m&&Ne(zg("current")),Ne(Rr)),...c_(m),style:u,role:"group","aria-roledescription":br(f,"Slide"),"aria-label":mg(f,s,r+e)},p())}function P_(){const t=At().styles.slide;return F.createElement("div",{className:Ne(ac),style:t})}function $_({carousel:t,labels:e}){const{slides:n,currentIndex:r,globalIndex:s}=fn(),{setCarouselRef:o}=_n(),{autoPlaying:i,focusWithin:a}=vc(),l=Fi(t.spacing),c=Fi(t.padding),d=yg(t,n,1),u=[];if(pc(n))for(let f=r-d;f<=r+d;f+=1){const g=mc(n,f),m=s-r+f,p=t.finite&&(f<0||f>n.length-1);u.push(p?{key:m}:{key:[`${m}`,bg(g)].filter(Boolean).join("|"),offset:f-r,slide:g})}return F.createElement("div",{ref:o,className:zt(Ne(vr()),u.length>0&&Ne(vr("with_slides"))),style:{[`${dt(vr("slides_count"))}`]:u.length,[`${dt(vr("spacing_px"))}`]:l.pixel||0,[`${dt(vr("spacing_percent"))}`]:l.percent||0,[`${dt(vr("padding_px"))}`]:c.pixel||0,[`${dt(vr("padding_percent"))}`]:c.percent||0},role:"region","aria-live":i&&!a?"off":"polite","aria-roledescription":br(e,"Carousel"),"aria-label":br(e,"Photo gallery")},u.map(({key:f,slide:g,offset:m})=>g?F.createElement(L_,{key:f,slide:g,offset:m}):F.createElement(P_,{key:f})))}const F_=un(eg,$_);function Vg(){const{carousel:t}=At(),{slides:e,currentIndex:n}=fn(),r=e.length===0||t.finite&&n===0,s=e.length===0||t.finite&&n===e.length-1;return{prevDisabled:r,nextDisabled:s}}function Hg(t){var e;const n=Hi(),{publish:r}=ss(),{animation:s,controller:o}=At(),{prevDisabled:i,nextDisabled:a}=Vg(),l=((e=s.navigation)!==null&&e!==void 0?e:s.swipe)/2,c=Lg(()=>r(rr),l),d=Lg(()=>r(sr),l),u=We(f=>{switch(f.key){case n_:if(!o.closeOnEscape)return;r(es);break;case r_:(n?a:i)||(n?d:c)();break;case s_:(n?i:a)||(n?c:d)();break;default:return}f.stopPropagation()});F.useEffect(()=>t(cc,u),[t,u])}function Ug({label:t,icon:e,renderIcon:n,action:r,onClick:s,disabled:o,style:i}){return F.createElement(os,{label:t,icon:e,renderIcon:n,className:Ne(`navigation_${r}`),disabled:o,onClick:s,style:i,...Dg(_n().focus,o)})}function z_({render:{buttonPrev:t,buttonNext:e,iconPrev:n,iconNext:r},styles:s}){const{prev:o,next:i,subscribeSensors:a}=_n(),{prevDisabled:l,nextDisabled:c}=Vg();return Hg(a),F.createElement(F.Fragment,null,t?t():F.createElement(Ug,{label:"Previous",action:rr,icon:C_,renderIcon:n,style:s.navigationPrev,disabled:l,onClick:o}),e?e():F.createElement(Ug,{label:"Next",action:sr,icon:S_,renderIcon:r,style:s.navigationNext,disabled:c,onClick:i}))}const V_=un(WS,z_),Bg=Ne(RS),H_=Ne(og);function U_(t){return"style"in t}function jg(t,e,n){const r=window.getComputedStyle(t),s=n?"padding-left":"padding-right",o=n?r.paddingLeft:r.paddingRight,i=t.style.getPropertyValue(s);return t.style.setProperty(s,`${($i(o)||0)+e}px`),()=>{i?t.style.setProperty(s,i):t.style.removeProperty(s)}}function B_({noScroll:{disabled:t},children:e}){const n=Hi(),{getOwnerDocument:r,getOwnerWindow:s}=rs();return F.useEffect(()=>{if(t)return()=>{};const o=[],i=s(),{body:a,documentElement:l}=r(),c=Math.round(i.innerWidth-l.clientWidth);if(c>0){o.push(jg(a,c,n));const d=a.getElementsByTagName("*");for(let u=0;u{a.classList.remove(Bg),o.forEach(d=>d())}},[n,t,r,s]),F.createElement(F.Fragment,null,e)}const j_=un(GS,B_);function Wg(t){return pr(oc,t)}function Gg(t,e,n){const r=t.getAttribute(e);return t.setAttribute(e,n),()=>{r?t.setAttribute(e,r):t.removeAttribute(e)}}function W_({portal:{root:t,container:{className:e,style:n,...r}={}},animation:s,styles:o,className:i,on:a,close:l,labels:c,children:d}){const[u,f]=F.useState(!1),[g,m]=F.useState(!1),p=F.useRef([]),y=F.useRef(null),{setTimeout:b}=io(),{subscribe:_}=ss(),E=xc()?0:s.fade;F.useEffect(()=>(f(!0),()=>{f(!1),m(!1)}),[]);const C=We(()=>{p.current.forEach(w=>w()),p.current=[]}),k=We(()=>{var w;m(!1),C(),(w=a.exiting)===null||w===void 0||w.call(a),b(()=>{var M;(M=a.exited)===null||M===void 0||M.call(a),l()},E)});F.useEffect(()=>_(es,k),[_,k]);const N=We(w=>{var M,T,P;d_(w),m(!0),(M=a.entering)===null||M===void 0||M.call(a);const L=(P=(T=w.parentNode)===null||T===void 0?void 0:T.children)!==null&&P!==void 0?P:[];for(let $=0;${var $,A;(A=($=y.current)===null||$===void 0?void 0:$.focus)===null||A===void 0||A.call($)}),b(()=>{var $;($=a.entered)===null||$===void 0||$.call(a)},E)}),S=F.useCallback(w=>{w?N(w):C()},[N,C]);return u?Tn.createPortal(F.createElement(N_,{ref:S,className:zt(i,e,Ne(Wg()),Ne(og),g&&Ne(Wg("open"))),"aria-modal":!0,role:"dialog","aria-label":br(c,"Lightbox"),style:{...s.fade!==yc.animation.fade?{[dt("fade_animation_duration")]:`${E}ms`}:null,...s.easing.fade!==yc.animation.easing.fade?{[dt("fade_animation_timing_function")]:s.easing.fade}:null,...o.root,...n},onFocus:w=>{y.current||(y.current=w.relatedTarget)},...r},d),(typeof t=="function"?t():t)||document.body):null}const G_=un(oc,W_);function Y_({children:t}){return F.createElement(F.Fragment,null,t)}const X_=un(YS,Y_);function K_(t){return pr(tg,t)}function q_({toolbar:{buttons:t},render:{buttonClose:e,iconClose:n},styles:r}){const{close:s,setToolbarWidth:o}=_n(),{setContainerRef:i,containerRect:a}=Tg();Cn(()=>{o(a?.width)},[o,a?.width]);const l=()=>e?e():F.createElement(os,{key:es,label:"Close",icon:k_,renderIcon:n,onClick:s});return F.createElement("div",{ref:i,style:r.toolbar,className:Ne(K_())},t?.map(c=>c===es?l():c))}const Z_=un(tg,q_);function Yg(t,e){var n;return F.createElement(t.module.component,{key:t.module.name,...e},(n=t.children)===null||n===void 0?void 0:n.map(r=>Yg(r,e)))}function Q_(t,e={}){const{easing:n,...r}=t,{easing:s,...o}=e;return{easing:{...n,...s},...r,...o}}function J_({carousel:t,animation:e,render:n,toolbar:r,controller:s,noScroll:o,on:i,plugins:a,slides:l,index:c,...d}){const{animation:u,carousel:f,render:g,toolbar:m,controller:p,noScroll:y,on:b,slides:_,index:x,plugins:E,...C}=yc,{config:k,augmentation:N}=u_([Tt(G_,[Tt(j_,[Tt(I_,[Tt(F_),Tt(Z_),Tt(V_)])])])],a||E),S=N({animation:Q_(u,e),carousel:{...f,...t},render:{...g,...n},toolbar:{...m,...r},controller:{...p,...s},noScroll:{...y,...o},on:{...b,...i},...C,...d});return S.open?F.createElement(Sg,{...S},F.createElement(b_,{slides:l||_,index:$i(c||x)},F.createElement(x_,null,F.createElement(g_,null,F.createElement(f_,null,Yg(Tt(X_,k),S)))))):null}const Xg={minZoom:1,maxZoomPixelRatio:1,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:500,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,pinchZoomV4:!1,scrollToZoom:!1,maxZoom:8};function R_(t){return Math.min(Math.max(t,Number.EPSILON),1)}function Kg(t){const{minZoom:e,...n}={...Xg,...t};return{minZoom:R_(e),...n}}function e2(t,e,n,r){const s=F.useRef(void 0),o=F.useRef(void 0),{zoom:i}=At().animation,a=xc(),l=We(()=>{var c,d,u;if((c=s.current)===null||c===void 0||c.cancel(),s.current=void 0,o.current&&r?.current){try{s.current=(u=(d=r.current).animate)===null||u===void 0?void 0:u.call(d,[{transform:o.current},{transform:`scale(${t}) translateX(${e}px) translateY(${n}px)`}],{duration:a?0:i??500,easing:s.current?"ease-out":"ease-in-out"})}catch(f){console.error(f)}o.current=void 0,s.current&&(s.current.onfinish=()=>{s.current=void 0})}});return Cn(l,[t,e,n,l]),F.useCallback(()=>{o.current=r?.current?window.getComputedStyle(r.current).transform:void 0},[r])}function t2(t,e){const{on:n}=At(),r=We(()=>{var s;e||(s=n.zoom)===null||s===void 0||s.call(n,{zoom:t})});F.useEffect(r,[t,r])}function Ui(){const{zoom:t}=At();return Kg(t)}function n2(t,e){var n;const r=typeof t=="function"?(n=t(e))!==null&&n!==void 0?n:Xg.maxZoom:t;return Math.max(r,1)}function r2(t,e){var n,r;let s={width:0,height:0},o={width:0,height:0};const{currentSlide:i}=fn(),{imageFit:a}=At().carousel,{maxZoomPixelRatio:l,maxZoom:c}=Ui();if(t&&i){const u={...i,...e};if(or(u)){const f=hc(u,a),g=Math.max(...(((n=u.srcSet)===null||n===void 0?void 0:n.map(p=>p.width))||[]).concat(u.width?[u.width]:[])),m=Math.max(...(((r=u.srcSet)===null||r===void 0?void 0:r.map(p=>p.height))||[]).concat(u.height?[u.height]:[]));g>0&&m>0&&t.width>0&&t.height>0&&(o=f?{width:Math.round(Math.min(g,t.width/t.height*m)),height:Math.round(Math.min(m,t.height/t.width*g))}:{width:g,height:m},o={width:o.width*l,height:o.height*l},s=f?{width:Math.min(t.width,o.width,g),height:Math.min(t.height,o.height,m)}:{width:Math.round(Math.min(t.width,t.height/m*g,g)),height:Math.round(Math.min(t.height,t.width/g*m,m))})}else t.width>0&&t.height>0&&(e&&e.width>0&&e.height>0?s={width:Math.min(t.width,e.width),height:Math.min(t.height,e.height)}:s={width:t.width,height:t.height})}const d=i&&s.width?or(i)?Math.max(fc(o.width/s.width,5),1):n2(c,i):1;return{imageRect:s,maxZoom:d}}function qg(t,e){return Math.hypot(t.clientX-e.clientX,t.clientY-e.clientY)}function Zg(t,e,n=100,r=2){return t*Math.min(1+Math.abs(e/n),r)**Math.sign(e)}function s2(t,e,n,r,s,o,i,a,l){const c=F.useRef([]),d=F.useRef(0),u=F.useRef(void 0),{globalIndex:f}=fn(),{getOwnerWindow:g}=rs(),{containerRef:m,subscribeSensors:p}=_n(),{keyboardMoveDistance:y,zoomInMultiplier:b,wheelZoomDistanceFactor:_,scrollToZoom:x,doubleTapDelay:E,doubleClickDelay:C,doubleClickMaxStops:k,pinchZoomDistanceFactor:N,pinchZoomV4:S}=Ui(),w=F.useCallback(z=>{if(m.current){const{pageX:H,pageY:j}=z,{scrollX:W,scrollY:U}=g(),{left:B,top:G,width:V,height:K}=m.current.getBoundingClientRect();return[H-B-W-V/2,j-G-U-K/2]}return[]},[m,g]),M=We(z=>{const{key:H,metaKey:j,ctrlKey:W}=z,U=j||W,B=()=>{z.preventDefault(),z.stopPropagation()};if(t>1){const G=(V,K)=>{B(),a(V,K)};H==="ArrowDown"?G(0,y):H==="ArrowUp"?G(0,-y):H==="ArrowLeft"?G(-y,0):H==="ArrowRight"&&G(y,0)}H==="+"||U&&H==="="?(B(),s()):H==="-"||U&&H==="_"?(B(),o()):U&&H==="0"&&(B(),i(1))}),T=We(z=>{if((z.ctrlKey||x)&&Math.abs(z.deltaY)>Math.abs(z.deltaX)){z.stopPropagation(),i(Zg(t,-z.deltaY,_),!0,...w(z));return}t>1&&(z.stopPropagation(),x||a(z.deltaX,z.deltaY))}),P=F.useCallback(z=>{const H=c.current;H.splice(0,H.length,...H.filter(j=>j.pointerId!==z.pointerId))},[]),L=F.useCallback(z=>{P(z),z.persist(),c.current.push(z)},[P]),$=We(z=>{var H;const j=c.current;if(z.pointerType==="mouse"&&z.buttons>1||!(!((H=l?.current)===null||H===void 0)&&H.contains(z.target)))return;t>1&&z.stopPropagation();const{timeStamp:W}=z;if(j.length===0&&W-d.current<(z.pointerType==="touch"?E:C)){d.current=0;const U=t>=1?t!==n?t*Math.max(n**(1/k),b):1:t!==e?t/Math.max(e**(-1/k),b):1;i(U,!1,...w(z))}else d.current=W;if(L(z),j.length===2){const U=qg(j[0],j[1]);u.current={previousDistance:U,initialDistance:Math.max(U,1),initialZoom:t}}}),A=We(z=>{const H=c.current,j=H.find(W=>W.pointerId===z.pointerId);if(H.length===2&&u.current){z.stopPropagation(),L(z);const W=qg(H[0],H[1]),U=S?u.current.initialZoom/u.current.initialDistance*W:Zg(t,W-u.current.previousDistance,N);i(U,!0,...H.map(B=>w(B)).reduce((B,G)=>G.map((V,K)=>B[K]+V/2))),u.current.previousDistance=W;return}t>1&&(z.stopPropagation(),j&&(H.length===1&&a((j.clientX-z.clientX)/t,(j.clientY-z.clientY)/t),L(z)))}),D=F.useCallback(z=>{const H=c.current;H.length===2&&H.find(j=>j.pointerId===z.pointerId)&&(u.current=void 0),P(z)},[P]),O=F.useCallback(()=>{const z=c.current;z.splice(0,z.length),d.current=0,u.current=void 0},[]);Pg(p,$,A,D,r),F.useEffect(O,[f,O]),F.useEffect(()=>r?()=>{}:ts(O,p(cc,M),p(dc,T)),[r,p,O,M,T])}function o2(t,e,n){const[r,s]=F.useState(1),[o,i]=F.useState(0),[a,l]=F.useState(0),c=e2(r,o,a,n),{currentSlide:d,globalIndex:u}=fn(),{containerRect:f,slideRect:g}=_n(),{minZoom:m,zoomInMultiplier:p}=Ui(),y=d&&or(d)?d.src:void 0,b=!n?.current;Cn(()=>{s(1),i(0),l(0)},[u,y]);const _=F.useCallback((N,S,w)=>{const M=w||r,T=o-(N||0),P=a-(S||0),L=(t.width*M-g.width)/2/M,$=(t.height*M-g.height)/2/M;i(Math.min(Math.abs(T),Math.max(L,0))*Math.sign(T)),l(Math.min(Math.abs(P),Math.max($,0))*Math.sign(P))},[r,o,a,g,t.width,t.height]),x=F.useCallback((N,S,w,M)=>{const T=fc(N+.01m?N:m:e,5);S||c(),_(w?w*(1/r-1/T):0,M?M*(1/r-1/T):0,T),s(T)},[r,m,e,_,c]),E=We(()=>{r>1&&(r>e&&x(e,!0),_())});Cn(E,[f.width,f.height,E]);const C=F.useCallback(()=>{const N=r*p;x(r<1&&N>1?1:N)},[r,p,x]),k=F.useCallback(()=>{const N=r/p;x(r>1&&N<1?1:N)},[r,p,x]);return{zoom:r,offsetX:o,offsetY:a,disabled:b,changeOffsets:_,changeZoom:x,zoomIn:C,zoomOut:k}}const Qg=F.createContext(null),Ec=ln("useZoom","ZoomControllerContext",Qg);function i2({children:t}){const[e,n]=F.useState(),{slideRect:r}=_n(),{ref:s,minZoom:o}=Ui(),{imageRect:i,maxZoom:a}=r2(r,e?.imageDimensions),{zoom:l,offsetX:c,offsetY:d,disabled:u,changeZoom:f,changeOffsets:g,zoomIn:m,zoomOut:p}=o2(i,a,e?.zoomWrapperRef);t2(l,u),s2(l,o,a,u,m,p,f,g,e?.zoomWrapperRef);const y=F.useMemo(()=>({zoom:l,minZoom:o,maxZoom:a,offsetX:c,offsetY:d,disabled:u,zoomIn:m,zoomOut:p,changeZoom:f}),[l,o,a,c,d,u,m,p,f]);F.useImperativeHandle(s,()=>y,[y]);const b=F.useMemo(()=>({...y,setZoomWrapper:n}),[y,n]);return F.createElement(Qg.Provider,{value:b},t)}const a2=Jt("ZoomIn",F.createElement(F.Fragment,null,F.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"}),F.createElement("path",{d:"M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"}))),l2=Jt("ZoomOut",F.createElement("path",{d:"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14zM7 9h5v1H7z"})),Jg=F.forwardRef(function({zoomIn:e,onLoseFocus:n},r){const s=F.useRef(!1),o=F.useRef(!1),{zoom:i,minZoom:a,maxZoom:l,zoomIn:c,zoomOut:d,disabled:u}=Ec(),{render:f}=At(),g=u||(e?i>=l:i<=a);return F.useEffect(()=>{g&&s.current&&o.current&&n(),g||(s.current=!0)},[g,n]),F.createElement(os,{ref:r,disabled:g,label:e?"Zoom in":"Zoom out",icon:e?a2:l2,renderIcon:e?f.iconZoomIn:f.iconZoomOut,onClick:e?c:d,onFocus:()=>{o.current=!0},onBlur:()=>{o.current=!1}})});function c2(){const t=F.useRef(null),e=F.useRef(null),{focus:n}=_n(),r=F.useCallback(i=>{var a,l;!((a=i.current)===null||a===void 0)&&a.disabled?n():(l=i.current)===null||l===void 0||l.focus()},[n]),s=F.useCallback(()=>r(t),[r]),o=F.useCallback(()=>r(e),[r]);return F.createElement(F.Fragment,null,F.createElement(Jg,{zoomIn:!0,ref:t,onLoseFocus:o}),F.createElement(Jg,{ref:e,onLoseFocus:s}))}function d2(){const{render:t}=At(),e=Ec();return t.buttonZoom?F.createElement(F.Fragment,null,t.buttonZoom(e)):F.createElement(c2,null)}function u2(t){var e;return(((e=t.srcSet)===null||e===void 0?void 0:e.length)||0)>0}function f2({current:t,preload:e},{type:n,source:r}){switch(n){case"fetch":return t?{current:t,preload:r}:{current:r};case"done":return r===e?{current:r}:{current:t,preload:e};default:throw new Error(gg)}}function h2(t){var e,n;const[{current:r,preload:s},o]=F.useReducer(f2,{}),{slide:i,rect:a,imageFit:l,render:c,interactive:d}=t,u=i.srcSet.sort((C,k)=>C.width-k.width),f=(e=i.width)!==null&&e!==void 0?e:u[u.length-1].width,g=(n=i.height)!==null&&n!==void 0?n:u[u.length-1].height,m=hc(i,l),p=Math.max(...u.map(C=>C.width)),y=Math.min((m?Math.max:Math.min)(a.width,f*(a.height/g)),p),b=a_(),_=We(()=>{var C;const k=(C=u.find(N=>N.width>=y*b))!==null&&C!==void 0?C:u[u.length-1];(!r||u.findIndex(N=>N.src===r)N===k))&&o({type:"fetch",source:k.src})});Cn(_,[a.width,a.height,b,_]);const x=We(C=>o({type:"done",source:C})),E={WebkitTransform:d?"initial":"translateZ(0)"};return m||Object.assign(E,a.width/a.heightx(s),render:{...c,iconLoading:()=>null,iconError:()=>null}}),r&&F.createElement(yr,{key:"current",...t,slide:{...i,src:r,srcSet:void 0},style:E}))}function g2({render:t,slide:e,offset:n,rect:r}){var s;const[o,i]=F.useState(),a=F.useRef(null),l=or(e),{zoom:c,maxZoom:d,offsetX:u,offsetY:f,setZoomWrapper:g}=Ec(),m=c>1,{carousel:p,on:y}=At(),{currentIndex:b}=fn();Cn(()=>{if(n!==0||l||!a.current)return()=>{};const x=()=>{const C=a.current;if(!C)return;let k=0,N=0;for(const S of C.children)S instanceof HTMLElement&&(k=Math.max(k,S.offsetWidth),N=Math.max(N,S.offsetHeight));i(S=>S&&S.width===k&&S.height===N?S:{width:k,height:N})};if(x(),typeof ResizeObserver>"u")return()=>{};const E=new ResizeObserver(x);for(const C of a.current.children)E.observe(C);return()=>E.disconnect()},[n,l,r]),Cn(()=>n===0?(g({zoomWrapperRef:a,imageDimensions:o}),()=>g(void 0)):()=>{},[n,o,g]);let _=(s=t.slide)===null||s===void 0?void 0:s.call(t,{slide:e,offset:n,rect:r,zoom:c,maxZoom:d});if(!_&&l){const x={slide:e,offset:n,rect:r,render:t,imageFit:p.imageFit,imageProps:p.imageProps,onClick:n===0?()=>{var E;return(E=y.click)===null||E===void 0?void 0:E.call(y,{index:b})}:void 0};_=u2(e)?F.createElement(h2,{...x,slide:e,interactive:m,rect:n===0?{width:r.width*c,height:r.height*c}:r}):F.createElement(yr,{onLoad:E=>i({width:E.naturalWidth,height:E.naturalHeight}),...x})}return _?F.createElement("div",{ref:a,className:zt(Ne(sg),Ne(Rr),Ne(ig),m&&Ne(e_)),style:n===0?{transform:`scale(${c}) translateX(${u}px) translateY(${f}px)`}:void 0},_):null}const p2=({augment:t,addModule:e})=>{t(({zoom:n,toolbar:r,render:s,controller:o,...i})=>{const a=Kg(n);return{zoom:a,toolbar:zi(r,rg,F.createElement(d2,null)),render:{...s,slide:l=>{var c,d;return or(l.slide)||l.slide.type!=null&&(!((c=a.supports)===null||c===void 0)&&c.includes(l.slide.type))?F.createElement(g2,{render:s,...l}):(d=s.slide)===null||d===void 0?void 0:d.call(s,l)}},controller:{...o,preventDefaultWheelY:a.scrollToZoom},...i}}),e(un(rg,i2))},m2={separator:"/",container:{}},Rg=t=>({...m2,...t});function b2({counter:t}){const{slides:e,currentIndex:n}=fn(),{separator:r,container:{className:s,...o},className:i,...a}=Rg(t);return e.length===0?null:F.createElement("div",{className:zt(Ne("counter"),s||i),...a,...o,"aria-hidden":!0},n+1," ",r," ",e.length)}function y2({augment:t,addChild:e}){t(({counter:n,...r})=>({counter:Rg(n),...r})),e(eo,un(XS,b2))}const v2={autoplay:!1,delay:3e3,ref:null},ep=t=>({...v2,...t}),tp=F.createContext(null),x2=ln("useSlideshow","SlideshowContext",tp);function w2({slideshow:t,carousel:{finite:e},on:n,children:r}){const{autoplay:s,delay:o,ref:i}=ep(t),a=F.useRef(s),[l,c]=F.useState(s),d=F.useRef(void 0),u=F.useRef(void 0),{slides:f,currentIndex:g}=fn(),{setTimeout:m,clearTimeout:p}=io(),{subscribe:y}=ss(),{next:b}=_n(),{setAutoPlaying:_}=vc();F.useEffect(()=>_(l),[l,_]);const x=f.length===0||e&&g===f.length-1,E=F.useCallback(()=>{!l&&!x&&c(!0)},[l,x]),C=F.useCallback(()=>{l&&c(!1)},[l]),k=F.useCallback(()=>{p(d.current),d.current=void 0},[p]),N=We(()=>{k(),!(!l||x||u.current===Jr||u.current===ic)&&(d.current=m(()=>{l&&(u.current=void 0,b())},o))});F.useEffect(N,[g,l,N]),F.useEffect(()=>{l&&x&&c(!1)},[g,l,x]);const S=We(()=>{var T;return(T=n.slideshowStart)===null||T===void 0?void 0:T.call(n)}),w=We(()=>{var T;return(T=n.slideshowStop)===null||T===void 0?void 0:T.call(n)});F.useEffect(()=>{l?S():a.current&&w(),a.current=l},[l,S,w]),F.useEffect(()=>ts(k,y(qS,()=>{u.current=Jr,k()}),y(ZS,()=>{u.current=ic,k()}),y(QS,()=>{u.current=ro,N()}),y(JS,()=>{u.current=so,N()})),[y,k,N]);const M=F.useMemo(()=>({playing:l,disabled:x,play:E,pause:C}),[l,x,E,C]);return F.useImperativeHandle(i,()=>M,[M]),F.createElement(tp.Provider,{value:M},r)}const k2=Jt("Play",F.createElement("path",{d:"M8 5v14l11-7z"})),C2=Jt("Pause",F.createElement("path",{d:"M6 19h4V5H6v14zm8-14v14h4V5h-4z"}));function S2(){const{playing:t,disabled:e,play:n,pause:r}=x2(),{render:s}=At(),o=Dg(_n().focus,e);return s.buttonSlideshow?F.createElement(F.Fragment,null,s.buttonSlideshow({playing:t,disabled:e,play:n,pause:r})):F.createElement(os,{label:t?"Pause":"Play",icon:t?C2:k2,renderIcon:t?s.iconSlideshowPause:s.iconSlideshowPlay,onClick:t?r:n,disabled:e,...o})}function _2({augment:t,addModule:e}){t(({slideshow:n,toolbar:r,...s})=>({toolbar:zi(r,ng,F.createElement(S2,null)),slideshow:ep(n),...s})),e(un(ng,w2))}const E2={auto:!1,ref:null},np=t=>({...E2,...t}),rp=F.createContext(null),M2=ln("useFullscreen","FullscreenContext",rp);function N2({fullscreen:t,on:e,children:n}){const{auto:r,ref:s}=np(t),o=F.useRef(null),[i,a]=F.useState(),[l,c]=F.useState(!1),d=F.useRef(!1),{getOwnerDocument:u}=rs();Cn(()=>{var x,E,C,k;const N=u();a(!((k=(C=(E=(x=N.fullscreenEnabled)!==null&&x!==void 0?x:N.webkitFullscreenEnabled)!==null&&E!==void 0?E:N.mozFullScreenEnabled)!==null&&C!==void 0?C:N.msFullscreenEnabled)!==null&&k!==void 0&&k))},[u]);const f=F.useCallback(()=>{var x;const E=u(),C=E.fullscreenElement||E.webkitFullscreenElement||E.mozFullScreenElement||E.msFullscreenElement;return((x=C?.shadowRoot)===null||x===void 0?void 0:x.fullscreenElement)||C},[u]),g=F.useCallback(()=>{const x=o.current;try{x.requestFullscreen?x.requestFullscreen().catch(()=>{}):x.webkitRequestFullscreen?x.webkitRequestFullscreen():x.mozRequestFullScreen?x.mozRequestFullScreen():x.msRequestFullscreen&&x.msRequestFullscreen()}catch{}},[]),m=F.useCallback(()=>{if(!f())return;const x=u();try{x.exitFullscreen?x.exitFullscreen().catch(()=>{}):x.webkitExitFullscreen?x.webkitExitFullscreen():x.mozCancelFullScreen?x.mozCancelFullScreen():x.msExitFullscreen&&x.msExitFullscreen()}catch{}},[f,u]);F.useEffect(()=>{const x=u(),E=()=>{c(f()===o.current)};return ts(...["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange"].map(C=>(x.addEventListener(C,E),()=>x.removeEventListener(C,E))))},[f,u]);const p=We(()=>{var x;return(x=e.enterFullscreen)===null||x===void 0?void 0:x.call(e)}),y=We(()=>{var x;return(x=e.exitFullscreen)===null||x===void 0?void 0:x.call(e)});F.useEffect(()=>{l&&(d.current=!0),d.current&&(l?p:y)()},[l,p,y]);const b=We(()=>{var x;return(x=r?g:null)===null||x===void 0||x(),m});F.useEffect(b,[b]);const _=F.useMemo(()=>({fullscreen:l,disabled:i,enter:g,exit:m}),[l,i,g,m]);return F.useImperativeHandle(s,()=>_,[_]),F.createElement("div",{ref:o,className:zt(Ne(to),Ne(sg))},F.createElement(rp.Provider,{value:_},n))}const O2=Jt("EnterFullscreen",F.createElement("path",{d:"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"})),T2=Jt("ExitFullscreen",F.createElement("path",{d:"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"}));function A2(){var t;const{fullscreen:e,disabled:n,enter:r,exit:s}=M2(),{render:o}=At();return n?null:o.buttonFullscreen?F.createElement(F.Fragment,null,(t=o.buttonFullscreen)===null||t===void 0?void 0:t.call(o,{fullscreen:e,disabled:n,enter:r,exit:s})):F.createElement(os,{disabled:n,label:e?"Exit Fullscreen":"Enter Fullscreen",icon:e?T2:O2,renderIcon:e?o.iconExitFullscreen:o.iconEnterFullscreen,onClick:e?s:r})}function D2({augment:t,contains:e,addParent:n}){t(({fullscreen:r,toolbar:s,...o})=>({toolbar:zi(s,to,F.createElement(A2,null)),fullscreen:np(r),...o})),n(e(no)?no:eo,un(to,N2))}const xr={ref:null,position:"bottom",width:120,height:80,border:1,borderRadius:4,padding:4,gap:16,imageFit:"contain",vignette:!0,hidden:!1,showToggle:!1},Mc=t=>({...xr,...t});function sp(){const{thumbnails:t}=At();return Mc(t)}const wr=t=>pr(no,t),Dt=t=>wr(pr("thumbnail",t)),I2=Jt("VideoThumbnail",F.createElement("path",{d:"M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"})),L2=Jt("UnknownThumbnail",F.createElement("path",{d:"M23 18V6c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2zM8.5 12.5l2.5 3.01L14.5 11l4.5 6H5l3.5-4.5z"}));function P2({slide:t,render:e,rect:n,imageFit:r}){var s;const o=(s=e.thumbnail)===null||s===void 0?void 0:s.call(e,{slide:t,render:e,rect:n,imageFit:r});if(o)return o;const i={render:e,rect:n,imageFit:r};if(t.thumbnail)return F.createElement(yr,{slide:{src:t.thumbnail},...i});if(or(t))return F.createElement(yr,{slide:t,...i});const a=Ne(Dt(Pi));return t.type==="video"?F.createElement(F.Fragment,null,t.poster&&F.createElement(yr,{slide:{src:t.poster},...i}),F.createElement(I2,{className:a})):F.createElement(L2,{className:a})}const $2=mr("active"),Nc=mr("fadein"),Oc=mr("fadeout"),F2=mr("placeholder"),op="delay",ip="duration";function z2({slide:t,index:e,onClick:n,fadeIn:r,fadeOut:s,placeholder:o,onLoseFocus:i}){const a=F.useRef(null),{render:l,styles:c,labels:d}=At(),{slides:u,globalIndex:f}=fn(),{getOwnerDocument:g}=rs(),{width:m,height:p,imageFit:y}=sp(),b={width:m,height:p},_=e===f,x=We(i);return F.useEffect(()=>{s&&g().activeElement===a.current&&x()},[s,x,g]),F.createElement("button",{ref:a,type:"button",className:zt(Ne(Rr),Ne(Dt()),_&&Ne(Dt($2())),r&&Ne(Dt(Nc())),s&&Ne(Dt(Oc())),o&&Ne(Dt(F2()))),style:{...r?{[dt(Dt(Nc(ip)))]:`${r.duration}ms`,[dt(Dt(Nc(op)))]:`${r.delay}ms`}:null,...s?{[dt(Dt(Oc(ip)))]:`${s.duration}ms`,[dt(Dt(Oc(op)))]:`${s.delay}ms`}:null,...c.thumbnail},onClick:n,"aria-current":_?!0:void 0,"aria-label":mg(d,u,e)},t&&P2({slide:t,render:l,rect:b,imageFit:y}))}function ap(t){return["top","bottom"].includes(t)}function lp(t,e){return e+2*(t.border+t.padding)+t.gap}function V2(t){const{thumbnail:e,poster:n}=t||{thumbnail:"placeholder"};return typeof e=="string"&&e||typeof n=="string"&&n||t&&bg(t)||void 0}function cp({visible:t,containerRef:e}){const n=F.useRef(null),r=Hi(),{publish:s,subscribe:o}=ss(),{carousel:i,styles:a,labels:l}=At(),{slides:c,globalIndex:d,animation:u}=fn(),{registerSensors:f,subscribeSensors:g}=Ig();Hg(g);const m=sp(),{position:p,width:y,height:b,border:_,borderStyle:x,borderColor:E,borderRadius:C,padding:k,gap:N,vignette:S}=m,w=u?.duration!==void 0&&u?.increment||0,M=u?.duration||0,{prepareAnimation:T}=wc(n,D=>({keyframes:ap(p)?[{transform:`translateX(${(r?-1:1)*lp(m,y)*w+D}px)`},{transform:"translateX(0)"}]:[{transform:`translateY(${lp(m,b)*w+D}px)`},{transform:"translateY(0)"}],duration:M,easing:u?.easing})),P=We(()=>{let D=0;if(e.current&&n.current){const O=e.current.getBoundingClientRect(),z=n.current.getBoundingClientRect();D=ap(p)?z.left-O.left-(O.width-z.width)/2:z.top-O.top-(O.height-z.height)/2}T(D)});F.useEffect(()=>ts(o(lc,P)),[o,P]);const L=yg(i,c),$=[];if(pc(c))for(let D=d-L-Math.abs(w);D<=d+L+Math.abs(w);D+=1){const z=i.finite&&(D<0||D>c.length-1)||w<0&&D0&&D>d+L?null:mc(c,D),H=[`${D}`,V2(z)].filter(Boolean).join("|");$.push({key:H,index:D,slide:z})}const A=D=>()=>{D>d?s(sr,{count:D-d}):D{const H=M/Math.abs(w||1),j=w>0&&O>d+L-w&&O<=d+L||w<0&&O=d-L?{duration:H,delay:((w>0?O-(d+L-w):d-L-w-O)-1)*H}:void 0,W=w>0&&Od+L?{duration:H,delay:(w>0?w-(d-L-O):-w-(O-(d+L)))*H}:void 0;return F.createElement(z2,{key:D,index:O,slide:z,fadeIn:j,fadeOut:W,placeholder:!z,onClick:A(O),onLoseFocus:()=>{var U;return(U=n.current)===null||U===void 0?void 0:U.focus()}})})),S&&F.createElement("div",{className:Ne(wr("vignette"))}))}const dp=F.createContext(null),H2=ln("useThumbnails","ThumbnailsContext",dp);function U2({children:t,...e}){const{ref:n,position:r,hidden:s}=Mc(e.thumbnails),[o,i]=F.useState(!s),a=F.useRef(null),l=F.useMemo(()=>({visible:o,show:()=>i(!0),hide:()=>i(!1)}),[o]);return F.useImperativeHandle(n,()=>l,[l]),F.createElement(Sg,{...e},F.createElement(dp.Provider,{value:l},F.createElement("div",{ref:a,className:zt(Ne(wr()),Ne(wr(`${r}`)))},["start","top"].includes(r)&&F.createElement(cp,{containerRef:a,visible:o}),F.createElement("div",{className:Ne(wr("wrapper"))},t),["end","bottom"].includes(r)&&F.createElement(cp,{containerRef:a,visible:o}))))}const up=()=>F.createElement(F.Fragment,null,F.createElement("path",{strokeWidth:2,stroke:"currentColor",strokeLinejoin:"round",fill:"none",d:"M3 5l18 0l0 14l-18 0l0-14z"}),F.createElement("path",{d:"M5 14h4v3h-4zM10 14h4v3h-4zM15 14h4v3h-4z"})),B2=Jt("ThumbnailsVisible",up()),j2=w_("ThumbnailsHidden",up());function W2(){const{visible:t,show:e,hide:n}=H2(),{render:r}=At();return r.buttonThumbnails?F.createElement(F.Fragment,null,r.buttonThumbnails({visible:t,show:e,hide:n})):F.createElement(os,{label:t?"Hide thumbnails":"Show thumbnails",icon:t?B2:j2,renderIcon:t?r.iconThumbnailsVisible:r.iconThumbnailsHidden,onClick:t?n:e})}function G2({augment:t,contains:e,append:n,addParent:r}){t(({thumbnails:o,toolbar:i,...a})=>{const l=Mc(o);return{toolbar:zi(i,no,l.showToggle?F.createElement(W2,null):null),thumbnails:l,...a}});const s=un(no,U2);e(to)?n(to,s):r(eo,s)}const Y2=window.G7Core?.createLogger?.("Comp:ImageGallery")??{log:(...t)=>console.log("[Comp:ImageGallery]",...t),warn:(...t)=>console.warn("[Comp:ImageGallery]",...t),error:(...t)=>console.error("[Comp:ImageGallery]",...t)},Tc=window.G7Core,Bi=(t,e)=>Tc?.t?.(t,e)??t,X2=async(t,e)=>{try{const n=await Tc.api.get(t,{responseType:"blob"});if(n){const r=URL.createObjectURL(n),s=document.createElement("a");s.href=r,s.download=e,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(r)}}catch(n){Y2.error("Failed to download file:",n),Tc?.toast?.error?.(Bi("common.download_failed"))}},K2=(t,e)=>{const n=document.createElement("a");n.href=t,n.download=e,n.target="_blank",document.body.appendChild(n),n.click(),document.body.removeChild(n)},Ac=async t=>{const e=t.downloadUrl||t.src,n=t.filename||t.title||"image";t.downloadRequiresAuth?await X2(e,n):K2(e,n)},q2=({image:t,index:e,onDownload:n})=>{const[r,s]=v.useState(!1),o=async i=>{if(i.stopPropagation(),n){n(t,e);return}s(!0);try{await Ac(t)}finally{s(!1)}};return h.jsx(oe,{type:"button",onClick:o,disabled:r,className:"yarl__button flex items-center justify-center","aria-label":Bi("common.download"),title:Bi("common.download"),children:r?h.jsx(mt,{className:"fa-solid fa-spinner fa-spin text-white"}):h.jsx(mt,{className:"fa-solid fa-download text-white"})})},ji=({images:t,isOpen:e,onClose:n,startIndex:r=0,enableZoom:s=!0,enableSlideshow:o=!1,enableFullscreen:i=!0,showCounter:a=!0,showDownload:l=!0,showThumbnails:c=!0,onDownload:d,editorAttrs:u,id:f})=>{const[g,m]=v.useState(r),p=v.useRef(r),y=t.map(x=>({src:x.src,title:x.title,description:x.description})),b=[];s&&b.push(p2),o&&b.push(_2),i&&b.push(D2),a&&b.push(y2),c&&b.push(G2);const _=t[g];if(v.useEffect(()=>{p.current=g},[g]),u){const x=(t??[]).slice(0,4);return h.jsx(I,{id:f,className:"grid grid-cols-2 gap-2 p-3 rounded-lg border border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800",...u,children:x.length>0?x.map((E,C)=>h.jsx(yn,{src:E.thumbnail||E.src,alt:E.title||"",className:"w-full aspect-square object-cover rounded bg-gray-200 dark:bg-gray-700"},C)):h.jsxs(I,{className:"col-span-2 flex items-center justify-center py-6 text-sm text-gray-400 dark:text-gray-500",children:[h.jsx(mt,{className:"fa-regular fa-images mr-2"}),Bi("editor.component.image_gallery")]})})}return h.jsx(J_,{open:e,close:n,slides:y,index:g,plugins:b,on:{view:({index:x})=>{x!==p.current&&m(x)}},zoom:{maxZoomPixelRatio:3,zoomInMultiplier:2,doubleTapDelay:300,doubleClickDelay:300,doubleClickMaxStops:2,keyboardMoveDistance:50,wheelZoomDistanceFactor:100,pinchZoomDistanceFactor:100,scrollToZoom:!0},carousel:{finite:!0,preload:2,padding:"16px",spacing:"30%"},animation:{fade:250,swipe:500,easing:{fade:"ease",swipe:"ease-out",navigation:"ease-in-out"}},controller:{closeOnBackdropClick:!0,closeOnPullDown:!0,closeOnPullUp:!0},thumbnails:{position:"bottom",width:120,height:80,border:2,borderRadius:4,padding:4,gap:16,showToggle:!1,vignette:!0},toolbar:{buttons:[l&&_&&h.jsx(q2,{image:_,index:g,onDownload:d},"download"),"close"].filter(Boolean)},styles:{container:{backgroundColor:"rgba(0, 0, 0, 0.9)"}}})},fp=()=>{const[t,e]=v.useState(!1),[n,r]=v.useState([]),[s,o]=v.useState(0),i=v.useCallback((l,c=0)=>{r(l),o(c),e(!0)},[]),a=v.useCallback(()=>{e(!1)},[]);return{isOpen:t,openGallery:i,closeGallery:a,galleryProps:{images:n,isOpen:t,onClose:a,startIndex:s}}};ji.displayName="ImageGallery";const Z2=Object.freeze(Object.defineProperty({__proto__:null,ImageGallery:ji,executeImageDownload:Ac,useImageGallery:fp},Symbol.toStringTag,{value:"Module"})),Q2=v.createContext(null),J2=({value:t,children:e})=>h.jsx(Q2.Provider,{value:t,children:e});function R2(t,e){return e.forEach((function(n){n&&typeof n!="string"&&!Array.isArray(n)&&Object.keys(n).forEach((function(r){if(r!=="default"&&!(r in t)){var s=Object.getOwnPropertyDescriptor(n,r);Object.defineProperty(t,r,s.get?s:{enumerable:!0,get:function(){return n[r]}})}}))})),Object.freeze(t)}function hp(t,e){return new Promise((function(n,r){let s;return eE(t).then((function(o){try{return s=o,n(new Blob([e.slice(0,2),s,e.slice(2)],{type:"image/jpeg"}))}catch(i){return r(i)}}),r)}))}const eE=t=>new Promise(((e,n)=>{const r=new FileReader;r.addEventListener("load",(({target:{result:s}})=>{const o=new DataView(s);let i=0;if(o.getUint16(i)!==65496)return n("not a valid JPEG");for(i+=2;;){const a=o.getUint16(i);if(a===65498)break;const l=o.getUint16(i+2);if(a===65505&&o.getUint32(i+4)===1165519206){const c=i+10;let d;switch(o.getUint16(c)){case 18761:d=!0;break;case 19789:d=!1;break;default:return n("TIFF header contains invalid endian")}if(o.getUint16(c+2,d)!==42)return n("TIFF header contains invalid version");const u=o.getUint32(c+4,d),f=c+u+2+12*o.getUint16(c+u,d);for(let g=c+u+2;g>>24&255,a[i+1]=l>>>16&255,a[i+2]=l>>>8&255,a[i+3]=l>>>0&255,new Uint8Array(a.buffer,0,i+4)},r.deflateRaw=function(s,o){o==null&&(o={level:6});var i=new Uint8Array(50+Math.floor(1.1*s.length)),a=r.F.deflateRaw(s,i,a,o.level);return new Uint8Array(i.buffer,0,a)},r.encode=function(s,o){o==null&&(o=!1);var i=0,a=r.bin.writeUint,l=r.bin.writeUshort,c={};for(var d in s){var u=!r._noNeed(d)&&!o,f=s[d],g=r.crc.crc(f,0,f.length);c[d]={cpr:u,usize:f.length,crc:g,file:u?r.deflateRaw(f):f}}for(var d in c)i+=c[d].file.length+30+46+2*r.bin.sizeUTF8(d);i+=22;var m=new Uint8Array(i),p=0,y=[];for(var d in c){var b=c[d];y.push(p),p=r._writeHeader(m,p,d,b,0)}var _=0,x=p;for(var d in c)b=c[d],y.push(p),p=r._writeHeader(m,p,d,b,1,y[_++]);var E=p-x;return a(m,p,101010256),p+=4,l(m,p+=4,_),l(m,p+=2,_),a(m,p+=2,E),a(m,p+=4,x),p+=4,p+=2,m.buffer},r._noNeed=function(s){var o=s.split(".").pop().toLowerCase();return"png,jpg,jpeg,zip".indexOf(o)!=-1},r._writeHeader=function(s,o,i,a,l,c){var d=r.bin.writeUint,u=r.bin.writeUshort,f=a.file;return d(s,o,l==0?67324752:33639248),o+=4,l==1&&(o+=2),u(s,o,20),u(s,o+=2,0),u(s,o+=2,a.cpr?8:0),d(s,o+=2,0),d(s,o+=4,a.crc),d(s,o+=4,f.length),d(s,o+=4,a.usize),u(s,o+=4,r.bin.sizeUTF8(i)),u(s,o+=2,0),o+=2,l==1&&(o+=2,o+=2,d(s,o+=6,c),o+=4),o+=r.bin.writeUTF8(s,o,i),l==0&&(s.set(f,o),o+=f.length),o},r.crc={table:(function(){for(var s=new Uint32Array(256),o=0;o<256;o++){for(var i=o,a=0;a<8;a++)1&i?i=3988292384^i>>>1:i>>>=1;s[o]=i}return s})(),update:function(s,o,i,a){for(var l=0;l>>8;return s},crc:function(s,o,i){return 4294967295^r.crc.update(4294967295,s,o,i)}},r.adler=function(s,o,i){for(var a=1,l=0,c=o,d=o+i;c>8&255},readUint:function(s,o){return 16777216*s[o+3]+(s[o+2]<<16|s[o+1]<<8|s[o])},writeUint:function(s,o,i){s[o]=255&i,s[o+1]=i>>8&255,s[o+2]=i>>16&255,s[o+3]=i>>24&255},readASCII:function(s,o,i){for(var a="",l=0;l>6,s[o+l+1]=128|d>>0&63,l+=2;else if((4294901760&d)==0)s[o+l]=224|d>>12,s[o+l+1]=128|d>>6&63,s[o+l+2]=128|d>>0&63,l+=3;else{if((4292870144&d)!=0)throw"e";s[o+l]=240|d>>18,s[o+l+1]=128|d>>12&63,s[o+l+2]=128|d>>6&63,s[o+l+3]=128|d>>0&63,l+=4}}return l},sizeUTF8:function(s){for(var o=s.length,i=0,a=0;a>>3}var y=c.lits,b=c.strt,_=c.prev,x=0,E=0,C=0,k=0,N=0,S=0;for(p>2&&(b[S=r.F._hash(s,0)]=0),f=0;f14e3||E>26697)&&p-f>100&&(m>>16,P=65535&M;if(M!=0){P=65535&M;var L=d(T=M>>>16,c.of0);c.lhst[257+L]++;var $=d(P,c.df0);c.dhst[$]++,k+=c.exb[L]+c.dxb[$],y[x]=T<<23|f-m,y[x+1]=P<<16|L<<8|$,x+=2,m=f+T}else c.lhst[s[f]]++;E++}}for(C==f&&s.length!=0||(m>>3},r.F._bestMatch=function(s,o,i,a,l,c){var d=32767&o,u=i[d],f=d-u+32768&32767;if(u==d||a!=r.F._hash(s,o-f))return 0;for(var g=0,m=0,p=Math.min(32767,o);f<=p&&--c!=0&&u!=d;){if(g==0||s[o+g]==s[o+g-f]){var y=r.F._howLong(s,o,f);if(y>g){if(m=f,(g=y)>=l)break;f+2b&&(b=E,u=x)}}}f+=(d=u)-(u=i[d])+32768&32767}return g<<16|m},r.F._howLong=function(s,o,i){if(s[o]!=s[o-i]||s[o+1]!=s[o+1-i]||s[o+2]!=s[o+2-i])return 0;var a=o,l=Math.min(s.length,o+258);for(o+=3;o>>23,W=O+(8388607&H);O>16,G=U>>8&255,V=255&U;S(u,f=r.F._writeLit(257+G,$,u,f),j-k.of0[G]),f+=k.exb[G],N(u,f=r.F._writeLit(V,A,u,f),B-k.df0[V]),f+=k.dxb[V],O+=j}}f=r.F._writeLit(256,$,u,f)}return f},r.F._copyExact=function(s,o,i,a,l){var c=l>>>3;return a[c]=i,a[c+1]=i>>>8,a[c+2]=255-a[c],a[c+3]=255-a[c+1],c+=4,a.set(new Uint8Array(s.buffer,o,i),c),l+(i+4<<3)},r.F.getTrees=function(){for(var s=r.F.U,o=r.F._hufTree(s.lhst,s.ltree,15),i=r.F._hufTree(s.dhst,s.dtree,15),a=[],l=r.F._lenCodes(s.ltree,a),c=[],d=r.F._lenCodes(s.dtree,c),u=0;u4&&s.itree[1+(s.ordr[g-1]<<1)]==0;)g--;return[o,i,f,l,d,g,a,c]},r.F.getSecond=function(s){for(var o=[],i=0;i>1)+",");return o},r.F.contSize=function(s,o){for(var i=0,a=0;a15&&(r.F._putsE(i,a,d,u),a+=u)}return a},r.F._lenCodes=function(s,o){for(var i=s.length;i!=2&&s[i-1]==0;)i-=2;for(var a=0;a>>1,138))<11?o.push(17,g-3):o.push(18,g-11),a+=2*g-2}else if(l==u&&c==l&&d==l){for(f=a+5;f+2>>1,6);o.push(16,g-3),a+=2*g-2}else o.push(l,0)}return i>>>1},r.F._hufTree=function(s,o,i){var a=[],l=s.length,c=o.length,d=0;for(d=0;di&&(r.F.restrictDepth(f,i,x),x=i),d=0;do;a++){var d=s[a].d;s[a].d=o,c+=l-(1<>>=i-o;c>0;)(d=s[a].d)=0;a--)s[a].d==o&&c<0&&(s[a].d--,c++);c!=0&&console.log("debt left")},r.F._goodIndex=function(s,o){var i=0;return o[16|i]<=s&&(i|=16),o[8|i]<=s&&(i|=8),o[4|i]<=s&&(i|=4),o[2|i]<=s&&(i|=2),o[1|i]<=s&&(i|=1),i},r.F._writeLit=function(s,o,i,a){return r.F._putsF(i,a,o[s<<1]),a+o[1+(s<<1)]},r.F.inflate=function(s,o){var i=Uint8Array;if(s[0]==3&&s[1]==0)return o||new i(0);var a=r.F,l=a._bitsF,c=a._bitsE,d=a._decodeTiny,u=a.makeCodes,f=a.codes2map,g=a._get17,m=a.U,p=o==null;p&&(o=new i(s.length>>>2<<3));for(var y,b,_=0,x=0,E=0,C=0,k=0,N=0,S=0,w=0,M=0;_==0;)if(_=l(s,M,1),x=l(s,M+1,2),M+=3,x!=0){if(p&&(o=r.F._check(o,w+131072)),x==1&&(y=m.flmap,b=m.fdmap,N=511,S=31),x==2){E=c(s,M,5)+257,C=c(s,M+5,5)+1,k=c(s,M+10,4)+4,M+=14;for(var T=0;T<38;T+=2)m.itree[T]=0,m.itree[T+1]=0;var P=1;for(T=0;TP&&(P=L)}M+=3*k,u(m.itree,P),f(m.itree,P,m.imap),y=m.lmap,b=m.dmap,M=d(m.imap,(1<>>4;if(!(O>>>8))o[w++]=O;else{if(O==256)break;var z=w+O-254;if(O>264){var H=m.ldef[O-257];z=w+(H>>>3)+c(s,M,7&H),M+=7&H}var j=b[g(s,M)&S];M+=15&j;var W=j>>>4,U=m.ddef[W],B=(U>>>4)+l(s,M,15&U);for(M+=15&U,p&&(o=r.F._check(o,w+131072));w>>3),V=s[G-4]|s[G-3]<<8;p&&(o=r.F._check(o,w+V)),o.set(new i(s.buffer,s.byteOffset+G,V),w),M=G+V<<3,w+=V}return o.length==w?o:o.slice(0,w)},r.F._check=function(s,o){var i=s.length;if(o<=i)return s;var a=new Uint8Array(Math.max(i<<1,o));return a.set(s,0),a},r.F._decodeTiny=function(s,o,i,a,l,c){for(var d=r.F._bitsE,u=r.F._get17,f=0;f>>4;if(m<=15)c[f]=m,f++;else{var p=0,y=0;m==16?(y=3+d(a,l,2),l+=2,p=c[f-1]):m==17?(y=3+d(a,l,3),l+=3):m==18&&(y=11+d(a,l,7),l+=7);for(var b=f+y;f>>1;cl&&(l=u),c++}for(;c>1,u=s[c+1],f=d<<4|u,g=o-u,m=s[c]<>>15-o]=f,m++},r.F.revCodes=function(s,o){for(var i=r.F.U.rev15,a=15-o,l=0;l>>a}},r.F._putsE=function(s,o,i){i<<=7&o;var a=o>>>3;s[a]|=i,s[a+1]|=i>>>8},r.F._putsF=function(s,o,i){i<<=7&o;var a=o>>>3;s[a]|=i,s[a+1]|=i>>>8,s[a+2]|=i>>>16},r.F._bitsE=function(s,o,i){return(s[o>>>3]|s[1+(o>>>3)]<<8)>>>(7&o)&(1<>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16)>>>(7&o)&(1<>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16)>>>(7&o)},r.F._get25=function(s,o){return(s[o>>>3]|s[1+(o>>>3)]<<8|s[2+(o>>>3)]<<16|s[3+(o>>>3)]<<24)>>>(7&o)},r.F.U=(e=Uint16Array,n=Uint32Array,{next_code:new e(16),bl_count:new e(16),ordr:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],of0:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],exb:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],ldef:new e(32),df0:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],dxb:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],ddef:new n(32),flmap:new e(512),fltree:[],fdmap:new e(32),fdtree:[],lmap:new e(32768),ltree:[],ttree:[],dmap:new e(32768),dtree:[],imap:new e(512),itree:[],rev15:new e(32768),lhst:new n(286),dhst:new n(30),ihst:new n(19),lits:new n(15e3),strt:new e(65536),prev:new e(32768)}),(function(){for(var s=r.F.U,o=0;o<32768;o++){var i=o;i=(4278255360&(i=(4042322160&(i=(3435973836&(i=(2863311530&i)>>>1|(1431655765&i)<<1))>>>2|(858993459&i)<<2))>>>4|(252645135&i)<<4))>>>8|(16711935&i)<<8,s.rev15[o]=(i>>>16|i<<16)>>>17}function a(l,c,d){for(;c--!=0;)l.push(0,d)}for(o=0;o<32;o++)s.ldef[o]=s.of0[o]<<3|s.exb[o],s.ddef[o]=s.df0[o]<<4|s.dxb[o];a(s.fltree,144,8),a(s.fltree,112,9),a(s.fltree,24,7),a(s.fltree,8,8),r.F.makeCodes(s.fltree,9),r.F.codes2map(s.fltree,9,s.flmap),r.F.revCodes(s.fltree,9),a(s.fdtree,32,5),r.F.makeCodes(s.fdtree,5),r.F.codes2map(s.fdtree,5,s.fdmap),r.F.revCodes(s.fdtree,5),a(s.itree,19,0),a(s.ltree,286,0),a(s.dtree,30,0),a(s.ttree,320,0)})()})();var nE=R2({__proto__:null,default:Wi},[Wi]);const En=(function(){var t={nextZero(d,u){for(;d[u]!=0;)u++;return u},readUshort:(d,u)=>d[u]<<8|d[u+1],writeUshort(d,u,f){d[u]=f>>8&255,d[u+1]=255&f},readUint:(d,u)=>16777216*d[u]+(d[u+1]<<16|d[u+2]<<8|d[u+3]),writeUint(d,u,f){d[u]=f>>24&255,d[u+1]=f>>16&255,d[u+2]=f>>8&255,d[u+3]=255&f},readASCII(d,u,f){let g="";for(let m=0;md.length<2?`0${d}`:d,readUTF8(d,u,f){let g,m="";for(let p=0;p>3)]>>7-((7&k)<<0)&1);b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>2)]>>6-((3&k)<<1)&3),b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>1)]>>4-((1&k)<<2)&15),b[w]=H[L],b[w+1]=H[L+1],b[w+2]=H[L+2],b[w+3]=$>>3)]>>>7-(7&O)&1))==255*S?0:255;_[j+O]=z<<24|A<<16|A<<8|A}else if(E==2)for(O=0;O>>2)]>>>6-((3&O)<<1)&3))==85*S?0:255,_[j+O]=z<<24|A<<16|A<<8|A;else if(E==4)for(O=0;O>>1)]>>>4-((1&O)<<2)&15))==17*S?0:255,_[j+O]=z<<24|A<<16|A<<8|A;else if(E==8)for(O=0;O>3,S=Math.ceil(E*k/8),w=new Uint8Array(C*S);let M=0;const T=[0,0,4,0,2,0,1],P=[0,4,0,2,0,1,0],L=[8,8,8,4,4,2,2],$=[8,8,4,4,2,2,1];let A=0;for(;A<7;){const O=L[A],z=$[A];let H=0,j=0,W=T[A];for(;W>3])>>7-(7&X)&1,w[V*S+(K>>3)]|=D<<7-((7&K)<<0)),k==2&&(D=(D=_[X>>3])>>6-(7&X)&3,w[V*S+(K>>2)]|=D<<6-((3&K)<<1)),k==4&&(D=(D=_[X>>3])>>4-(7&X)&15,w[V*S+(K>>1)]|=D<<4-((1&K)<<2)),k>=8){const Z=V*S+K*N;for(let ee=0;ee>3)+ee]}X+=k,K+=z}G++,V+=O}H*j!=0&&(M+=j*(1+B)),A+=1}return w})(u,d)),u}function r(d,u){return s(new Uint8Array(d.buffer,2,d.length-6),u)}var s=(function(){const d={H:{}};return d.H.N=function(u,f){const g=Uint8Array;let m,p,y=0,b=0,_=0,x=0,E=0,C=0,k=0,N=0,S=0;if(u[0]==3&&u[1]==0)return f||new g(0);const w=d.H,M=w.b,T=w.e,P=w.R,L=w.n,$=w.A,A=w.Z,D=w.m,O=f==null;for(O&&(f=new g(u.length>>>2<<5));y==0;)if(y=M(u,S,1),b=M(u,S+1,2),S+=3,b!=0){if(O&&(f=d.H.W(f,N+(1<<17))),b==1&&(m=D.J,p=D.h,C=511,k=31),b==2){_=T(u,S,5)+257,x=T(u,S+5,5)+1,E=T(u,S+10,4)+4,S+=14;let H=1;for(var z=0;z<38;z+=2)D.Q[z]=0,D.Q[z+1]=0;for(z=0;zH&&(H=U)}S+=3*E,L(D.Q,H),$(D.Q,H,D.u),m=D.w,p=D.d,S=P(D.u,(1<>>4;if(!(j>>>8))f[N++]=j;else{if(j==256)break;{let W=N+j-254;if(j>264){const K=D.q[j-257];W=N+(K>>>3)+T(u,S,7&K),S+=7&K}const U=p[A(u,S)&k];S+=15&U;const B=U>>>4,G=D.c[B],V=(G>>>4)+M(u,S,15&G);for(S+=15&G;N>>3),j=u[H-4]|u[H-3]<<8;O&&(f=d.H.W(f,N+j)),f.set(new g(u.buffer,u.byteOffset+H,j),N),S=H+j<<3,N+=j}return f.length==N?f:f.slice(0,N)},d.H.W=function(u,f){const g=u.length;if(f<=g)return u;const m=new Uint8Array(g<<1);return m.set(u,0),m},d.H.R=function(u,f,g,m,p,y){const b=d.H.e,_=d.H.Z;let x=0;for(;x>>4;if(C<=15)y[x]=C,x++;else{let k=0,N=0;C==16?(N=3+b(m,p,2),p+=2,k=y[x-1]):C==17?(N=3+b(m,p,3),p+=3):C==18&&(N=11+b(m,p,7),p+=7);const S=x+N;for(;x>>1;for(;yp&&(p=_),y++}for(;y>1,_=u[y+1],x=b<<4|_,E=f-_;let C=u[y]<>>15-f]=x,C++}},d.H.l=function(u,f){const g=d.H.m.r,m=15-f;for(let p=0;p>>m}},d.H.M=function(u,f,g){g<<=7&f;const m=f>>>3;u[m]|=g,u[m+1]|=g>>>8},d.H.I=function(u,f,g){g<<=7&f;const m=f>>>3;u[m]|=g,u[m+1]|=g>>>8,u[m+2]|=g>>>16},d.H.e=function(u,f,g){return(u[f>>>3]|u[1+(f>>>3)]<<8)>>>(7&f)&(1<>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16)>>>(7&f)&(1<>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16)>>>(7&f)},d.H.i=function(u,f){return(u[f>>>3]|u[1+(f>>>3)]<<8|u[2+(f>>>3)]<<16|u[3+(f>>>3)]<<24)>>>(7&f)},d.H.m=(function(){const u=Uint16Array,f=Uint32Array;return{K:new u(16),j:new u(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new u(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new f(32),J:new u(512),_:[],h:new u(32),$:[],w:new u(32768),C:[],v:[],d:new u(32768),D:[],u:new u(512),Q:[],r:new u(32768),s:new f(286),Y:new f(30),a:new f(19),t:new f(15e3),k:new u(65536),g:new u(32768)}})(),(function(){const u=d.H.m;for(var f=0;f<32768;f++){let m=f;m=(2863311530&m)>>>1|(1431655765&m)<<1,m=(3435973836&m)>>>2|(858993459&m)<<2,m=(4042322160&m)>>>4|(252645135&m)<<4,m=(4278255360&m)>>>8|(16711935&m)<<8,u.r[f]=(m>>>16|m<<16)>>>17}function g(m,p,y){for(;p--!=0;)m.push(0,y)}for(f=0;f<32;f++)u.q[f]=u.S[f]<<3|u.T[f],u.c[f]=u.p[f]<<4|u.z[f];g(u._,144,8),g(u._,112,9),g(u._,24,7),g(u._,8,8),d.H.n(u._,9),d.H.A(u._,9,u.J),d.H.l(u._,9),g(u.$,32,5),d.H.n(u.$,5),d.H.A(u.$,5,u.h),d.H.l(u.$,5),g(u.Q,19,0),g(u.C,286,0),g(u.D,30,0),g(u.v,320,0)})(),d.H.N})();function o(d){return[1,null,3,1,2,null,4][d.ctype]*d.depth}function i(d,u,f,g,m){let p=o(u);const y=Math.ceil(g*p/8);let b,_;p=Math.ceil(p/8);let x=d[f],E=0;if(x>1&&(d[f]=[0,0,1][x-2]),x==3)for(E=p;E>>1)&255;for(let C=0;C>>1);for(;E>>1)}else{for(;E=0&&b>=0?(C=A*u+D<<2,k=(b+A)*m+y+D<<2):(C=(-b+A)*u-y+D<<2,k=A*m+D<<2),_==0)g[k]=d[C],g[k+1]=d[C+1],g[k+2]=d[C+2],g[k+3]=d[C+3];else if(_==1){var N=d[C+3]*.00392156862745098,S=d[C]*N,w=d[C+1]*N,M=d[C+2]*N,T=g[k+3]*(1/255),P=g[k]*T,L=g[k+1]*T,$=g[k+2]*T;const O=1-N,z=N+T*O,H=z==0?0:1/z;g[k+3]=255*z,g[k+0]=(S+P*O)*H,g[k+1]=(w+L*O)*H,g[k+2]=(M+$*O)*H}else if(_==2)N=d[C+3],S=d[C],w=d[C+1],M=d[C+2],T=g[k+3],P=g[k],L=g[k+1],$=g[k+2],N==T&&S==P&&w==L&&M==$?(g[k]=0,g[k+1]=0,g[k+2]=0,g[k+3]=0):(g[k]=S,g[k+1]=w,g[k+2]=M,g[k+3]=N);else if(_==3){if(N=d[C+3],S=d[C],w=d[C+1],M=d[C+2],T=g[k+3],P=g[k],L=g[k+1],$=g[k+2],N==T&&S==P&&w==L&&M==$)continue;if(N<220&&T>20)return!1}return!0}return{decode:function(u){const f=new Uint8Array(u);let g=8;const m=t,p=m.readUshort,y=m.readUint,b={tabs:{},frames:[]},_=new Uint8Array(f.length);let x,E=0,C=0;const k=[137,80,78,71,13,10,26,10];for(var N=0;N<8;N++)if(f[N]!=k[N])throw"The input is not a PNG file!";for(;g>>1:M>>>=1;S[w]=M}return S})(),update(S,w,M,T){for(let P=0;P>>8;return S},crc:(S,w,M)=>4294967295^r.update(4294967295,S,w,M)};function s(S,w,M,T){w[M]+=S[0]*T>>4,w[M+1]+=S[1]*T>>4,w[M+2]+=S[2]*T>>4,w[M+3]+=S[3]*T>>4}function o(S){return Math.max(0,Math.min(255,S))}function i(S,w){const M=S[0]-w[0],T=S[1]-w[1],P=S[2]-w[2],L=S[3]-w[3];return M*M+T*T+P*P+L*L}function a(S,w,M,T,P,L,$){$==null&&($=1);const A=T.length,D=[];for(var O=0;O>>0&255,V>>>8&255,V>>>16&255,V>>>24&255])}for(O=0;O>2]=z,W[O>>2]=T[z]}}function l(S,w,M,T,P){P==null&&(P={});const{crc:L}=r,$=e.writeUint,A=e.writeUshort,D=e.writeASCII;let O=8;const z=S.frames.length>1;let H,j=!1,W=33+(z?20:0);if(P.sRGB!=null&&(W+=13),P.pHYs!=null&&(W+=21),P.iCCP!=null&&(H=pako.deflate(P.iCCP),W+=21+H.length+4),S.ctype==3){for(var U=S.plte.length,B=0;B>>24!=255&&(j=!0);W+=8+3*U+4+(j?8+1*U+4:0)}for(var G=0;G>>8&255,le=ne>>>16&255;V[O+ee+0]=he,V[O+ee+1]=J,V[O+ee+2]=le}if(O+=3*U,$(V,O,L(V,O-3*U-4,3*U+4)),O+=4,j){for($(V,O,U),O+=4,D(V,O,"tRNS"),O+=4,B=0;B>>24&255;O+=U,$(V,O,L(V,O-U-4,U+4)),O+=4}}let X=0;for(G=0;GVe&&(Ve=pt),itDe&&(De=it));Ve==-1&&(Pe=Te=Ve=De=0),se&&((1&Pe)==1&&Pe--,(1&Te)==1&&Te--);const qe=(Ve-Pe+1)*(De-Te+1);qe>2,He>>2);ee.push(ne);const se=new Uint8Array(ye.abuf,we,He);z&&a(he.img,he.rect.width,he.rect.height,Z,se,ne),he.img.set(se),we+=He}}else for(U=0;UJ&&fe==ve[G-J])ne[G]=ne[G-J];else{let ye=X[fe];if(ye==null&&(X[fe]=ye=Z.length,Z.push(fe),Z.length>=300))break;ne[G]=ye}}}const le=Z.length;for(le<=256&&O==0&&(j=le<=2?1:le<=4?2:le<=16?4:8,j=Math.max(j,D)),U=0;U>1)]|=He[Oe+ae]<<4-4*(1&ae);else if(j==2)for(ae=0;ae>2)]|=He[Oe+ae]<<6-2*(3&ae);else if(j==1)for(ae=0;ae>3)]|=He[Oe+ae]<<7-1*(7&ae)}fe=ue,H=3,we=1}else if(V==0&&K.length==1){ue=new Uint8Array(J*ve*3);const He=J*ve;for(G=0;GG&&(G=ee),neV&&(V=ne))}G==-1&&(U=B=G=V=0),$&&((1&U)==1&&U--,(1&B)==1&&B--),L={x:U,y:B,width:G-U+1,height:V-B+1};const K=T[P];K.rect=L,K.blend=1,K.img=new Uint8Array(L.width*L.height*4),T[P-1].dispose==0?(t(O,w,M,K.img,L.width,L.height,-L.x,-L.y,0),f(j,w,M,K.img,L)):t(j,w,M,K.img,L.width,L.height,-L.x,-L.y,0)}function f(S,w,M,T,P){t(S,w,M,T,P.width,P.height,-P.x,-P.y,2)}function g(S,w,M,T,P,L,$){const A=[];let D,O=[0,1,2,3,4];L!=-1?O=[L]:(w*T>5e5||M==1)&&(O=[0]),$&&(D={level:0});const z=nE;for(var H=0;H>1)+256&255;if(L==4)for(D=P;D>1)&255;for(D=P;D>1)&255}if(L==4){for(D=0;D>2);let z;if(M.length<2e7)for(var H=0;H>2]=z.ind,P[H>>2]=z.est.rgba;else for(H=0;H>2]=z.ind,P[H>>2]=z.est.rgba}return{abuf:T.buffer,inds:O,plte:A}}function y(S,w,M){M==null&&(M=1e-4);const T=new Uint32Array(S.buffer),P={i0:0,i1:S.length,bst:null,est:null,tdst:0,left:null,right:null};P.bst=C(S,P.i0,P.i1),P.est=k(P.bst);const L=[P];for(;L.lengthA&&(A=L[$].est.L,D=$);if(A=z||O.i1<=z){O.est.L=0;continue}const H={i0:O.i0,i1:z,bst:null,est:null,tdst:0,left:null,right:null};H.bst=C(S,H.i0,H.i1),H.est=k(H.bst);const j={i0:z,i1:O.i1,bst:null,est:null,tdst:0,left:null,right:null};for(j.bst={R:[],m:[],N:O.bst.N-H.bst.N},$=0;$<16;$++)j.bst.R[$]=O.bst.R[$]-H.bst.R[$];for($=0;$<4;$++)j.bst.m[$]=O.bst.m[$]-H.bst.m[$];j.est=k(j.bst),O.left=H,O.right=j,L[D]=H,L.push(j)}for(L.sort(((A,D)=>D.bst.N-A.bst.N)),$=0;$0&&($=S.right,A=S.left);const D=b($,w,M,T,P);if(D.tdst<=L*L)return D;const O=b(A,w,M,T,P);return O.tdstL;)T-=4;if(M>=T)break;const $=w[M>>2];w[M>>2]=w[T>>2],w[T>>2]=$,M+=4,T-=4}for(;E(S,M,P)>L;)M-=4;return M+4}function E(S,w,M){return S[w]*M[0]+S[w+1]*M[1]+S[w+2]*M[2]+S[w+3]*M[3]}function C(S,w,M){const T=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],P=[0,0,0,0],L=M-w>>2;for(let $=w;$>>0}}var N={multVec:(S,w)=>[S[0]*w[0]+S[1]*w[1]+S[2]*w[2]+S[3]*w[3],S[4]*w[0]+S[5]*w[1]+S[6]*w[2]+S[7]*w[3],S[8]*w[0]+S[9]*w[1]+S[10]*w[2]+S[11]*w[3],S[12]*w[0]+S[13]*w[1]+S[14]*w[2]+S[15]*w[3]],dot:(S,w)=>S[0]*w[0]+S[1]*w[1]+S[2]*w[2]+S[3]*w[3],sml:(S,w)=>[S*w[0],S*w[1],S*w[2],S*w[3]]};En.encode=function(w,M,T,P,L,$,A){P==null&&(P=0),A==null&&(A=!1);const D=d(w,M,T,P,[!1,!1,!1,0,A,!1]);return c(D,-1),l(D,M,T,L,$)},En.encodeLL=function(w,M,T,P,L,$,A,D){const O={ctype:0+(P==1?0:2)+(L==0?0:4),depth:$,frames:[]},z=(P+L)*$,H=z*M;for(let j=0;j>>0),C(1),C(32),k(3),k(l),k(2835),k(2835),N(8),k(16711680),k(65280),k(255),k(4278190080),k(1466527264),(function S(){for(;_0;){for(y=122+_*a,g=0;g>>24,u.setUint32(y+g,m<<8|p),g+=4;_++}E{e(new Blob([n],{type:"image/bmp"}))}))},_dly:9};var Rt={CHROME:"CHROME",FIREFOX:"FIREFOX",DESKTOP_SAFARI:"DESKTOP_SAFARI",IE:"IE",IOS:"IOS",ETC:"ETC"},rE={[Rt.CHROME]:16384,[Rt.FIREFOX]:11180,[Rt.DESKTOP_SAFARI]:16384,[Rt.IE]:8192,[Rt.IOS]:4096,[Rt.ETC]:8192};const Dc=typeof window<"u",pp=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,Gi=Dc&&window.cordova&&window.cordova.require&&window.cordova.require("cordova/modulemapper"),sE=(Dc||pp)&&(Gi&&Gi.getOriginalSymbol(window,"File")||typeof File<"u"&&File),mp=(Dc||pp)&&(Gi&&Gi.getOriginalSymbol(window,"FileReader")||typeof FileReader<"u"&&FileReader);function Ic(t,e,n=Date.now()){return new Promise((r=>{const s=t.split(","),o=s[0].match(/:(.*?);/)[1],i=globalThis.atob(s[1]);let a=i.length;const l=new Uint8Array(a);for(;a--;)l[a]=i.charCodeAt(a);const c=new Blob([l],{type:o});c.name=e,c.lastModified=n,r(c)}))}function bp(t){return new Promise(((e,n)=>{const r=new mp;r.onload=()=>e(r.result),r.onerror=s=>n(s),r.readAsDataURL(t)}))}function yp(t){return new Promise(((e,n)=>{const r=new Image;r.onload=()=>e(r),r.onerror=s=>n(s),r.src=t}))}function kr(){if(kr.cachedResult!==void 0)return kr.cachedResult;let t=Rt.ETC;const{userAgent:e}=navigator;return/Chrom(e|ium)/i.test(e)?t=Rt.CHROME:/iP(ad|od|hone)/i.test(e)&&/WebKit/i.test(e)?t=Rt.IOS:/Safari/i.test(e)?t=Rt.DESKTOP_SAFARI:/Firefox/i.test(e)?t=Rt.FIREFOX:(/MSIE/i.test(e)||document.documentMode)&&(t=Rt.IE),kr.cachedResult=t,kr.cachedResult}function vp(t,e){const n=kr(),r=rE[n];let s=t,o=e,i=s*o;const a=s>o?o/s:s/o;for(;i>r*r;){const l=(r+s)/2,c=(r+o)/2;lgp.toBlob(t,d))).then(function(d){try{return a=d,a.name=n,a.lastModified=r,c.call(this)}catch(u){return i(u)}}.bind(this),i);{let d=function(){return c.call(this)};if(typeof OffscreenCanvas=="function"&&t instanceof OffscreenCanvas)return t.convertToBlob({type:e,quality:s}).then(function(u){try{return a=u,a.name=n,a.lastModified=r,d.call(this)}catch(f){return i(f)}}.bind(this),i);{let u;return u=t.toDataURL(e,s),Ic(u,n,r).then(function(f){try{return a=f,d.call(this)}catch(g){return i(g)}}.bind(this),i)}}}function l(){return o(a)}}))}function $n(t){t.width=0,t.height=0}function is(){return new Promise((function(t,e){let n,r,s,o;return is.cachedResult!==void 0?t(is.cachedResult):Ic("data:image/jpeg;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAYAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAf/AABEIAAEAAgMBEQACEQEDEQH/xABKAAEAAAAAAAAAAAAAAAAAAAALEAEAAAAAAAAAAAAAAAAAAAAAAQEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwA/8H//2Q==","test.jpg",Date.now()).then((function(i){try{return n=i,Ki(n).then((function(a){try{return r=a[1],qi(r,n.type,n.name,n.lastModified).then((function(l){try{return s=l,$n(r),Ki(s).then((function(c){try{return o=c[0],is.cachedResult=o.width===1&&o.height===2,t(is.cachedResult)}catch(d){return e(d)}}),e)}catch(c){return e(c)}}),e)}catch(l){return e(l)}}),e)}catch(a){return e(a)}}),e)}))}function wp(t){return new Promise(((e,n)=>{const r=new mp;r.onload=s=>{const o=new DataView(s.target.result);if(o.getUint16(0,!1)!=65496)return e(-2);const i=o.byteLength;let a=2;for(;an(s),r.readAsArrayBuffer(t)}))}function kp(t,e){const{width:n}=t,{height:r}=t,{maxWidthOrHeight:s}=e;let o,i=t;return isFinite(s)&&(n>s||r>s)&&([i,o]=Yi(n,r),n>r?(i.width=s,i.height=r/n*s):(i.width=n/r*s,i.height=s),o.drawImage(t,0,0,i.width,i.height),$n(t)),i}function Cp(t,e){const{width:n}=t,{height:r}=t,[s,o]=Yi(n,r);switch(e>4&&e<9?(s.width=r,s.height=n):(s.width=n,s.height=r),e){case 2:o.transform(-1,0,0,1,n,0);break;case 3:o.transform(-1,0,0,-1,n,r);break;case 4:o.transform(1,0,0,-1,0,r);break;case 5:o.transform(0,1,1,0,0,0);break;case 6:o.transform(0,1,-1,0,r,0);break;case 7:o.transform(0,-1,-1,0,r,n);break;case 8:o.transform(0,-1,1,0,0,n)}return o.drawImage(t,0,0,n,r),$n(t),s}function Sp(t,e,n=0){return new Promise((function(r,s){let o,i,a,l,c,d,u,f,g,m,p,y,b,_,x,E,C,k,N,S;function w(T=5){if(e.signal&&e.signal.aborted)throw e.signal.reason;o+=T,e.onProgress(Math.min(o,100))}function M(T){if(e.signal&&e.signal.aborted)throw e.signal.reason;o=Math.min(Math.max(T,o),100),e.onProgress(o)}return o=n,i=e.maxIteration||10,a=1024*e.maxSizeMB*1024,w(),Ki(t,e).then(function(T){try{return[,l]=T,w(),c=kp(l,e),w(),new Promise((function(P,L){var $;if(!($=e.exifOrientation))return wp(t).then(function(D){try{return $=D,A.call(this)}catch(O){return L(O)}}.bind(this),L);function A(){return P($)}return A.call(this)})).then(function(P){try{return d=P,w(),is().then(function(L){try{return u=L?c:Cp(c,d),w(),f=e.initialQuality||1,g=e.fileType||t.type,qi(u,g,t.name,t.lastModified,f).then(function($){try{{let D=function(){if(i--&&(x>a||x>b)){let z,H;return z=S?.95*N.width:N.width,H=S?.95*N.height:N.height,[C,k]=Yi(z,H),k.drawImage(N,0,0,z,H),f*=g==="image/png"?.85:.95,qi(C,g,t.name,t.lastModified,f).then((function(j){try{return E=j,$n(N),N=C,x=E.size,M(Math.min(99,Math.floor((_-x)/(_-a)*100))),D}catch(W){return s(W)}}),s)}return[1]},O=function(){return $n(N),$n(C),$n(c),$n(u),$n(l),M(100),r(E)};if(m=$,w(),p=m.size>a,y=m.size>t.size,!p&&!y)return M(100),r(m);var A;return b=t.size,_=m.size,x=_,N=u,S=!e.alwaysKeepResolution&&p,(A=function(z){for(;z;){if(z.then)return void z.then(A,s);try{if(z.pop){if(z.length)return z.pop()?O.call(this):z;z=D}else z=z.call(this)}catch(H){return s(H)}}}.bind(this))(D)}}catch(D){return s(D)}}.bind(this),s)}catch($){return s($)}}.bind(this),s)}catch(L){return s(L)}}.bind(this),s)}catch(P){return s(P)}}.bind(this),s)}))}const oE=` let scriptImported = false self.addEventListener('message', async (e) => { const { file, id, imageCompressionLibUrl, options } = e.data @@ -221,11 +220,11 @@ self.addEventListener('message', async (e) => { self.postMessage({ error: e.message + '\\n' + e.stack, id }) } }) -`;let Bc;function _E(e,t){return new Promise(((n,r)=>{Bc||(Bc=(function(i){const a=[];return a.push(i),URL.createObjectURL(new Blob(a))})(SE));const s=new Worker(Bc);s.addEventListener("message",(function(i){if(t.signal&&t.signal.aborted)s.terminate();else if(i.data.progress===void 0){if(i.data.error)return r(new Error(i.data.error)),void s.terminate();n(i.data.file),s.terminate()}else t.onProgress(i.data.progress)})),s.addEventListener("error",r),t.signal&&t.signal.addEventListener("abort",(()=>{r(t.signal.reason),s.terminate()})),s.postMessage({file:e,imageCompressionLibUrl:t.libURL,options:{...t,onProgress:void 0,signal:void 0}})}))}function Yt(e,t){return new Promise((function(n,r){let s,o,i,a,l,c;if(s={...t},i=0,{onProgress:a}=s,s.maxSizeMB=s.maxSizeMB||Number.POSITIVE_INFINITY,l=typeof s.useWebWorker!="boolean"||s.useWebWorker,delete s.useWebWorker,s.onProgress=g=>{i=g,typeof a=="function"&&a(i)},!(e instanceof Blob||e instanceof CE))return r(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(e.type))return r(new Error("The file given is not an image"));if(c=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,!l||typeof Worker!="function"||c)return $p(e,s).then(function(g){try{return o=g,f.call(this)}catch(m){return r(m)}}.bind(this),r);var d=function(){try{return f.call(this)}catch(g){return r(g)}}.bind(this),u=function(g){try{return $p(e,s).then((function(m){try{return o=m,d()}catch(p){return r(p)}}),r)}catch(m){return r(m)}};try{return s.libURL=s.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",_E(e,s).then((function(g){try{return o=g,d()}catch{return u()}}),u)}catch{u()}function f(){try{o.name=e.name,o.lastModified=e.lastModified}catch{}try{s.preserveExif&&e.type==="image/jpeg"&&(!s.fileType||s.fileType&&s.fileType===e.type)&&(o=_p(e,o))}catch{}return n(o)}}))}Yt.getDataUrlFromFile=Tp,Yt.getFilefromDataUrl=Uc,Yt.loadImage=Op,Yt.drawImageInCanvas=Dp,Yt.drawFileInCanvas=ra,Yt.canvasToFile=sa,Yt.getExifOrientation=Ip,Yt.handleMaxWidthOrHeight=Lp,Yt.followExifOrientation=Pp,Yt.cleanupCanvasMemory=Wn,Yt.isAutoOrientationInBrowser=Ss,Yt.approximateBelowMaximumCanvasSizeOfBrowser=Ap,Yt.copyExifWithoutOrientation=_p,Yt.getBrowserName=Ir,Yt.version="2.0.2";const EE=window.G7Core,jc=e=>{const t=["B","KB","MB","GB"];let n=0,r=e;for(;r>=1024&&n{if(e&&typeof e=="object"){const t=e;if(t.response?.data?.message)return t.response.data.message;if(t.response?.data?.errors){const n=Object.values(t.response.data.errors)[0];if(Array.isArray(n)&&n[0])return n[0]}if(t.message)return t.message}return e instanceof Error?e.message:"업로드 실패"},NE=e=>e?e.startsWith("image/")?"fa-file-image":e.startsWith("video/")?"fa-file-video":e.startsWith("audio/")?"fa-file-audio":e==="application/pdf"?"fa-file-pdf":e.includes("word")||e.includes("document")?"fa-file-word":e.includes("excel")||e.includes("spreadsheet")?"fa-file-excel":e.includes("zip")||e.includes("compressed")?"fa-file-zipper":"fa-file":"fa-file",Ft=(e,t)=>EE?.t?.(e,t)??e,Fp=window.G7Core?.createLogger?.("Comp:FileUploader")??{log:(...e)=>console.log("[Comp:FileUploader]",...e),warn:(...e)=>console.warn("[Comp:FileUploader]",...e),error:(...e)=>console.error("[Comp:FileUploader]",...e)},Lr=window.G7Core;function TE(e){const{attachmentableType:t,attachmentableId:n,collection:r,maxFiles:s,maxSize:o,maxConcurrentUploads:i,accept:a,imageCompression:l,roleIds:c,autoUpload:d,initialFiles:u,endpoints:f,onFilesChange:g,onUploadComplete:m,onUploadError:p,onRemove:y,onReorder:b,uploadTriggerEvent:_,confirmBeforeRemove:x,uploadParams:M}=e,[C,k]=v.useState(u),[N,S]=v.useState([]),[w,E]=v.useState(!1),D=v.useRef(null),P=v.useRef(0),L=v.useRef(new Set),$=v.useRef(new Map),A=v.useRef(null),[O,T]=v.useState(!1),[z,H]=v.useState(null),[j,G]=v.useState(!1),U=v.useRef(!1),[W,Y]=v.useState([]),[V,X]=v.useState(!1),[K,q]=v.useState(0),ne=v.useRef(0),[ee,de]=v.useState(new Map),J=v.useRef(new Map),le=v.useRef(g);le.current=g,v.useEffect(()=>{le.current?.(N)},[N]),v.useEffect(()=>{if(U.current)return;const re=u.filter(we=>!L.current.has(we.id)&&(!we.hash||!L.current.has(we.hash))),ue=new Set(re.map(we=>we.id)),me=Array.from($.current.values()).filter(we=>!ue.has(we.id)&&!L.current.has(we.id));k([...re,...me])},[u]);const pe=v.useCallback(async re=>{const ue={maxSizeMB:l?.maxSizeMB??1,maxWidthOrHeight:l?.maxWidthOrHeight??1920,useWebWorker:!0};return await Yt(re,ue)},[l]),ae=v.useCallback(async re=>{const ue=C.length+N.length,me=Math.max(0,s-ue),we=Array.from(re).slice(0,me);if(re.length>me){const ve=Array.from(re).slice(me),Ue=Ft("attachment.upload_limit_exceeded",{max:s});p?.(Ue,ve[0]??re[0])}for(const ve of we){if(ve.size>o*1024*1024){p?.(`[${ve.name}] 파일 크기(${jc(ve.size)})가 최대 허용 크기(${o}MB)를 초과합니다.`,ve);continue}if(a){const _e="."+ve.name.split(".").pop()?.toLowerCase();if(!a.split(",").map(Ce=>Ce.trim().toLowerCase()).includes(_e)){p?.(`[${ve.name}] 허용되지 않는 파일 형식입니다. (허용: ${a})`,ve);continue}}const Ue={id:`pending-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,file:ve,original_filename:ve.name,mime_type:ve.type,size:ve.size,size_formatted:jc(ve.size),status:"pending",progress:0};if(ve.type.startsWith("image/")&&(Ue.preview=URL.createObjectURL(ve)),S(_e=>[..._e,Ue]),ve.type.startsWith("image/")&&l){S(_e=>_e.map(Le=>Le.id===Ue.id?{...Le,status:"compressing"}:Le));try{const _e=await pe(ve);S(Le=>Le.map(Ce=>Ce.id===Ue.id?{...Ce,compressedBlob:_e,size:_e.size,size_formatted:jc(_e.size),status:"pending",progress:0}:Ce))}catch{S(_e=>_e.map(Le=>Le.id===Ue.id?{...Le,status:"error",error:"이미지 압축 실패"}:Le))}}}},[C.length,N.length,s,o,a,l,pe,p]),ye=v.useCallback(async re=>{const ue=new FormData,me=re.compressedBlob?new File([re.compressedBlob],re.original_filename,{type:re.mime_type}):re.file;ue.append("file",me),t&&ue.append("attachmentable_type",t),n&&ue.append("attachmentable_id",String(n)),ue.append("collection",r),c.forEach(we=>{ue.append("role_ids[]",String(we))}),M&&Object.entries(M).forEach(([we,ve])=>{ue.append(we,ve)});try{const we=await Lr.api.post(f.upload,ue,{headers:{"Content-Type":"multipart/form-data"},onUploadProgress:Ue=>{const _e=Math.round(Ue.loaded*100/(Ue.total??1));S(Le=>Le.map(Ce=>Ce.id===re.id?{...Ce,progress:_e}:Ce))}});return we?.data?.data??we?.data}catch(we){const ve=ME(we);throw new Error(ve)}},[t,n,r,c,f.upload]),he=v.useCallback(async()=>{const re=N.filter(_e=>_e.status==="pending"||_e.status==="error");if(re.length===0)return A.current=C,[];const ue=[],me=new Map,we=[...re],ve=async()=>{for(;we.length>0&&P.currentLe.map(Ce=>Ce.id===_e.id?{...Ce,status:"uploading",progress:0}:Ce));try{const Le=await ye(_e);if(Le)ue.push(Le),me.set(String(_e.id),String(Le.hash)),$.current.set(Le.id,Le),S(Ce=>Ce.filter(Ge=>Ge.id!==_e.id)),k(Ce=>[...Ce,Le]),Y(Ce=>{if(Ce.length===0)return Ce;const Ge=String(_e.id);return Ce.some(ze=>String(ze)===Ge)?Ce.map(ze=>String(ze)===Ge?String(Le.hash):ze):Ce}),_e.preview&&URL.revokeObjectURL(_e.preview);else{const Ce=Ft("attachment.upload_response_invalid");S(Ge=>Ge.map(Qe=>Qe.id===_e.id?{...Qe,status:"error",error:Ce}:Qe)),p?.(`[${_e.original_filename}] ${Ce}`,_e.file)}}catch(Le){const Ce=Le instanceof Error?Le.message:"업로드 실패";S(Ge=>Ge.map(Qe=>Qe.id===_e.id?{...Qe,status:"error",error:Ce}:Qe)),p?.(`[${_e.original_filename}] ${Ce}`,_e.file)}finally{P.current--}}},Ue=Array(Math.min(i,re.length)).fill(null).map(()=>ve());if(await Promise.all(Ue),ue.length>0){m?.(ue);const _e=[...C,...ue];if(W.length>0){const Le=W.map(Qe=>me.get(String(Qe))??Qe),Ce=new Map(Le.map((Qe,ze)=>[String(Qe),ze])),Ge=[..._e].sort((Qe,ze)=>{const Ne=Ce.get(String(Qe.hash))??_e.length,Ve=Ce.get(String(ze.hash))??_e.length;return Ne-Ve}).map((Qe,ze)=>({...Qe,sort_order:ze,order:ze+1}));A.current=Ge,f.reorder&&b&&b(Ge)}else A.current=_e.map((Le,Ce)=>({...Le,sort_order:Ce,order:Ce+1}))}else A.current=C;return ue},[N,i,ye,m,p,W,C,b,f.reorder]);v.useEffect(()=>{if(!_)return;const re=Lr.componentEvent.on(_,async()=>{const ue=await he(),me=A.current??[...C,...ue];return{uploadedAttachments:ue,existingFiles:C,allFiles:me}});return()=>{re()}},[_,he,C]),v.useEffect(()=>{d&&N.some(re=>re.status==="pending")&&he()},[d,N,he]);const ge=v.useCallback(async re=>{G(!0);try{if(re.id&&f.delete){const me=f.delete.replace(":id",String(re.id));await Lr.api.delete(me)}const ue=re.hash||re.id;re.id&&(L.current.add(re.id),$.current.delete(re.id)),re.hash&&L.current.add(re.hash),k(me=>me.filter(we=>(we.hash||we.id)!==ue)),y?.(re.hash||re.id),T(!1),H(null)}catch{Fp.error("첨부파일 삭제 실패")}finally{G(!1)}},[y,f.delete]),ke=v.useCallback(async re=>{"hash"in re?x?(H(re),T(!0)):await ge(re):(re.preview&&URL.revokeObjectURL(re.preview),S(ue=>ue.filter(me=>me.id!==re.id)))},[x,ge]),Fe=v.useCallback(re=>{S(ue=>ue.map(me=>me.id===re.id?{...me,status:"pending",error:void 0}:me))},[]),oe=v.useCallback(async re=>{const{arrayMove:ue}=await Promise.resolve().then(()=>QS),{active:me,over:we}=re;if(!we||me.id===we.id)return;const ve=[...C,...N];let Ue;if(W.length===0)Ue=ve;else{const Ne=new Map(W.map((Ve,qe)=>[String(Ve),qe]));Ue=[...ve].sort((Ve,qe)=>{const nt="hash"in Ve?String(Ve.hash):String(Ve.id),lt="hash"in qe?String(qe.hash):String(qe.id);return(Ne.get(nt)??ve.length)-(Ne.get(lt)??ve.length)})}const _e=Ue.findIndex(Ne=>"hash"in Ne?Ne.hash===me.id:Ne.id===me.id),Le=Ue.findIndex(Ne=>"hash"in Ne?Ne.hash===we.id:Ne.id===we.id),Ce=ue(Ue,_e,Le);Y(Ce.map(Ne=>"hash"in Ne?Ne.hash:Ne.id));const Ge=Ce.filter(Ne=>"hash"in Ne),Qe=Ce.filter(Ne=>!("hash"in Ne));U.current=!0,k(Ge),S(Qe);const ze=Ge.map((Ne,Ve)=>({...Ne,sort_order:Ve,order:Ve+1}));if(Ge.length>0&&f.reorder){const Ne=Ge.map((Ve,qe)=>({id:Ve.id,order:qe+1}));try{const Ve=await Lr.api.patch(f.reorder,{order:Ne});Lr.toast.success(Ve.message)}catch(Ve){const qe=Ve instanceof Error?Ve.message:Ft("attachment.reorder_failed");Lr.toast.error(qe)}}b?.(ze)},[C,N,W,f.reorder,b]),Me=v.useMemo(()=>[...C,...N].filter(re=>"is_image"in re?re.is_image:re.mime_type?.startsWith("image/")??!1),[C,N]),Ae=v.useCallback(re=>{const ue=Me.findIndex(me=>"hash"in re&&"hash"in me?me.hash===re.hash:"id"in re&&"id"in me&&!("hash"in re)&&!("hash"in me)?me.id===re.id:!1);ue!==-1&&(ne.current+=1,q(ue),X(!0))},[Me]);v.useEffect(()=>{let re=!1;return(async()=>{const me=C.filter(ve=>ve.is_image),we=new Set(me.map(ve=>ve.id));for(const[ve,Ue]of J.current)we.has(ve)||(URL.revokeObjectURL(Ue),J.current.delete(ve));for(const ve of me){if(re)break;if(!J.current.has(ve.id))try{const Ue=await Lr.api.get(ve.download_url,{responseType:"blob"});if(!re&&Ue){const _e=URL.createObjectURL(Ue);J.current.set(ve.id,_e),de(new Map(J.current))}}catch(Ue){Fp.error("Failed to load authenticated image:",Ue)}}})(),()=>{re=!0}},[C]),v.useEffect(()=>()=>{J.current.forEach(re=>URL.revokeObjectURL(re)),J.current.clear()},[]);const De=v.useCallback(async re=>{const{executeImageDownload:ue}=await Promise.resolve().then(()=>pE);await ue({src:re.download_url,downloadUrl:re.download_url,filename:re.original_filename,downloadRequiresAuth:!0})},[]),Ke=v.useMemo(()=>{const re=[...C,...N];if(W.length===0)return re;const ue=new Map(W.map((me,we)=>[String(me),we]));return[...re].sort((me,we)=>{const ve="hash"in me?String(me.hash):String(me.id),Ue="hash"in we?String(we.hash):String(we.id),_e=ue.get(ve)??re.length,Le=ue.get(Ue)??re.length;return _e-Le})},[C,N,W]),pt=Ke.length,mt=pt>0,je=ptS([]),[]),We=v.useCallback(()=>N,[N]);return{existingFiles:C,pendingFiles:N,isDragOver:w,setIsDragOver:E,isDeleting:j,totalCount:pt,hasFiles:mt,canAddMore:je,allItems:Ke,imageFiles:Me,handleFiles:ae,handleRemove:ke,handleRetry:Fe,handleUploadAll:he,handleOpenGallery:Ae,handleDownload:De,handleDragEnd:oe,inputRef:D,galleryOpen:V,setGalleryOpen:X,galleryStartIndex:K,galleryKeyRef:ne,confirmDialogOpen:O,setConfirmDialogOpen:T,itemToDelete:z,executeRemoveAttachment:ge,authenticatedImageUrls:ee,clear:tt,getPendingFiles:We}}const OE=({isDragOver:e,canAddMore:t,onFiles:n,setIsDragOver:r,inputRef:s,accept:o,maxFiles:i,maxSize:a,disabled:l,children:c})=>{const d=t&&!l;return h.jsxs(I,{className:` +`;let Lc;function iE(t,e){return new Promise(((n,r)=>{Lc||(Lc=(function(i){const a=[];return a.push(i),URL.createObjectURL(new Blob(a))})(oE));const s=new Worker(Lc);s.addEventListener("message",(function(i){if(e.signal&&e.signal.aborted)s.terminate();else if(i.data.progress===void 0){if(i.data.error)return r(new Error(i.data.error)),void s.terminate();n(i.data.file),s.terminate()}else e.onProgress(i.data.progress)})),s.addEventListener("error",r),e.signal&&e.signal.addEventListener("abort",(()=>{r(e.signal.reason),s.terminate()})),s.postMessage({file:t,imageCompressionLibUrl:e.libURL,options:{...e,onProgress:void 0,signal:void 0}})}))}function Ht(t,e){return new Promise((function(n,r){let s,o,i,a,l,c;if(s={...e},i=0,{onProgress:a}=s,s.maxSizeMB=s.maxSizeMB||Number.POSITIVE_INFINITY,l=typeof s.useWebWorker!="boolean"||s.useWebWorker,delete s.useWebWorker,s.onProgress=g=>{i=g,typeof a=="function"&&a(i)},!(t instanceof Blob||t instanceof sE))return r(new Error("The file given is not an instance of Blob or File"));if(!/^image/.test(t.type))return r(new Error("The file given is not an image"));if(c=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,!l||typeof Worker!="function"||c)return Sp(t,s).then(function(g){try{return o=g,f.call(this)}catch(m){return r(m)}}.bind(this),r);var d=function(){try{return f.call(this)}catch(g){return r(g)}}.bind(this),u=function(g){try{return Sp(t,s).then((function(m){try{return o=m,d()}catch(p){return r(p)}}),r)}catch(m){return r(m)}};try{return s.libURL=s.libURL||"https://cdn.jsdelivr.net/npm/browser-image-compression@2.0.2/dist/browser-image-compression.js",iE(t,s).then((function(g){try{return o=g,d()}catch{return u()}}),u)}catch{u()}function f(){try{o.name=t.name,o.lastModified=t.lastModified}catch{}try{s.preserveExif&&t.type==="image/jpeg"&&(!s.fileType||s.fileType&&s.fileType===t.type)&&(o=hp(t,o))}catch{}return n(o)}}))}Ht.getDataUrlFromFile=bp,Ht.getFilefromDataUrl=Ic,Ht.loadImage=yp,Ht.drawImageInCanvas=xp,Ht.drawFileInCanvas=Ki,Ht.canvasToFile=qi,Ht.getExifOrientation=wp,Ht.handleMaxWidthOrHeight=kp,Ht.followExifOrientation=Cp,Ht.cleanupCanvasMemory=$n,Ht.isAutoOrientationInBrowser=is,Ht.approximateBelowMaximumCanvasSizeOfBrowser=vp,Ht.copyExifWithoutOrientation=hp,Ht.getBrowserName=kr,Ht.version="2.0.2";const aE=window.G7Core,Pc=t=>{const e=["B","KB","MB","GB"];let n=0,r=t;for(;r>=1024&&n{if(t&&typeof t=="object"){const e=t;if(e.response?.data?.message)return e.response.data.message;if(e.response?.data?.errors){const n=Object.values(e.response.data.errors)[0];if(Array.isArray(n)&&n[0])return n[0]}if(e.message)return e.message}return t instanceof Error?t.message:"업로드 실패"},cE=t=>t?t.startsWith("image/")?"fa-file-image":t.startsWith("video/")?"fa-file-video":t.startsWith("audio/")?"fa-file-audio":t==="application/pdf"?"fa-file-pdf":t.includes("word")||t.includes("document")?"fa-file-word":t.includes("excel")||t.includes("spreadsheet")?"fa-file-excel":t.includes("zip")||t.includes("compressed")?"fa-file-zipper":"fa-file":"fa-file",It=(t,e)=>aE?.t?.(t,e)??t,_p=window.G7Core?.createLogger?.("Comp:FileUploader")??{log:(...t)=>console.log("[Comp:FileUploader]",...t),warn:(...t)=>console.warn("[Comp:FileUploader]",...t),error:(...t)=>console.error("[Comp:FileUploader]",...t)},Cr=window.G7Core;function dE(t){const{attachmentableType:e,attachmentableId:n,collection:r,maxFiles:s,maxSize:o,maxConcurrentUploads:i,accept:a,imageCompression:l,roleIds:c,autoUpload:d,initialFiles:u,endpoints:f,onFilesChange:g,onUploadComplete:m,onUploadError:p,onRemove:y,onReorder:b,uploadTriggerEvent:_,confirmBeforeRemove:x,uploadParams:E}=t,[C,k]=v.useState(u),[N,S]=v.useState([]),[w,M]=v.useState(!1),T=v.useRef(null),P=v.useRef(0),L=v.useRef(new Set),$=v.useRef(new Map),A=v.useRef(null),[D,O]=v.useState(!1),[z,H]=v.useState(null),[j,W]=v.useState(!1),U=v.useRef(!1),[B,G]=v.useState([]),[V,K]=v.useState(!1),[X,Z]=v.useState(0),ee=v.useRef(0),[ne,he]=v.useState(new Map),J=v.useRef(new Map),le=v.useRef(g);le.current=g,v.useEffect(()=>{le.current?.(N)},[N]),v.useEffect(()=>{if(U.current)return;const te=u.filter(pe=>!L.current.has(pe.id)&&(!pe.hash||!L.current.has(pe.hash))),ge=new Set(te.map(pe=>pe.id)),me=Array.from($.current.values()).filter(pe=>!ge.has(pe.id)&&!L.current.has(pe.id));k([...te,...me])},[u]);const ue=v.useCallback(async te=>{const ge={maxSizeMB:l?.maxSizeMB??1,maxWidthOrHeight:l?.maxWidthOrHeight??1920,useWebWorker:!0};return await Ht(te,ge)},[l]),ae=v.useCallback(async te=>{const ge=C.length+N.length,me=Math.max(0,s-ge),pe=Array.from(te).slice(0,me);if(te.length>me){const be=Array.from(te).slice(me),Ue=It("attachment.upload_limit_exceeded",{max:s});p?.(Ue,be[0]??te[0])}for(const be of pe){if(be.size>o*1024*1024){p?.(`[${be.name}] 파일 크기(${Pc(be.size)})가 최대 허용 크기(${o}MB)를 초과합니다.`,be);continue}if(a){const Ce="."+be.name.split(".").pop()?.toLowerCase();if(!a.split(",").map(Se=>Se.trim().toLowerCase()).includes(Ce)){p?.(`[${be.name}] 허용되지 않는 파일 형식입니다. (허용: ${a})`,be);continue}}const Ue={id:`pending-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,file:be,original_filename:be.name,mime_type:be.type,size:be.size,size_formatted:Pc(be.size),status:"pending",progress:0};if(be.type.startsWith("image/")&&(Ue.preview=URL.createObjectURL(be)),S(Ce=>[...Ce,Ue]),be.type.startsWith("image/")&&l){S(Ce=>Ce.map(ze=>ze.id===Ue.id?{...ze,status:"compressing"}:ze));try{const Ce=await ue(be);S(ze=>ze.map(Se=>Se.id===Ue.id?{...Se,compressedBlob:Ce,size:Ce.size,size_formatted:Pc(Ce.size),status:"pending",progress:0}:Se))}catch{S(Ce=>Ce.map(ze=>ze.id===Ue.id?{...ze,status:"error",error:"이미지 압축 실패"}:ze))}}}},[C.length,N.length,s,o,a,l,ue,p]),ve=v.useCallback(async te=>{const ge=new FormData,me=te.compressedBlob?new File([te.compressedBlob],te.original_filename,{type:te.mime_type}):te.file;ge.append("file",me),e&&ge.append("attachmentable_type",e),n&&ge.append("attachmentable_id",String(n)),ge.append("collection",r),c.forEach(pe=>{ge.append("role_ids[]",String(pe))}),E&&Object.entries(E).forEach(([pe,be])=>{ge.append(pe,be)});try{const pe=await Cr.api.post(f.upload,ge,{headers:{"Content-Type":"multipart/form-data"},onUploadProgress:Ue=>{const Ce=Math.round(Ue.loaded*100/(Ue.total??1));S(ze=>ze.map(Se=>Se.id===te.id?{...Se,progress:Ce}:Se))}});return pe?.data?.data??pe?.data}catch(pe){const be=lE(pe);throw new Error(be)}},[e,n,r,c,f.upload]),fe=v.useCallback(async()=>{const te=N.filter(Ce=>Ce.status==="pending"||Ce.status==="error");if(te.length===0)return A.current=C,[];const ge=[],me=new Map,pe=[...te],be=async()=>{for(;pe.length>0&&P.currentze.map(Se=>Se.id===Ce.id?{...Se,status:"uploading",progress:0}:Se));try{const ze=await ve(Ce);if(ze)ge.push(ze),me.set(String(Ce.id),String(ze.hash)),$.current.set(ze.id,ze),S(Se=>Se.filter(Re=>Re.id!==Ce.id)),k(Se=>[...Se,ze]),G(Se=>{if(Se.length===0)return Se;const Re=String(Ce.id);return Se.some(Pe=>String(Pe)===Re)?Se.map(Pe=>String(Pe)===Re?String(ze.hash):Pe):Se}),Ce.preview&&URL.revokeObjectURL(Ce.preview);else{const Se=It("attachment.upload_response_invalid");S(Re=>Re.map(ot=>ot.id===Ce.id?{...ot,status:"error",error:Se}:ot)),p?.(`[${Ce.original_filename}] ${Se}`,Ce.file)}}catch(ze){const Se=ze instanceof Error?ze.message:"업로드 실패";S(Re=>Re.map(ot=>ot.id===Ce.id?{...ot,status:"error",error:Se}:ot)),p?.(`[${Ce.original_filename}] ${Se}`,Ce.file)}finally{P.current--}}},Ue=Array(Math.min(i,te.length)).fill(null).map(()=>be());if(await Promise.all(Ue),ge.length>0){m?.(ge);const Ce=[...C,...ge];if(B.length>0){const ze=B.map(ot=>me.get(String(ot))??ot),Se=new Map(ze.map((ot,Pe)=>[String(ot),Pe])),Re=[...Ce].sort((ot,Pe)=>{const Te=Se.get(String(ot.hash))??Ce.length,Ve=Se.get(String(Pe.hash))??Ce.length;return Te-Ve}).map((ot,Pe)=>({...ot,sort_order:Pe,order:Pe+1}));A.current=Re,f.reorder&&b&&b(Re)}else A.current=Ce.map((ze,Se)=>({...ze,sort_order:Se,order:Se+1}))}else A.current=C;return ge},[N,i,ve,m,p,B,C,b,f.reorder]);v.useEffect(()=>{if(!_)return;const te=Cr.componentEvent.on(_,async()=>{const ge=await fe(),me=A.current??[...C,...ge];return{uploadedAttachments:ge,existingFiles:C,allFiles:me}});return()=>{te()}},[_,fe,C]),v.useEffect(()=>{d&&N.some(te=>te.status==="pending")&&fe()},[d,N,fe]);const ye=v.useCallback(async te=>{W(!0);try{if(te.id&&f.delete){const me=f.delete.replace(":id",String(te.id));await Cr.api.delete(me)}const ge=te.hash||te.id;te.id&&(L.current.add(te.id),$.current.delete(te.id)),te.hash&&L.current.add(te.hash),k(me=>me.filter(pe=>(pe.hash||pe.id)!==ge)),y?.(te.hash||te.id),O(!1),H(null)}catch{_p.error("첨부파일 삭제 실패")}finally{W(!1)}},[y,f.delete]),we=v.useCallback(async te=>{"hash"in te?x?(H(te),O(!0)):await ye(te):(te.preview&&URL.revokeObjectURL(te.preview),S(ge=>ge.filter(me=>me.id!==te.id)))},[x,ye]),He=v.useCallback(te=>{S(ge=>ge.map(me=>me.id===te.id?{...me,status:"pending",error:void 0}:me))},[]),se=v.useCallback(async te=>{const{arrayMove:ge}=await Promise.resolve().then(()=>DS),{active:me,over:pe}=te;if(!pe||me.id===pe.id)return;const be=[...C,...N];let Ue;if(B.length===0)Ue=be;else{const Te=new Map(B.map((Ve,De)=>[String(Ve),De]));Ue=[...be].sort((Ve,De)=>{const qe="hash"in Ve?String(Ve.hash):String(Ve.id),it="hash"in De?String(De.hash):String(De.id);return(Te.get(qe)??be.length)-(Te.get(it)??be.length)})}const Ce=Ue.findIndex(Te=>"hash"in Te?Te.hash===me.id:Te.id===me.id),ze=Ue.findIndex(Te=>"hash"in Te?Te.hash===pe.id:Te.id===pe.id),Se=ge(Ue,Ce,ze);G(Se.map(Te=>"hash"in Te?Te.hash:Te.id));const Re=Se.filter(Te=>"hash"in Te),ot=Se.filter(Te=>!("hash"in Te));U.current=!0,k(Re),S(ot);const Pe=Re.map((Te,Ve)=>({...Te,sort_order:Ve,order:Ve+1}));if(Re.length>0&&f.reorder){const Te=Re.map((Ve,De)=>({id:Ve.id,order:De+1}));try{const Ve=await Cr.api.patch(f.reorder,{order:Te});Cr.toast.success(Ve.message)}catch(Ve){const De=Ve instanceof Error?Ve.message:It("attachment.reorder_failed");Cr.toast.error(De)}}b?.(Pe)},[C,N,B,f.reorder,b]),Oe=v.useMemo(()=>[...C,...N].filter(te=>"is_image"in te?te.is_image:te.mime_type?.startsWith("image/")??!1),[C,N]),Le=v.useCallback(te=>{const ge=Oe.findIndex(me=>"hash"in te&&"hash"in me?me.hash===te.hash:"id"in te&&"id"in me&&!("hash"in te)&&!("hash"in me)?me.id===te.id:!1);ge!==-1&&(ee.current+=1,Z(ge),K(!0))},[Oe]);v.useEffect(()=>{let te=!1;return(async()=>{const me=C.filter(be=>be.is_image),pe=new Set(me.map(be=>be.id));for(const[be,Ue]of J.current)pe.has(be)||(URL.revokeObjectURL(Ue),J.current.delete(be));for(const be of me){if(te)break;if(!J.current.has(be.id))try{const Ue=await Cr.api.get(be.download_url,{responseType:"blob"});if(!te&&Ue){const Ce=URL.createObjectURL(Ue);J.current.set(be.id,Ce),he(new Map(J.current))}}catch(Ue){_p.error("Failed to load authenticated image:",Ue)}}})(),()=>{te=!0}},[C]),v.useEffect(()=>()=>{J.current.forEach(te=>URL.revokeObjectURL(te)),J.current.clear()},[]);const Ie=v.useCallback(async te=>{const{executeImageDownload:ge}=await Promise.resolve().then(()=>Z2);await ge({src:te.download_url,downloadUrl:te.download_url,filename:te.original_filename,downloadRequiresAuth:!0})},[]),Be=v.useMemo(()=>{const te=[...C,...N];if(B.length===0)return te;const ge=new Map(B.map((me,pe)=>[String(me),pe]));return[...te].sort((me,pe)=>{const be="hash"in me?String(me.hash):String(me.id),Ue="hash"in pe?String(pe.hash):String(pe.id),Ce=ge.get(be)??te.length,ze=ge.get(Ue)??te.length;return Ce-ze})},[C,N,B]),st=Be.length,ut=st>0,je=stS([]),[]),Ye=v.useCallback(()=>N,[N]);return{existingFiles:C,pendingFiles:N,isDragOver:w,setIsDragOver:M,isDeleting:j,totalCount:st,hasFiles:ut,canAddMore:je,allItems:Be,imageFiles:Oe,handleFiles:ae,handleRemove:we,handleRetry:He,handleUploadAll:fe,handleOpenGallery:Le,handleDownload:Ie,handleDragEnd:se,inputRef:T,galleryOpen:V,setGalleryOpen:K,galleryStartIndex:X,galleryKeyRef:ee,confirmDialogOpen:D,setConfirmDialogOpen:O,itemToDelete:z,executeRemoveAttachment:ye,authenticatedImageUrls:ne,clear:nt,getPendingFiles:Ye}}const uE=({isDragOver:t,canAddMore:e,onFiles:n,setIsDragOver:r,inputRef:s,accept:o,maxFiles:i,maxSize:a,disabled:l,children:c})=>{const d=e&&!l;return h.jsxs(I,{className:` relative border-2 border-dashed rounded-lg transition-colors ${l?"opacity-60 cursor-not-allowed":""} - ${e&&!l?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 dark:border-gray-600"} - `,onDragOver:u=>{u.preventDefault(),d&&r(!0)},onDragLeave:()=>r(!1),onDrop:u=>{u.preventDefault(),r(!1),d&&n(u.dataTransfer.files)},children:[h.jsx("input",{ref:s,type:"file",multiple:!0,accept:o,onChange:u=>u.target.files&&!l&&n(u.target.files),className:"hidden",disabled:l}),!c&&h.jsxs(I,{className:`p-8 text-center transition-colors ${l?"cursor-not-allowed":"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50"}`,onClick:()=>!l&&s.current?.click(),children:[h.jsx(yt,{className:"fa-solid fa-cloud-arrow-up text-4xl text-gray-400 dark:text-gray-500 mb-4"}),h.jsx(wt,{className:"text-sm text-gray-600 dark:text-gray-400",children:Ft("attachment.drop_or_click")}),h.jsxs(wt,{className:"text-xs text-gray-500 dark:text-gray-500 mt-2",children:[Ft("attachment.upload_limit",{maxFiles:i,maxSize:a}),o&&` (${o})`]})]}),c]})},AE=window.G7Core,DE=window.G7Core?.createLogger?.("Comp:SortableThumbnail")??{log:(...e)=>console.log("[Comp:SortableThumbnail]",...e),warn:(...e)=>console.warn("[Comp:SortableThumbnail]",...e),error:(...e)=>console.error("[Comp:SortableThumbnail]",...e)},IE=({file:e,onRemove:t,onRetry:n,onImageClick:r,onDownload:s,isPrimary:o,onPrimaryClick:i,disabled:a})=>{const{attributes:l,listeners:c,setNodeRef:d,transform:u,transition:f,isDragging:g}=Bi({id:"hash"in e?e.hash:e.id}),m={transform:jn.Transform.toString(u),transition:f,opacity:g?.5:1},p="status"in e&&e.status!==void 0,y=p?e.status:"done",b=y==="uploading",_=y==="compressing",x=y==="error",M=p?e.progress:100,C=p?e.preview:void 0,k=p?e.mime_type?.startsWith("image/")??!1:e.is_image,[N,S]=v.useState(void 0),w=!p&&k?e.download_url:void 0;v.useEffect(()=>{if(!w){S(void 0);return}let D=!0,P;return(async()=>{try{const $=await AE.api.get(w,{responseType:"blob"});D&&$&&(P=URL.createObjectURL($),S(P))}catch($){DE.error("Failed to load authenticated image:",$)}})(),()=>{D=!1,P&&URL.revokeObjectURL(P)}},[w]);const E=N;return h.jsxs(I,{ref:d,style:m,className:"relative group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden",children:[h.jsxs(I,{className:`aspect-square w-full flex items-center justify-center bg-gray-50 dark:bg-gray-900 relative ${k||!p?"cursor-pointer":""}`,onClick:()=>{b||_||(k&&r?r():!p&&s&&s())},children:[C||E?h.jsx(Sn,{src:C||E,alt:e.original_filename,className:"w-full h-full object-cover"}):h.jsx(yt,{className:`fa-solid ${NE(e.mime_type)} text-4xl text-gray-400 dark:text-gray-500`}),(b||_)&&h.jsxs(I,{className:"absolute inset-0 bg-black/50 flex flex-col items-center justify-center",children:[h.jsx(I,{className:"w-3/4 h-2 bg-gray-700 rounded-full overflow-hidden",children:h.jsx(I,{className:"h-full bg-blue-500 transition-all",style:{width:`${M}%`}})}),h.jsxs(Q,{className:"text-white text-xs mt-1",children:[M,"%"]})]}),x&&h.jsx(I,{className:"absolute inset-0 bg-red-500/50 flex items-center justify-center",children:h.jsx(yt,{className:"fa-solid fa-exclamation-triangle text-2xl text-white"})}),i&&h.jsx(ie,{type:"button",onClick:D=>{D.stopPropagation(),i()},className:`absolute top-1.5 left-1.5 z-10 p-1 rounded-full transition-colors ${o?"bg-yellow-500 text-white":"bg-gray-800/50 text-gray-300 hover:bg-yellow-500 hover:text-white opacity-0 group-hover:opacity-100"}`,title:Ft(o?"attachment.primary_image":"attachment.set_as_primary"),children:h.jsx(yt,{className:"fa-solid fa-star text-xs"})}),h.jsxs(I,{className:`absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-start justify-end p-1 gap-1 opacity-0 group-hover:opacity-100 ${a?"hidden":""}`,children:[h.jsx(ie,{type:"button",...l,...c,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-gray-600 dark:text-gray-300 hover:bg-white cursor-grab active:cursor-grabbing",title:Ft("attachment.drag_to_reorder"),onClick:D=>D.stopPropagation(),children:h.jsx(yt,{className:"fa-solid fa-grip-vertical text-sm"})}),!p&&s&&h.jsx(ie,{type:"button",onClick:D=>{D.stopPropagation(),s()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-blue-500 hover:bg-white",title:Ft("common.download"),children:h.jsx(yt,{className:"fa-solid fa-download text-sm"})}),x&&n&&h.jsx(ie,{type:"button",onClick:D=>{D.stopPropagation(),n()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-orange-500 hover:bg-white",title:Ft("common.retry"),children:h.jsx(yt,{className:"fa-solid fa-arrow-rotate-right text-sm"})}),h.jsx(ie,{type:"button",onClick:D=>{D.stopPropagation(),t()},disabled:b||_,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-red-500 hover:bg-white disabled:opacity-50",title:Ft("common.delete"),children:h.jsx(yt,{className:"fa-solid fa-times text-sm"})})]})]}),h.jsxs(I,{className:`p-2 ${!p&&s?"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700":""}`,onClick:()=>{!p&&s&&s()},children:[h.jsx(wt,{className:"text-xs font-medium text-gray-900 dark:text-white truncate",title:e.original_filename,children:e.original_filename}),h.jsx(wt,{className:"text-xs text-gray-500 dark:text-gray-400",children:e.size_formatted})]})]})},LE=({allItems:e,canAddMore:t,totalCount:n,maxFiles:r,maxSize:s,accept:o,onRemove:i,onRetry:a,onImageClick:l,onDownload:c,onDragEnd:d,onAddClick:u,enablePrimarySelection:f,primaryFileId:g,onPrimaryChange:m,disabled:p})=>{const y=Jl(hs(ao,{activationConstraint:{distance:8}}),hs(io,{coordinateGetter:ji}));return h.jsxs(I,{className:"p-3",children:[h.jsx(cc,{sensors:y,collisionDetection:Rl,onDragEnd:d,children:h.jsx(Ui,{items:e.map(b=>"hash"in b?b.hash:b.id),strategy:Vi,children:h.jsxs(I,{className:"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3",children:[e.map(b=>{const _="hash"in b,x=_?b.is_image:b.mime_type?.startsWith("image/")??!1;return h.jsx(IE,{file:b,onRemove:()=>i(b),onRetry:"status"in b&&b.status==="error"?()=>a(b):void 0,onImageClick:x?()=>l(b):void 0,onDownload:_?()=>c(b):void 0,isPrimary:f&&_&&(b.hash||b.id)===g,onPrimaryClick:f&&_?()=>m?.(b.hash||b.id):void 0,disabled:p},_?b.hash:b.id)}),t&&!p&&h.jsxs(I,{className:"aspect-square flex flex-col items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors",onClick:u,children:[h.jsx(yt,{className:"fa-solid fa-plus text-2xl text-gray-400 dark:text-gray-500 mb-1"}),h.jsx(Q,{className:"text-xs text-gray-500 dark:text-gray-400",children:Ft("common.add")})]})]})})}),h.jsxs(I,{className:"mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 flex justify-between items-center text-xs text-gray-500 dark:text-gray-400",children:[h.jsx(Q,{children:Ft("attachment.attached_count",{count:n,max:r})}),h.jsxs(Q,{children:[Ft("attachment.max_size",{size:s}),o&&` (${o})`]})]})]})},zp=v.forwardRef(({uploadTriggerEvent:e,attachmentableType:t,attachmentableId:n,collection:r="default",maxFiles:s=10,maxSize:o=10,maxConcurrentUploads:i=3,accept:a,imageCompression:l,roleIds:c=[],autoUpload:d=!0,onFilesChange:u,onUploadComplete:f,onUploadError:g,onRemove:m,onReorder:p,initialFiles:y=[],disabled:b=!1,className:_="",confirmBeforeRemove:x=!1,confirmRemoveTitle:M,confirmRemoveMessage:C,apiEndpoints:k,uploadParams:N,enablePrimarySelection:S=!1,primaryFileId:w,onPrimaryChange:E,id:D,editorAttrs:P},L)=>{const $={upload:k?.upload??"/api/admin/attachments",delete:k?.delete??"/api/admin/attachments/:id",reorder:k?.reorder??"/api/admin/attachments/reorder"},A=TE({attachmentableType:t,attachmentableId:n,collection:r,maxFiles:s,maxSize:o,maxConcurrentUploads:i,accept:a,imageCompression:l,roleIds:c,autoUpload:d,initialFiles:y,endpoints:$,onFilesChange:u,onUploadComplete:f,onUploadError:g,onRemove:m,onReorder:p,uploadTriggerEvent:e,confirmBeforeRemove:x,uploadParams:N});v.useImperativeHandle(L,()=>({uploadAll:A.handleUploadAll,clear:A.clear,getPendingFiles:A.getPendingFiles}));const O=v.useMemo(()=>A.imageFiles.map(z=>{const H=!("hash"in z),j=z,G=z;return H?{src:j.preview||"",filename:j.original_filename,title:j.original_filename,downloadRequiresAuth:!1}:{src:A.authenticatedImageUrls.get(G.id)||G.download_url,downloadUrl:G.download_url,filename:G.original_filename,title:G.original_filename,downloadRequiresAuth:!0}}),[A.imageFiles,A.authenticatedImageUrls]),T=v.useMemo(()=>({existingFiles:A.existingFiles,pendingFiles:A.pendingFiles,isDragOver:A.isDragOver,isDeleting:A.isDeleting,maxFiles:s,maxSize:o,accept:a,endpoints:$,confirmBeforeRemove:x,totalCount:A.totalCount,hasFiles:A.hasFiles,canAddMore:A.canAddMore,allItems:A.allItems,imageFiles:A.imageFiles,handleFiles:A.handleFiles,handleRemove:A.handleRemove,handleRetry:A.handleRetry,handleUploadAll:A.handleUploadAll,handleOpenGallery:A.handleOpenGallery,handleDownload:A.handleDownload,handleDragEnd:A.handleDragEnd,setIsDragOver:A.setIsDragOver,inputRef:A.inputRef,galleryOpen:A.galleryOpen,setGalleryOpen:A.setGalleryOpen,galleryStartIndex:A.galleryStartIndex,galleryKeyRef:A.galleryKeyRef,confirmDialogOpen:A.confirmDialogOpen,setConfirmDialogOpen:A.setConfirmDialogOpen,itemToDelete:A.itemToDelete,executeRemoveAttachment:A.executeRemoveAttachment,authenticatedImageUrls:A.authenticatedImageUrls}),[A,s,o,a,$,x]);return h.jsx(bE,{value:T,children:h.jsxs(I,{id:D,className:_,...P,children:[h.jsx(OE,{isDragOver:A.isDragOver,canAddMore:A.canAddMore,onFiles:A.handleFiles,setIsDragOver:A.setIsDragOver,inputRef:A.inputRef,accept:a,maxFiles:s,maxSize:o,disabled:b,children:A.hasFiles&&h.jsx(LE,{allItems:A.allItems,canAddMore:A.canAddMore,totalCount:A.totalCount,maxFiles:s,maxSize:o,accept:a,onRemove:A.handleRemove,onRetry:A.handleRetry,onImageClick:A.handleOpenGallery,onDownload:A.handleDownload,onDragEnd:A.handleDragEnd,onAddClick:()=>A.inputRef.current?.click(),enablePrimarySelection:S,primaryFileId:w,onPrimaryChange:E,disabled:b})}),x&&h.jsx(pu,{isOpen:A.confirmDialogOpen,onClose:()=>{A.isDeleting||A.setConfirmDialogOpen(!1)},title:M??Ft("attachment.delete_confirm_title"),message:C??Ft("attachment.delete_confirm_message"),confirmText:Ft("common.delete"),confirmButtonVariant:"danger",onConfirm:()=>{A.itemToDelete&&A.executeRemoveAttachment(A.itemToDelete)},isLoading:A.isDeleting,loadingText:Ft("attachment.deleting")}),O.length>0&&A.galleryOpen&&h.jsx(Ji,{images:O,isOpen:!0,onClose:()=>A.setGalleryOpen(!1),startIndex:A.galleryStartIndex,showDownload:!0},`gallery-${A.galleryKeyRef.current}`)]})})});zp.displayName="FileUploader";const PE=({name:e,value:t,options:n,onChange:r,disabled:s=!1,inline:o=!1,className:i="",size:a="md",label:l,error:c,id:d,editorAttrs:u})=>{const[f,g]=v.useState(()=>t??"");v.useEffect(()=>{g(t??"")},[t]);const m=_=>{const x=_.target.value;g(x),r&&!s&&r(_)},y={sm:{radio:"w-3.5 h-3.5",label:"text-xs",gap:"gap-1.5"},md:{radio:"w-4 h-4",label:"text-sm",gap:"gap-2"},lg:{radio:"w-5 h-5",label:"text-base",gap:"gap-2.5"}}[a],b=o?"flex flex-wrap items-center gap-4":"flex flex-col gap-2";return h.jsxs(I,{className:i,id:d,...u,children:[l&&h.jsx(Q,{className:`block mb-2 font-medium text-gray-700 dark:text-gray-300 ${y.label}`,children:l}),h.jsx(I,{className:b,children:n.map(_=>{const x=String(_.value)===String(f),M=s||_.disabled;return h.jsxs(dn,{className:`inline-flex items-center ${y.gap} ${M?"cursor-not-allowed opacity-50":"cursor-pointer"}`,children:[h.jsx("input",{type:"radio",name:e,value:_.value,checked:x,onChange:m,disabled:M,className:` + ${t&&!l?"border-blue-500 bg-blue-50 dark:bg-blue-900/20":"border-gray-300 dark:border-gray-600"} + `,onDragOver:u=>{u.preventDefault(),d&&r(!0)},onDragLeave:()=>r(!1),onDrop:u=>{u.preventDefault(),r(!1),d&&n(u.dataTransfer.files)},children:[h.jsx("input",{ref:s,type:"file",multiple:!0,accept:o,onChange:u=>u.target.files&&!l&&n(u.target.files),className:"hidden",disabled:l}),!c&&h.jsxs(I,{className:`p-8 text-center transition-colors ${l?"cursor-not-allowed":"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800/50"}`,onClick:()=>!l&&s.current?.click(),children:[h.jsx(mt,{className:"fa-solid fa-cloud-arrow-up text-4xl text-gray-400 dark:text-gray-500 mb-4"}),h.jsx(yt,{className:"text-sm text-gray-600 dark:text-gray-400",children:It("attachment.drop_or_click")}),h.jsxs(yt,{className:"text-xs text-gray-500 dark:text-gray-500 mt-2",children:[It("attachment.upload_limit",{maxFiles:i,maxSize:a}),o&&` (${o})`]})]}),c]})},fE=window.G7Core,hE=window.G7Core?.createLogger?.("Comp:SortableThumbnail")??{log:(...t)=>console.log("[Comp:SortableThumbnail]",...t),warn:(...t)=>console.warn("[Comp:SortableThumbnail]",...t),error:(...t)=>console.error("[Comp:SortableThumbnail]",...t)},gE=({file:t,onRemove:e,onRetry:n,onImageClick:r,onDownload:s,isPrimary:o,onPrimaryClick:i,disabled:a})=>{const{attributes:l,listeners:c,setNodeRef:d,transform:u,transition:f,isDragging:g}=Ii({id:"hash"in t?t.hash:t.id}),m={transform:Pn.Transform.toString(u),transition:f,opacity:g?.5:1},p="status"in t&&t.status!==void 0,y=p?t.status:"done",b=y==="uploading",_=y==="compressing",x=y==="error",E=p?t.progress:100,C=p?t.preview:void 0,k=p?t.mime_type?.startsWith("image/")??!1:t.is_image,[N,S]=v.useState(void 0),w=!p&&k?t.download_url:void 0;v.useEffect(()=>{if(!w){S(void 0);return}let T=!0,P;return(async()=>{try{const $=await fE.api.get(w,{responseType:"blob"});T&&$&&(P=URL.createObjectURL($),S(P))}catch($){hE.error("Failed to load authenticated image:",$)}})(),()=>{T=!1,P&&URL.revokeObjectURL(P)}},[w]);const M=N;return h.jsxs(I,{ref:d,style:m,className:"relative group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden",children:[h.jsxs(I,{className:`aspect-square w-full flex items-center justify-center bg-gray-50 dark:bg-gray-900 relative ${k||!p?"cursor-pointer":""}`,onClick:()=>{b||_||(k&&r?r():!p&&s&&s())},children:[C||M?h.jsx(yn,{src:C||M,alt:t.original_filename,className:"w-full h-full object-cover"}):h.jsx(mt,{className:`fa-solid ${cE(t.mime_type)} text-4xl text-gray-400 dark:text-gray-500`}),(b||_)&&h.jsxs(I,{className:"absolute inset-0 bg-black/50 flex flex-col items-center justify-center",children:[h.jsx(I,{className:"w-3/4 h-2 bg-gray-700 rounded-full overflow-hidden",children:h.jsx(I,{className:"h-full bg-blue-500 transition-all",style:{width:`${E}%`}})}),h.jsxs(Q,{className:"text-white text-xs mt-1",children:[E,"%"]})]}),x&&h.jsx(I,{className:"absolute inset-0 bg-red-500/50 flex items-center justify-center",children:h.jsx(mt,{className:"fa-solid fa-exclamation-triangle text-2xl text-white"})}),i&&h.jsx(oe,{type:"button",onClick:T=>{T.stopPropagation(),i()},className:`absolute top-1.5 left-1.5 z-10 p-1 rounded-full transition-colors ${o?"bg-yellow-500 text-white":"bg-gray-800/50 text-gray-300 hover:bg-yellow-500 hover:text-white opacity-0 group-hover:opacity-100"}`,title:It(o?"attachment.primary_image":"attachment.set_as_primary"),children:h.jsx(mt,{className:"fa-solid fa-star text-xs"})}),h.jsxs(I,{className:`absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-start justify-end p-1 gap-1 opacity-0 group-hover:opacity-100 ${a?"hidden":""}`,children:[h.jsx(oe,{type:"button",...l,...c,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-gray-600 dark:text-gray-300 hover:bg-white cursor-grab active:cursor-grabbing",title:It("attachment.drag_to_reorder"),onClick:T=>T.stopPropagation(),children:h.jsx(mt,{className:"fa-solid fa-grip-vertical text-sm"})}),!p&&s&&h.jsx(oe,{type:"button",onClick:T=>{T.stopPropagation(),s()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-blue-500 hover:bg-white",title:It("common.download"),children:h.jsx(mt,{className:"fa-solid fa-download text-sm"})}),x&&n&&h.jsx(oe,{type:"button",onClick:T=>{T.stopPropagation(),n()},className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-orange-500 hover:bg-white",title:It("common.retry"),children:h.jsx(mt,{className:"fa-solid fa-arrow-rotate-right text-sm"})}),h.jsx(oe,{type:"button",onClick:T=>{T.stopPropagation(),e()},disabled:b||_,className:"p-1.5 bg-white/90 dark:bg-gray-800/90 rounded text-red-500 hover:bg-white disabled:opacity-50",title:It("common.delete"),children:h.jsx(mt,{className:"fa-solid fa-times text-sm"})})]})]}),h.jsxs(I,{className:`p-2 ${!p&&s?"cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700":""}`,onClick:()=>{!p&&s&&s()},children:[h.jsx(yt,{className:"text-xs font-medium text-gray-900 dark:text-white truncate",title:t.original_filename,children:t.original_filename}),h.jsx(yt,{className:"text-xs text-gray-500 dark:text-gray-400",children:t.size_formatted})]})]})},pE=({allItems:t,canAddMore:e,totalCount:n,maxFiles:r,maxSize:s,accept:o,onRemove:i,onRetry:a,onImageClick:l,onDownload:c,onDragEnd:d,onAddClick:u,enablePrimarySelection:f,primaryFileId:g,onPrimaryChange:m,disabled:p})=>{const y=Wl(qr(Xs,{activationConstraint:{distance:8}}),qr(Ys,{coordinateGetter:Li}));return h.jsxs(I,{className:"p-3",children:[h.jsx(tc,{sensors:y,collisionDetection:Gl,onDragEnd:d,children:h.jsx(Di,{items:t.map(b=>"hash"in b?b.hash:b.id),strategy:Ti,children:h.jsxs(I,{className:"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3",children:[t.map(b=>{const _="hash"in b,x=_?b.is_image:b.mime_type?.startsWith("image/")??!1;return h.jsx(gE,{file:b,onRemove:()=>i(b),onRetry:"status"in b&&b.status==="error"?()=>a(b):void 0,onImageClick:x?()=>l(b):void 0,onDownload:_?()=>c(b):void 0,isPrimary:f&&_&&(b.hash||b.id)===g,onPrimaryClick:f&&_?()=>m?.(b.hash||b.id):void 0,disabled:p},_?b.hash:b.id)}),e&&!p&&h.jsxs(I,{className:"aspect-square flex flex-col items-center justify-center border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg cursor-pointer hover:border-gray-400 dark:hover:border-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors",onClick:u,children:[h.jsx(mt,{className:"fa-solid fa-plus text-2xl text-gray-400 dark:text-gray-500 mb-1"}),h.jsx(Q,{className:"text-xs text-gray-500 dark:text-gray-400",children:It("common.add")})]})]})})}),h.jsxs(I,{className:"mt-3 pt-3 border-t border-gray-200 dark:border-gray-700 flex justify-between items-center text-xs text-gray-500 dark:text-gray-400",children:[h.jsx(Q,{children:It("attachment.attached_count",{count:n,max:r})}),h.jsxs(Q,{children:[It("attachment.max_size",{size:s}),o&&` (${o})`]})]})]})},Ep=v.forwardRef(({uploadTriggerEvent:t,attachmentableType:e,attachmentableId:n,collection:r="default",maxFiles:s=10,maxSize:o=10,maxConcurrentUploads:i=3,accept:a,imageCompression:l,roleIds:c=[],autoUpload:d=!0,onFilesChange:u,onUploadComplete:f,onUploadError:g,onRemove:m,onReorder:p,initialFiles:y=[],disabled:b=!1,className:_="",confirmBeforeRemove:x=!1,confirmRemoveTitle:E,confirmRemoveMessage:C,apiEndpoints:k,uploadParams:N,enablePrimarySelection:S=!1,primaryFileId:w,onPrimaryChange:M,id:T,editorAttrs:P},L)=>{const $={upload:k?.upload??"/api/admin/attachments",delete:k?.delete??"/api/admin/attachments/:id",reorder:k?.reorder??"/api/admin/attachments/reorder"},A=dE({attachmentableType:e,attachmentableId:n,collection:r,maxFiles:s,maxSize:o,maxConcurrentUploads:i,accept:a,imageCompression:l,roleIds:c,autoUpload:d,initialFiles:y,endpoints:$,onFilesChange:u,onUploadComplete:f,onUploadError:g,onRemove:m,onReorder:p,uploadTriggerEvent:t,confirmBeforeRemove:x,uploadParams:N});v.useImperativeHandle(L,()=>({uploadAll:A.handleUploadAll,clear:A.clear,getPendingFiles:A.getPendingFiles}));const D=v.useMemo(()=>A.imageFiles.map(z=>{const H=!("hash"in z),j=z,W=z;return H?{src:j.preview||"",filename:j.original_filename,title:j.original_filename,downloadRequiresAuth:!1}:{src:A.authenticatedImageUrls.get(W.id)||W.download_url,downloadUrl:W.download_url,filename:W.original_filename,title:W.original_filename,downloadRequiresAuth:!0}}),[A.imageFiles,A.authenticatedImageUrls]),O=v.useMemo(()=>({existingFiles:A.existingFiles,pendingFiles:A.pendingFiles,isDragOver:A.isDragOver,isDeleting:A.isDeleting,maxFiles:s,maxSize:o,accept:a,endpoints:$,confirmBeforeRemove:x,totalCount:A.totalCount,hasFiles:A.hasFiles,canAddMore:A.canAddMore,allItems:A.allItems,imageFiles:A.imageFiles,handleFiles:A.handleFiles,handleRemove:A.handleRemove,handleRetry:A.handleRetry,handleUploadAll:A.handleUploadAll,handleOpenGallery:A.handleOpenGallery,handleDownload:A.handleDownload,handleDragEnd:A.handleDragEnd,setIsDragOver:A.setIsDragOver,inputRef:A.inputRef,galleryOpen:A.galleryOpen,setGalleryOpen:A.setGalleryOpen,galleryStartIndex:A.galleryStartIndex,galleryKeyRef:A.galleryKeyRef,confirmDialogOpen:A.confirmDialogOpen,setConfirmDialogOpen:A.setConfirmDialogOpen,itemToDelete:A.itemToDelete,executeRemoveAttachment:A.executeRemoveAttachment,authenticatedImageUrls:A.authenticatedImageUrls}),[A,s,o,a,$,x]);return h.jsx(J2,{value:O,children:h.jsxs(I,{id:T,className:_,...P,children:[h.jsx(uE,{isDragOver:A.isDragOver,canAddMore:A.canAddMore,onFiles:A.handleFiles,setIsDragOver:A.setIsDragOver,inputRef:A.inputRef,accept:a,maxFiles:s,maxSize:o,disabled:b,children:A.hasFiles&&h.jsx(pE,{allItems:A.allItems,canAddMore:A.canAddMore,totalCount:A.totalCount,maxFiles:s,maxSize:o,accept:a,onRemove:A.handleRemove,onRetry:A.handleRetry,onImageClick:A.handleOpenGallery,onDownload:A.handleDownload,onDragEnd:A.handleDragEnd,onAddClick:()=>A.inputRef.current?.click(),enablePrimarySelection:S,primaryFileId:w,onPrimaryChange:M,disabled:b})}),x&&h.jsx(ru,{isOpen:A.confirmDialogOpen,onClose:()=>{A.isDeleting||A.setConfirmDialogOpen(!1)},title:E??It("attachment.delete_confirm_title"),message:C??It("attachment.delete_confirm_message"),confirmText:It("common.delete"),confirmButtonVariant:"danger",onConfirm:()=>{A.itemToDelete&&A.executeRemoveAttachment(A.itemToDelete)},isLoading:A.isDeleting,loadingText:It("attachment.deleting")}),D.length>0&&A.galleryOpen&&h.jsx(ji,{images:D,isOpen:!0,onClose:()=>A.setGalleryOpen(!1),startIndex:A.galleryStartIndex,showDownload:!0},`gallery-${A.galleryKeyRef.current}`)]})})});Ep.displayName="FileUploader";const mE=({name:t,value:e,options:n,onChange:r,disabled:s=!1,inline:o=!1,className:i="",size:a="md",label:l,error:c,id:d,editorAttrs:u})=>{const[f,g]=v.useState(()=>e??"");v.useEffect(()=>{g(e??"")},[e]);const m=_=>{const x=_.target.value;g(x),r&&!s&&r(_)},y={sm:{radio:"w-3.5 h-3.5",label:"text-xs",gap:"gap-1.5"},md:{radio:"w-4 h-4",label:"text-sm",gap:"gap-2"},lg:{radio:"w-5 h-5",label:"text-base",gap:"gap-2.5"}}[a],b=o?"flex flex-wrap items-center gap-4":"flex flex-col gap-2";return h.jsxs(I,{className:i,id:d,...u,children:[l&&h.jsx(Q,{className:`block mb-2 font-medium text-gray-700 dark:text-gray-300 ${y.label}`,children:l}),h.jsx(I,{className:b,children:n.map(_=>{const x=String(_.value)===String(f),E=s||_.disabled;return h.jsxs(sn,{className:`inline-flex items-center ${y.gap} ${E?"cursor-not-allowed opacity-50":"cursor-pointer"}`,children:[h.jsx("input",{type:"radio",name:t,value:_.value,checked:x,onChange:m,disabled:E,className:` ${y.radio} text-blue-600 bg-gray-100 dark:bg-gray-700 @@ -233,15 +232,15 @@ self.addEventListener('message', async (e) => { focus:ring-blue-500 dark:focus:ring-blue-600 focus:ring-2 dark:ring-offset-gray-800 - ${M?"cursor-not-allowed":"cursor-pointer"} + ${E?"cursor-not-allowed":"cursor-pointer"} `}),h.jsx(Q,{className:` ${y.label} font-medium text-gray-900 dark:text-gray-300 - `,children:_.label})]},_.value)})}),c&&h.jsx(Q,{className:"mt-1 text-xs text-red-600 dark:text-red-400",children:c})]})},_s=()=>window.G7Core,oa=(e,t)=>_s()?.t?.(e,t)??e,$E=()=>_s()?.locale?.supported?.()??["ko","en"],FE=()=>_s()?.locale?.current?.()??"ko",Vp=e=>{const t=oa(`common.language_${e}`);return t===`common.language_${e}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[e]||e.toUpperCase():t},zE=({content:e="",value:t,onChange:n,multilingual:r=!1,isHtml:s=!1,onIsHtmlChange:o,rows:i=15,placeholder:a="",label:l,showHtmlModeToggle:c=!0,contentClassName:d="",purifyConfig:u,className:f="",name:g="content",htmlFieldName:m="content_mode",readOnly:p=!1,id:y,editorAttrs:b})=>{const _=v.useMemo(()=>$E(),[]),x=v.useMemo(()=>FE(),[]),[M,C]=v.useState(x),[k,N]=v.useState(s),[S,w]=v.useState(e),[E,D]=v.useState(t??{}),[P,L]=v.useState(!1);v.useEffect(()=>{N(s)},[s]),v.useEffect(()=>{r||w(e)},[e,r]),v.useEffect(()=>{r&&t&&D(t)},[t,r]);const $=v.useCallback(G=>{const U=G.target.value;if(w(U),n){const W=_s()?.createChangeEvent?.({value:U,name:g,type:"textarea"})??{target:{name:g,value:U}};n(W)}},[n,g]),A=v.useCallback((G,U)=>{const W={...E,[G]:U};if(D(W),n){const Y=_s()?.createChangeEvent?.({value:W,name:g,type:"multilingual"})??{target:{name:g,value:W}};n(Y)}},[n,g,E]),O=v.useCallback(G=>{const U=G.target.checked;if(N(U),U||L(!1),o){const W=_s()?.createChangeEvent?.({checked:U,name:m,type:"checkbox"})??{target:{name:m,checked:U}};o(W)}},[o,m]),T=v.useCallback(()=>{L(G=>!G)},[]),z=r?E[M]??"":S,H=v.useCallback(G=>!!E[G]?.trim(),[E]),j=()=>h.jsx(I,{className:"flex flex-wrap items-center gap-2 mb-3",children:_.map(G=>{const U=G===x,W=M===G;return h.jsxs(ie,{type:"button",className:` + `,children:_.label})]},_.value)})}),c&&h.jsx(Q,{className:"mt-1 text-xs text-red-600 dark:text-red-400",children:c})]})},as=()=>window.G7Core,Zi=(t,e)=>as()?.t?.(t,e)??t,bE=()=>as()?.locale?.supported?.()??["ko","en"],yE=()=>as()?.locale?.current?.()??"ko",Mp=t=>{const e=Zi(`common.language_${t}`);return e===`common.language_${t}`?{ko:"한국어",en:"English",ja:"日本語",zh:"中文"}[t]||t.toUpperCase():e},vE=({content:t="",value:e,onChange:n,multilingual:r=!1,isHtml:s=!1,onIsHtmlChange:o,rows:i=15,placeholder:a="",label:l,showHtmlModeToggle:c=!0,contentClassName:d="",purifyConfig:u,className:f="",name:g="content",htmlFieldName:m="content_mode",readOnly:p=!1,id:y,editorAttrs:b})=>{const _=v.useMemo(()=>bE(),[]),x=v.useMemo(()=>yE(),[]),[E,C]=v.useState(x),[k,N]=v.useState(s),[S,w]=v.useState(t),[M,T]=v.useState(e??{}),[P,L]=v.useState(!1);v.useEffect(()=>{N(s)},[s]),v.useEffect(()=>{r||w(t)},[t,r]),v.useEffect(()=>{r&&e&&T(e)},[e,r]);const $=v.useCallback(W=>{const U=W.target.value;if(w(U),n){const B=as()?.createChangeEvent?.({value:U,name:g,type:"textarea"})??{target:{name:g,value:U}};n(B)}},[n,g]),A=v.useCallback((W,U)=>{const B={...M,[W]:U};if(T(B),n){const G=as()?.createChangeEvent?.({value:B,name:g,type:"multilingual"})??{target:{name:g,value:B}};n(G)}},[n,g,M]),D=v.useCallback(W=>{const U=W.target.checked;if(N(U),U||L(!1),o){const B=as()?.createChangeEvent?.({checked:U,name:m,type:"checkbox"})??{target:{name:m,checked:U}};o(B)}},[o,m]),O=v.useCallback(()=>{L(W=>!W)},[]),z=r?M[E]??"":S,H=v.useCallback(W=>!!M[W]?.trim(),[M]),j=()=>h.jsx(I,{className:"flex flex-wrap items-center gap-2 mb-3",children:_.map(W=>{const U=W===x,B=E===W;return h.jsxs(oe,{type:"button",className:` inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold transition-all - ${W?U?"bg-blue-500 text-white dark:bg-blue-600 dark:text-white shadow-md scale-105":"bg-gray-500 text-white dark:bg-gray-600 dark:text-white shadow-md scale-105":U?"bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} - `,onClick:()=>C(G),children:[h.jsx(se,{name:"globe",className:"w-3 h-3"}),h.jsx(Q,{children:Vp(G)}),U&&h.jsx(Q,{className:"text-red-500",children:"*"}),H(G)&&!W&&h.jsx(se,{name:"check",className:"w-3 h-3 text-green-500 dark:text-green-400"})]},G)})});return h.jsxs(I,{className:`space-y-2 ${f}`,id:y,...b,children:[h.jsxs(I,{className:"flex items-center justify-between",children:[l&&h.jsx(dn,{className:"block text-sm font-medium text-gray-500 dark:text-gray-400",children:l}),h.jsxs(I,{className:"flex items-center gap-3",children:[k&&!p&&h.jsx(ie,{type:"button",onClick:T,className:`px-3 py-1.5 text-xs font-bold rounded-lg focus:outline-none focus:ring-2 ${P?"text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-500 focus:ring-gray-400 dark:focus:ring-gray-500":"text-blue-600 dark:text-blue-400 bg-white dark:bg-gray-700 border border-blue-300 dark:border-blue-600 hover:bg-blue-50 dark:hover:bg-gray-600 focus:ring-blue-500 dark:focus:ring-blue-600"}`,children:oa(P?"common.preview_off":"common.preview")}),c&&h.jsxs(dn,{className:"flex items-center gap-2 p-2 cursor-pointer",children:[h.jsx(_t,{type:"checkbox",name:m,checked:k,onChange:O,disabled:p,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(I,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:oa("common.html_mode")})]})]})]}),r&&j(),!P&&(r?_.map(G=>h.jsx(I,{className:M===G?"block":"hidden",children:h.jsx(ir,{name:`${g}[${G}]`,value:E[G]??"",onChange:U=>A(G,U.target.value),placeholder:`${a} (${Vp(G)})`,rows:i,readOnly:p,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${k?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`})},G)):h.jsx(ir,{name:g,value:S,onChange:$,placeholder:a,rows:i,readOnly:p,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${k?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`})),P&&h.jsx(I,{className:"p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 min-h-[400px]",children:h.jsx(il,{content:z,isHtml:!0,className:d||"prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100",purifyConfig:u})})]})},Hp="g7_dismissed_warnings";function Up(){try{const e=sessionStorage.getItem(Hp);return e?JSON.parse(e):[]}catch{return[]}}function VE(e){try{const t=Up();t.includes(e)||(t.push(e),sessionStorage.setItem(Hp,JSON.stringify(t)))}catch{}}function HE(e){switch(e){case"error":return"error";case"warning":return"warning";default:return"info"}}const UE=({warnings:e,className:t=""})=>{const[n,r]=v.useState([]);v.useEffect(()=>{r(Up())},[]);const s=v.useCallback(i=>{VE(i),r(a=>[...a,i])},[]);if(!e||e.length===0)return null;const o=e.filter(i=>!n.includes(i.id));return o.length===0?null:h.jsx(I,{className:`space-y-2 ${t}`.trim(),children:o.map(i=>h.jsx(wu,{type:HE(i.level),message:i.message,dismissible:!0,onDismiss:()=>s(i.id)},i.id))})},Bp=window.G7Core?.createLogger?.("Comp:FilterVisibility")??{log:(...e)=>console.log("[Comp:FilterVisibility]",...e),warn:(...e)=>console.warn("[Comp:FilterVisibility]",...e),error:(...e)=>console.error("[Comp:FilterVisibility]",...e)},BE=({id:e,visibleFilters:t,defaultFilters:n=[],onFilterVisibilityChange:r})=>{const[s,o]=v.useState(!1),i=v.useRef(!0),a=v.useMemo(()=>{try{const f=window.G7Core?.AuthManager?.getInstance();return f?f.getUser()?.uuid:void 0}catch{return}},[]),l=v.useMemo(()=>`g7_filters_${e}${a!==void 0?`_${a}`:""}`,[e,a]),c=v.useCallback(()=>{try{const f=localStorage.getItem(l);if(f){const g=JSON.parse(f);if(Array.isArray(g))return g}}catch(f){Bp.warn("Failed to load from localStorage:",f)}return null},[l]),d=v.useCallback(f=>{try{localStorage.setItem(l,JSON.stringify(f))}catch(g){Bp.warn("Failed to save to localStorage:",g)}},[l]),u=v.useCallback(()=>{const f=c();return f||(t&&t.length>0?t:n)},[c,t,n]);return v.useEffect(()=>{if(!s){const f=u();r&&f.length>0&&r(f),o(!0)}},[s,r,u]),v.useEffect(()=>{if(s&&t&&t.length>0){if(i.current){i.current=!1;return}d(t)}},[s,t,d]),null},jE=window.G7Core?.createLogger?.("Comp:SlotContainer")??{log:(...e)=>console.log("[Comp:SlotContainer]",...e),warn:(...e)=>console.warn("[Comp:SlotContainer]",...e),error:(...e)=>console.error("[Comp:SlotContainer]",...e)};function WE(e,t){return t&&e&&typeof e.id=="string"&&e.id.length>0?{...e,id:`${e.id}__${t}`}:e}const GE=({slotId:e,className:t="",emptyContent:n,style:r,id:s,children:o})=>{const[i,a]=v.useState([]),[,l]=v.useState({}),c=v.useCallback(()=>{const m=window.__slotContextValue;if(m)return m;const p=window.G7Core;return p&&p.getSlotContext?.()||null},[]),d=v.useCallback(()=>{const m=c();m&&m.isEnabled&&a(m.getSlotComponents(e))},[e,c]);v.useEffect(()=>{const m=c();return!m||!m.isEnabled?void 0:(d(),m.subscribeToSlot(e,()=>{d(),l({})}))},[e,c,d]);const u=v.useCallback(()=>{const m=window.G7Core;return m?m.getDynamicRenderer?.():null},[]);if(i.length===0)return n?h.jsx(I,{className:t,style:r,id:s,children:n}):o?h.jsx(I,{className:t,style:r,id:s,children:o}):null;const f=u(),g=window.G7Core;return!f||!g?(jE.error("G7Core or DynamicRenderer not available"),null):h.jsxs(I,{className:t,style:r,id:s,children:[i.map(m=>{const{componentDef:p,dataContext:y,parentFormContext:b,getParentComponentContext:_,translationContext:x}=m,M=WE(p,s);return h.jsx(f,{componentDef:M,dataContext:y,translationContext:x,registry:g.getComponentRegistry(),bindingEngine:g.getDataBindingEngine(),translationEngine:g.getTranslationEngine(),actionDispatcher:g.getActionDispatcher(),parentComponentContext:_?.(),parentFormContextProp:b,isRootRenderer:!1},M.id||`slot-${e}-${m.order}`)}),o]})},jp=(e,t)=>window.G7Core?.t?.(e,t)??e,Wp=(e,t,n)=>window.G7Core?.renderComponentLayout?.(e,t,n)??null,YE=({options:e=[],value:t,onChange:n,placeholder:r,disabled:s=!1,className:o="",maxHeight:i="320px",children:a,selectedChildren:l,__componentLayoutDefs:c,id:d,editorAttrs:u})=>{const[f,g]=v.useState(!1),m=v.useRef(null),p=v.useRef(null),y=v.useMemo(()=>t==null?null:e.find(k=>String(k.value)===String(t))||null,[e,t]);v.useEffect(()=>{const k=N=>{m.current&&!m.current.contains(N.target)&&g(!1)};return f&&document.addEventListener("mousedown",k),()=>{document.removeEventListener("mousedown",k)}},[f]),v.useEffect(()=>{const k=N=>{N.key==="Escape"&&f&&(g(!1),p.current?.focus())};return document.addEventListener("keydown",k),()=>document.removeEventListener("keydown",k)},[f]);const b=v.useCallback(()=>{s||g(k=>!k)},[s]),_=v.useCallback(k=>{n&&n({target:{value:k},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),p.current?.focus()},[n]),x=r??jp("common.select"),M=()=>{if(!y)return h.jsx(Q,{className:"text-gray-400 dark:text-gray-500",children:x});if(c?.selected){const k=Wp(c.selected,{item:y,isSelected:!0},"selected");if(k)return k}return l||h.jsx(Q,{className:"truncate",children:y.label})},C=(k,N)=>h.jsxs(I,{className:"flex items-center justify-between w-full",children:[h.jsx(Q,{className:N?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-900 dark:text-white",children:k.label}),N&&h.jsx(Qn,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]});return h.jsxs(I,{ref:m,className:`relative ${o}`,id:d,...u,children:[h.jsxs(ie,{ref:p,type:"button",onClick:b,disabled:s,className:` + ${B?U?"bg-blue-500 text-white dark:bg-blue-600 dark:text-white shadow-md scale-105":"bg-gray-500 text-white dark:bg-gray-600 dark:text-white shadow-md scale-105":U?"bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400 hover:bg-blue-100 dark:hover:bg-blue-900/50":"bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600"} + `,onClick:()=>C(W),children:[h.jsx(re,{name:"globe",className:"w-3 h-3"}),h.jsx(Q,{children:Mp(W)}),U&&h.jsx(Q,{className:"text-red-500",children:"*"}),H(W)&&!B&&h.jsx(re,{name:"check",className:"w-3 h-3 text-green-500 dark:text-green-400"})]},W)})});return h.jsxs(I,{className:`space-y-2 ${f}`,id:y,...b,children:[h.jsxs(I,{className:"flex items-center justify-between",children:[l&&h.jsx(sn,{className:"block text-sm font-medium text-gray-500 dark:text-gray-400",children:l}),h.jsxs(I,{className:"flex items-center gap-3",children:[k&&!p&&h.jsx(oe,{type:"button",onClick:O,className:`px-3 py-1.5 text-xs font-bold rounded-lg focus:outline-none focus:ring-2 ${P?"text-gray-700 dark:text-gray-200 bg-gray-200 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-500 focus:ring-gray-400 dark:focus:ring-gray-500":"text-blue-600 dark:text-blue-400 bg-white dark:bg-gray-700 border border-blue-300 dark:border-blue-600 hover:bg-blue-50 dark:hover:bg-gray-600 focus:ring-blue-500 dark:focus:ring-blue-600"}`,children:Zi(P?"common.preview_off":"common.preview")}),c&&h.jsxs(sn,{className:"flex items-center gap-2 p-2 cursor-pointer",children:[h.jsx(kt,{type:"checkbox",name:m,checked:k,onChange:D,disabled:p,className:"w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}),h.jsx(I,{className:"text-sm font-medium text-gray-700 dark:text-gray-300",children:Zi("common.html_mode")})]})]})]}),r&&j(),!P&&(r?_.map(W=>h.jsx(I,{className:E===W?"block":"hidden",children:h.jsx(Qn,{name:`${g}[${W}]`,value:M[W]??"",onChange:U=>A(W,U.target.value),placeholder:`${a} (${Mp(W)})`,rows:i,readOnly:p,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${k?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`})},W)):h.jsx(Qn,{name:g,value:S,onChange:$,placeholder:a,rows:i,readOnly:p,className:`block w-full rounded-lg border px-3 py-2 text-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-1 disabled:opacity-50 disabled:cursor-not-allowed ${k?"font-mono bg-white dark:bg-gray-800 border-blue-300 dark:border-blue-600 text-gray-800 dark:text-gray-200 focus:border-blue-500 focus:ring-blue-500":"bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:border-blue-500 focus:ring-blue-500"}`})),P&&h.jsx(I,{className:"p-4 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 min-h-[400px]",children:h.jsx(Za,{content:z,isHtml:!0,className:d||"prose dark:prose-invert max-w-none text-gray-900 dark:text-gray-100",purifyConfig:u})})]})},Np="g7_dismissed_warnings";function Op(){try{const t=sessionStorage.getItem(Np);return t?JSON.parse(t):[]}catch{return[]}}function xE(t){try{const e=Op();e.includes(t)||(e.push(t),sessionStorage.setItem(Np,JSON.stringify(e)))}catch{}}function wE(t){switch(t){case"error":return"error";case"warning":return"warning";default:return"info"}}const kE=({warnings:t,className:e=""})=>{const[n,r]=v.useState([]);v.useEffect(()=>{r(Op())},[]);const s=v.useCallback(i=>{xE(i),r(a=>[...a,i])},[]);if(!t||t.length===0)return null;const o=t.filter(i=>!n.includes(i.id));return o.length===0?null:h.jsx(I,{className:`space-y-2 ${e}`.trim(),children:o.map(i=>h.jsx(cu,{type:wE(i.level),message:i.message,dismissible:!0,onDismiss:()=>s(i.id)},i.id))})},Tp=window.G7Core?.createLogger?.("Comp:FilterVisibility")??{log:(...t)=>console.log("[Comp:FilterVisibility]",...t),warn:(...t)=>console.warn("[Comp:FilterVisibility]",...t),error:(...t)=>console.error("[Comp:FilterVisibility]",...t)},CE=({id:t,visibleFilters:e,defaultFilters:n=[],onFilterVisibilityChange:r})=>{const[s,o]=v.useState(!1),i=v.useRef(!0),a=v.useMemo(()=>{try{const f=window.G7Core?.AuthManager?.getInstance();return f?f.getUser()?.uuid:void 0}catch{return}},[]),l=v.useMemo(()=>`g7_filters_${t}${a!==void 0?`_${a}`:""}`,[t,a]),c=v.useCallback(()=>{try{const f=localStorage.getItem(l);if(f){const g=JSON.parse(f);if(Array.isArray(g))return g}}catch(f){Tp.warn("Failed to load from localStorage:",f)}return null},[l]),d=v.useCallback(f=>{try{localStorage.setItem(l,JSON.stringify(f))}catch(g){Tp.warn("Failed to save to localStorage:",g)}},[l]),u=v.useCallback(()=>{const f=c();return f||(e&&e.length>0?e:n)},[c,e,n]);return v.useEffect(()=>{if(!s){const f=u();r&&f.length>0&&r(f),o(!0)}},[s,r,u]),v.useEffect(()=>{if(s&&e&&e.length>0){if(i.current){i.current=!1;return}d(e)}},[s,e,d]),null},SE=window.G7Core?.createLogger?.("Comp:SlotContainer")??{log:(...t)=>console.log("[Comp:SlotContainer]",...t),warn:(...t)=>console.warn("[Comp:SlotContainer]",...t),error:(...t)=>console.error("[Comp:SlotContainer]",...t)};function _E(t,e){return e&&t&&typeof t.id=="string"&&t.id.length>0?{...t,id:`${t.id}__${e}`}:t}const EE=({slotId:t,className:e="",emptyContent:n,style:r,id:s,children:o})=>{const[i,a]=v.useState([]),[,l]=v.useState({}),c=v.useCallback(()=>{const m=window.__slotContextValue;if(m)return m;const p=window.G7Core;return p&&p.getSlotContext?.()||null},[]),d=v.useCallback(()=>{const m=c();m&&m.isEnabled&&a(m.getSlotComponents(t))},[t,c]);v.useEffect(()=>{const m=c();return!m||!m.isEnabled?void 0:(d(),m.subscribeToSlot(t,()=>{d(),l({})}))},[t,c,d]);const u=v.useCallback(()=>{const m=window.G7Core;return m?m.getDynamicRenderer?.():null},[]);if(i.length===0)return n?h.jsx(I,{className:e,style:r,id:s,children:n}):o?h.jsx(I,{className:e,style:r,id:s,children:o}):null;const f=u(),g=window.G7Core;return!f||!g?(SE.error("G7Core or DynamicRenderer not available"),null):h.jsxs(I,{className:e,style:r,id:s,children:[i.map(m=>{const{componentDef:p,dataContext:y,parentFormContext:b,getParentComponentContext:_,translationContext:x}=m,E=_E(p,s);return h.jsx(f,{componentDef:E,dataContext:y,translationContext:x,registry:g.getComponentRegistry(),bindingEngine:g.getDataBindingEngine(),translationEngine:g.getTranslationEngine(),actionDispatcher:g.getActionDispatcher(),parentComponentContext:_?.(),parentFormContextProp:b,isRootRenderer:!1},E.id||`slot-${t}-${m.order}`)}),o]})},Ap=(t,e)=>window.G7Core?.t?.(t,e)??t,Dp=(t,e,n)=>window.G7Core?.renderComponentLayout?.(t,e,n)??null,ME=({options:t=[],value:e,onChange:n,placeholder:r,disabled:s=!1,className:o="",maxHeight:i="320px",children:a,selectedChildren:l,__componentLayoutDefs:c,id:d,editorAttrs:u})=>{const[f,g]=v.useState(!1),m=v.useRef(null),p=v.useRef(null),y=v.useMemo(()=>e==null?null:t.find(k=>String(k.value)===String(e))||null,[t,e]);v.useEffect(()=>{const k=N=>{m.current&&!m.current.contains(N.target)&&g(!1)};return f&&document.addEventListener("mousedown",k),()=>{document.removeEventListener("mousedown",k)}},[f]),v.useEffect(()=>{const k=N=>{N.key==="Escape"&&f&&(g(!1),p.current?.focus())};return document.addEventListener("keydown",k),()=>document.removeEventListener("keydown",k)},[f]);const b=v.useCallback(()=>{s||g(k=>!k)},[s]),_=v.useCallback(k=>{n&&n({target:{value:k},preventDefault:()=>{},stopPropagation:()=>{},type:"change"}),g(!1),p.current?.focus()},[n]),x=r??Ap("common.select"),E=()=>{if(!y)return h.jsx(Q,{className:"text-gray-400 dark:text-gray-500",children:x});if(c?.selected){const k=Dp(c.selected,{item:y,isSelected:!0},"selected");if(k)return k}return l||h.jsx(Q,{className:"truncate",children:y.label})},C=(k,N)=>h.jsxs(I,{className:"flex items-center justify-between w-full",children:[h.jsx(Q,{className:N?"text-blue-600 dark:text-blue-400 font-medium":"text-gray-900 dark:text-white",children:k.label}),N&&h.jsx(Un,{className:"w-5 h-5 text-blue-600 dark:text-blue-400",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M5 13l4 4L19 7"})})]});return h.jsxs(I,{ref:m,className:`relative ${o}`,id:d,...u,children:[h.jsxs(oe,{ref:p,type:"button",onClick:b,disabled:s,className:` w-full px-4 py-2.5 bg-gray-100 dark:bg-gray-700 border-0 rounded-xl @@ -250,12 +249,13 @@ self.addEventListener('message', async (e) => { flex items-center justify-between gap-2 text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed - `,"aria-haspopup":"listbox","aria-expanded":f,children:[h.jsx(I,{className:"flex-1 min-w-0",children:M()}),h.jsx(Qn,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),f&&h.jsx(I,{className:"absolute z-50 w-full mt-2 bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",role:"listbox",children:h.jsx(I,{className:"overflow-auto",style:{maxHeight:i},children:e.length===0?h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:jp("common.no_options")}):e.map((k,N)=>{const S=String(k.value)===String(t);return h.jsx(ie,{type:"button",onClick:()=>!k.disabled&&_(k.value),disabled:k.disabled,className:` + `,"aria-haspopup":"listbox","aria-expanded":f,children:[h.jsx(I,{className:"flex-1 min-w-0",children:E()}),h.jsx(Un,{className:`w-4 h-4 flex-shrink-0 transition-transform duration-200 ${f?"rotate-180":""}`,fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:h.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),f&&h.jsx(I,{className:"absolute z-50 w-full mt-2 bg-white dark:bg-gray-800 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-600 overflow-hidden",role:"listbox",children:h.jsx(I,{className:"overflow-auto",style:{maxHeight:i},children:t.length===0?h.jsx(I,{className:"px-4 py-6 text-center text-gray-500 dark:text-gray-400",children:Ap("common.no_options")}):t.map((k,N)=>{const S=String(k.value)===String(e);return h.jsx(oe,{type:"button",onClick:()=>!k.disabled&&_(k.value),disabled:k.disabled,className:` w-full px-4 py-3 text-left text-sm hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${S?"bg-blue-50 dark:bg-blue-900/20":""} ${k.disabled?"opacity-50 cursor-not-allowed":"cursor-pointer"} - `,role:"option","aria-selected":S,"data-item-value":k.value,"data-item-index":N,"data-item-selected":S,children:c?.item?Wp(c.item,{item:k,index:N,isSelected:S},`item-${N}`):a||C(k,S)},k.value)})})})]})};function XE(e){const t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null}const KE=({value:e,checked:t=!1,icon:n,label:r,variant:s="blue",customColor:o,className:i="",style:a,onChange:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(!1),g="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium border cursor-pointer transition-all";if(o){const b=XE(o),_=document.documentElement.classList.contains("dark");let x;return t?x={backgroundColor:o,color:"#ffffff",borderColor:o,opacity:u?.85:1}:x=b?{backgroundColor:`rgba(${b.r}, ${b.g}, ${b.b}, ${u?_?.25:.12:_?.15:.06})`,color:o,borderColor:`rgba(${b.r}, ${b.g}, ${b.b}, ${_?.3:.2})`}:{},h.jsxs(dn,{className:`${g} ${i}`,style:{...x,...a},onMouseEnter:()=>f(!0),onMouseLeave:()=>f(!1),id:c,...d,children:[h.jsx(_t,{type:"checkbox",value:e,checked:t,onChange:M=>{l?.(M)},className:"hidden"}),n&&h.jsx(se,{name:n,className:"w-4 h-4"}),h.jsx(I,{children:r})]})}const y=t?{green:"bg-green-600 dark:bg-green-600 text-white dark:text-white border-green-700 dark:border-green-500 hover:bg-green-700 dark:hover:bg-green-500",orange:"bg-orange-600 dark:bg-orange-600 text-white dark:text-white border-orange-700 dark:border-orange-500 hover:bg-orange-700 dark:hover:bg-orange-500",yellow:"bg-yellow-600 dark:bg-yellow-600 text-white dark:text-white border-yellow-700 dark:border-yellow-500 hover:bg-yellow-700 dark:hover:bg-yellow-500",gray:"bg-gray-600 dark:bg-gray-500 text-white dark:text-white border-gray-700 dark:border-gray-400 hover:bg-gray-700 dark:hover:bg-gray-400",blue:"bg-blue-600 dark:bg-blue-600 text-white dark:text-white border-blue-700 dark:border-blue-500 hover:bg-blue-700 dark:hover:bg-blue-500"}[s]:{green:"bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 border-green-200 dark:border-green-800 hover:bg-green-100 dark:hover:bg-green-900/30",orange:"bg-orange-50 dark:bg-orange-900/20 text-orange-600 dark:text-orange-400 border-orange-200 dark:border-orange-800 hover:bg-orange-100 dark:hover:bg-orange-900/30",yellow:"bg-yellow-50 dark:bg-yellow-900/20 text-yellow-600 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800 hover:bg-yellow-100 dark:hover:bg-yellow-900/30",gray:"bg-gray-50 dark:bg-gray-700 text-gray-600 dark:text-gray-300 border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600",blue:"bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800 hover:bg-blue-100 dark:hover:bg-blue-900/30"}[s];return h.jsxs(dn,{className:`${g} ${y} ${i}`,style:a,id:c,...d,children:[h.jsx(_t,{type:"checkbox",value:e,checked:t,onChange:b=>{l?.(b)},className:"hidden"}),n&&h.jsx(se,{name:n,className:"w-4 h-4"}),h.jsx(I,{children:r})]})},qE=({label:e,icon:t,iconPosition:n="left",position:r="right",className:s="",style:o,children:i})=>{const[a,l]=v.useState(!1),[c,d]=v.useState({top:0,left:0}),u=v.useRef(null),f=v.useRef(null),g=v.useCallback(()=>{if(!u.current)return;const p=u.current.getBoundingClientRect(),y=224;let b=r==="right"?p.right-y:p.left;const _=p.bottom+8;b<8&&(b=8),b+y>window.innerWidth-8&&(b=window.innerWidth-y-8),d({top:_,left:b})},[r]);v.useEffect(()=>{a&&g()},[a,g]),v.useEffect(()=>{if(!a)return;const p=()=>{l(!1)};return window.addEventListener("scroll",p,!0),window.addEventListener("resize",p),()=>{window.removeEventListener("scroll",p,!0),window.removeEventListener("resize",p)}},[a]),v.useEffect(()=>{const p=y=>{const b=y.target,_=u.current?.contains(b),x=f.current?.contains(b);!_&&!x&&l(!1)};return a&&document.addEventListener("mousedown",p),()=>{document.removeEventListener("mousedown",p)}},[a]);const m=a?$n.createPortal(h.jsx("div",{ref:f,className:"fixed z-[9999] w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden",style:{top:c.top,left:c.left},onClick:()=>l(!1),children:i}),document.body):null;return h.jsxs(I,{className:`relative inline-block ${s}`,style:o,children:[h.jsxs(ie,{ref:u,onClick:()=>l(!a),className:"flex items-center gap-2",children:[n==="left"&&t&&h.jsx(se,{name:t,className:"w-4 h-4"}),e&&h.jsx(Q,{className:"text-sm font-medium",children:e}),n==="right"&&t&&h.jsx(se,{name:t,className:"w-4 h-4"})]}),m]})},ZE=({label:e,icon:t,variant:n="default",disabled:r=!1,divider:s=!1,className:o="",style:i,onClick:a})=>{if(s)return h.jsx(I,{className:"h-px bg-gray-200 dark:bg-gray-700 my-1",style:i});const l=n==="danger"?"text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700",c=r?"opacity-50 cursor-not-allowed":"cursor-pointer",d=()=>{!r&&a&&a()};return h.jsxs(I,{onClick:d,className:`flex items-center gap-3 px-4 py-3 transition-colors ${l} ${c} ${o}`,style:i,children:[t&&h.jsx(se,{name:t,className:"w-4 h-4"}),e&&h.jsx(Q,{className:"text-sm font-medium",children:e})]})},QE=()=>window.G7Core,Wc=v.createContext(null),Gp=()=>{const e=v.useContext(Wc);if(!e){const t=QE(),n=t?.locale?.current?.()||"ko",r=t?.locale?.supported?.()||["ko","en"];return{activeLocale:n,getActiveLocale:()=>t?.locale?.current?.()||"ko",supportedLocales:r,isInsideMultilingualTabPanel:!1}}return{...e,isInsideMultilingualTabPanel:!0}},JE=()=>window.G7Core,RE=({value:e,onChange:t,placeholder:n,readOnly:r,disabled:s,hasError:o})=>{const{activeLocale:i,getActiveLocale:a}=Gp(),l=typeof e=="string"?{[i]:e}:e||{},c=v.useCallback(u=>{const f=a();t({...l,[f]:u.target.value})},[l,a,t]),d=o?"border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500":"border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500";return h.jsx(_t,{type:"text",value:l[i]||"",onChange:c,placeholder:n,readOnly:r,disabled:s,className:`w-full px-3 py-2 text-sm border ${d} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2`})},eM=(e,t)=>{if(!t)return!1;const n=t.startsWith("!"),r=n?t.slice(1):t,s=!!e[r];return n?!s:s},tM=({column:e,item:t,index:n,onValueChange:r,readOnly:s,error:o})=>{const i=t[e.key],a=s||e.readOnly||eM(t,e.readOnlyCondition)||e.disabled,l=!!o,c=l?"border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500":"border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500",d=v.useCallback(f=>{r(e.key,f)},[e.key,r]),u=f=>h.jsxs(I,{className:"w-full",children:[f,l&&h.jsx(Q,{className:"text-xs text-red-500 dark:text-red-400 mt-1 block",children:o})]});switch(e.type){case"input":return u(h.jsx(_t,{type:"text",value:i||"",onChange:f=>d(f.target.value),placeholder:e.placeholder,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${e.className||""}`}));case"number":return u(h.jsx(_t,{type:"number",value:i??"",onChange:f=>d(f.target.value?Number(f.target.value):null),placeholder:e.placeholder,min:e.min,max:e.max,step:e.step,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${e.className||""}`}));case"select":return u(h.jsxs(qr,{value:i??"",onChange:f=>d(f.target.value),disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${e.className||""}`,children:[h.jsx("option",{value:"",children:"선택하세요"}),e.options?.map(f=>h.jsx("option",{value:f.value,disabled:f.disabled,children:f.label},f.value))]}));case"textarea":return u(h.jsx(ir,{value:i||"",onChange:f=>d(f.target.value),placeholder:e.placeholder,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 resize-none ${e.className||""}`,rows:2}));case"multilingual":return u(h.jsx(RE,{value:i,onChange:f=>d(f),placeholder:e.placeholder,readOnly:a,disabled:a,hasError:l}));case"custom":return e.render?h.jsx(h.Fragment,{children:e.render(t,n,d)}):h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:"-"});default:return h.jsx(Q,{className:"text-gray-900 dark:text-white text-sm",children:String(i??"")})}},nM=(e,t,n)=>{if(!e)return;if(n==="multilingual"){const o=JE()?.locale?.current?.()||"ko",i=`${t}.${o}`;return e[i]?.[0]}return e[t]?.[0]},rM=({item:e,index:t,id:n,columns:r,enableDrag:s=!0,showIndex:o=!0,canRemove:i=!0,rowActions:a,readOnly:l=!1,onValueChange:c,onRemove:d,className:u="",level:f=0,errors:g})=>{const{attributes:m,listeners:p,setNodeRef:y,transform:b,transition:_,isDragging:x}=Bi({id:n,disabled:!s||l}),M={transform:jn.Transform.toString(b),transition:_,opacity:x?.5:1},C=v.useCallback((S,w)=>{c?.(S,w)},[c]),k=v.useCallback(S=>{S.onClick?.(e,t)},[e,t]),N=f>0?{paddingLeft:`${f*24}px`}:{};return h.jsxs(I,{ref:y,style:M,className:`flex items-center gap-2 py-2 px-2 border-b border-gray-100 dark:border-gray-700 last:border-b-0 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors ${x?"bg-blue-50 dark:bg-blue-900/20 shadow-lg rounded-lg z-10":""} ${u}`,children:[s&&!l&&h.jsx(I,{...m,...p,className:"flex-shrink-0 w-6 h-6 flex items-center justify-center cursor-grab active:cursor-grabbing text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400",style:N,children:h.jsx(se,{name:"grip-vertical",className:"w-4 h-4"})}),o&&h.jsx(I,{className:"flex-shrink-0 w-8 text-center text-sm text-gray-500 dark:text-gray-400 font-medium",children:t+1}),r.map(S=>{const w=nM(g,S.key,S.type);return h.jsx(I,{className:`flex-1 min-w-0 ${S.width||""}`,style:S.width?{flex:"none",width:S.width}:{},children:h.jsx(tM,{column:S,item:e,index:t,onValueChange:C,readOnly:l,error:w})},S.key)}),a&&a.length>0&&!l&&h.jsx(I,{className:"flex-shrink-0 flex items-center gap-1",children:a.map(S=>{const w=typeof S.disabled=="function"?S.disabled(e,t):S.disabled;return h.jsx(ie,{type:"button",onClick:()=>k(S),disabled:w,className:`p-1.5 rounded transition-colors ${S.danger?"text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-600":"text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300"} disabled:opacity-50 disabled:cursor-not-allowed`,title:S.label,children:S.icon?h.jsx(se,{name:S.icon,className:"w-4 h-4"}):h.jsx(Q,{className:"text-xs",children:S.label})},S.key)})}),i&&!a&&!l&&h.jsx(I,{className:"flex-shrink-0",children:h.jsx(ie,{type:"button",onClick:d,className:"p-1.5 text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300 rounded transition-colors",title:"삭제",children:h.jsx(se,{name:"minus",className:"w-4 h-4"})})})]})},Yp=()=>window.G7Core,Gc=()=>`item_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,sM=e=>{const t={_id:Gc()};return e.forEach(n=>{if(n.type==="multilingual"){const s=Yp()?.locale?.all?.()||["ko","en"],o={};s.forEach(i=>{o[i]=""}),t[n.key]=o}else n.type==="number"?t[n.key]=null:(n.type,t[n.key]="")}),t},oM=(e,t)=>{if(!e)return{};const n=`fields.${t}.`,r={};return Object.keys(e).forEach(s=>{if(s.startsWith(n)){const o=s.substring(n.length);r[o]=e[s]}}),r},iM=({items:e=[],columns:t,onChange:n,onAddItem:r,onRemoveItem:s,onReorder:o,addLabel:i="항목 추가",enableDrag:a=!0,showIndex:l=!0,minItems:c=0,maxItems:d,rowActions:u,emptyMessage:f="항목이 없습니다.",className:g="",headerClassName:m="",rowClassName:p="",name:y,readOnly:b=!1,createDefaultItem:_,childrenKey:x,itemIdKey:M="_id",errors:C,id:k,editorAttrs:N})=>{const S=v.useRef(null),w=v.useRef([]),E=v.useRef(null),D=v.useMemo(()=>{let G=!1;for(const U of e)if(U[M]==null){G=!0;break}return G?e.map(U=>U[M]!=null?U:{...U,[M]:Gc()}):e},[e,M]);w.current=D,v.useEffect(()=>{E.current=null},[D]);const P=v.useCallback(G=>String(G[M]??""),[M]);v.useEffect(()=>{if(y){const G=Yp();G?.form&&(S.current=G.form)}},[y]);const L=Jl(hs(ao,{activationConstraint:{distance:8}}),hs(io,{coordinateGetter:ji})),$=v.useMemo(()=>D.map(G=>P(G)),[D,P]),A=v.useCallback(G=>{const{active:U,over:W}=G;if(W&&U.id!==W.id){const Y=E.current??w.current,V=Y.map(q=>String(q[M]??"")),X=V.indexOf(String(U.id)),K=V.indexOf(String(W.id));if(X!==-1&&K!==-1){const q=ps([...Y],X,K);E.current=q,n?.(q),o?.(q)}}},[n,o,M]),O=v.useCallback((G,U,W)=>{const V=[...E.current??w.current];V[G]={...V[G],[U]:W},E.current=V,n?.(V)},[n]),T=v.useCallback(()=>{const G=E.current??w.current;if(d&&G.length>=d)return;if(r){r();return}const U=_?_():sM(t);U[M]||(U[M]=Gc());const W=[...G,U];E.current=W,n?.(W)},[d,r,_,t,M,n]),z=v.useCallback(G=>{const U=E.current??w.current;if(U.length<=c)return;const W=U[G];if(s){s(G,W);return}const Y=U.filter((V,X)=>X!==G);E.current=Y,n?.(Y)},[c,s,n]),H=D.length>c,j=!d||D.lengthh.jsxs(I,{className:`flex-1 min-w-0 text-xs font-medium text-gray-500 dark:text-gray-400 uppercase ${G.width||""}`,style:G.width?{flex:"none",width:G.width}:{},children:[G.label,G.required&&h.jsx(Q,{className:"text-red-500 dark:text-red-400 ml-0.5",children:"*"})]},G.key)),(u||!b&&H)&&h.jsx(I,{className:"flex-shrink-0 w-8"})]}),D.length>0?h.jsx(cc,{sensors:L,collisionDetection:Rl,onDragEnd:A,children:h.jsx(Ui,{items:$,strategy:dc,children:h.jsx(I,{className:"border-x border-gray-200 dark:border-gray-700",children:D.map((G,U)=>{const W=P(G),Y=oM(C,U);return h.jsx(rM,{id:W,item:G,index:U,columns:t,enableDrag:a,showIndex:l,canRemove:H,rowActions:u,readOnly:b,onValueChange:(V,X)=>O(U,V,X),onRemove:()=>z(U),className:p,errors:Y},W)})})})}):h.jsx(I,{className:"border-x border-gray-200 dark:border-gray-700 py-8 text-center text-gray-500 dark:text-gray-400 text-sm",children:f}),!b&&j&&h.jsx(I,{className:"relative border border-gray-200 dark:border-gray-700 border-t-0 rounded-b-lg",children:h.jsx(I,{className:"flex items-center justify-center py-2",children:h.jsxs(ie,{type:"button",onClick:T,className:"flex items-center gap-1.5 px-4 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[h.jsx(se,{name:"plus",className:"w-4 h-4"}),h.jsx(Q,{children:i})]})})}),(b||!j)&&D.length>0&&h.jsx(I,{className:"border-x border-b border-gray-200 dark:border-gray-700 rounded-b-lg h-1"})]})},Xp=()=>window.G7Core,aM=()=>Xp()?.locale?.supported?.()??["ko","en"],lM=()=>Xp()?.locale?.current?.()??"ko",cM={ko:"한국어",en:"English",ja:"日本語",zh:"中文"},dM=({children:e,className:t="",style:n,variant:r="default",defaultLocale:s,onLocaleChange:o,locales:i})=>{const a=v.useMemo(()=>i&&i.length>0?i:aM(),[i]),[l,c]=v.useState(s||lM());v.useEffect(()=>{if(a.length>0&&!a.includes(l)){const p=a[0];c(p),o?.(p)}},[a,l,o]);const d=v.useRef(l);d.current=l;const u=v.useCallback(()=>d.current,[]),f=v.useCallback(p=>{c(p),o?.(p)},[o]),g=v.useMemo(()=>({activeLocale:l,getActiveLocale:u,supportedLocales:a}),[l,u,a]),m=p=>{const y=p===l,b="px-4 py-2 text-sm font-medium transition-all";return r==="pills"?y?`${b} bg-blue-600 text-white rounded-lg`:`${b} text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg`:r==="underline"?y?`${b} text-blue-600 dark:text-blue-400 border-b-2 border-blue-600 dark:border-blue-400`:`${b} text-gray-600 dark:text-gray-400 border-b-2 border-transparent hover:border-gray-300 dark:hover:border-gray-500`:y?`${b} text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 border-b-2 border-blue-600`:`${b} text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 border-b-2 border-transparent`};return h.jsxs(I,{className:t,style:n,children:[h.jsx(Xo,{className:"flex gap-1 border-b border-gray-200 dark:border-gray-700 mb-4",children:a.map(p=>h.jsx(ie,{type:"button",onClick:()=>f(p),className:m(p),children:h.jsx(Q,{children:cM[p]||p})},p))}),h.jsx(Wc.Provider,{value:g,children:e})]})},uM=({label:e,required:t=!1,error:n,helperText:r,className:s="",labelClassName:o="",children:i,horizontal:a=!1,labelWidth:l="w-1/3",id:c,editorAttrs:d})=>{const u=a?`flex items-start gap-4 ${s}`:`space-y-1 ${s}`,f=a?`${l} flex-shrink-0 pt-2`:"",g=a?"flex-1":"";return h.jsxs(I,{className:u,id:c,...d,children:[e&&h.jsx(I,{className:f,children:h.jsxs(dn,{className:`block text-sm font-medium text-gray-700 dark:text-gray-300 ${o}`,children:[e,t&&h.jsx("span",{className:"ml-1 text-red-500 dark:text-red-400",children:"*"})]})}),h.jsxs(I,{className:`${g} ${n?"form-field-error":""}`.trim(),children:[i,r&&!n&&h.jsx(I,{className:"mt-1 text-xs text-gray-500 dark:text-gray-400",children:r}),n&&h.jsx(I,{className:"mt-1 text-xs text-red-600 dark:text-red-400",children:n})]})]})};function ko(e){return e+.5|0}const pr=(e,t,n)=>Math.max(Math.min(e,n),t);function Co(e){return pr(ko(e*2.55),0,255)}function mr(e){return pr(ko(e*255),0,255)}function Rn(e){return pr(ko(e/2.55)/100,0,1)}function Kp(e){return pr(ko(e*100),0,100)}const yn={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Yc=[..."0123456789ABCDEF"],fM=e=>Yc[e&15],hM=e=>Yc[(e&240)>>4]+Yc[e&15],ia=e=>(e&240)>>4===(e&15),gM=e=>ia(e.r)&&ia(e.g)&&ia(e.b)&&ia(e.a);function pM(e){var t=e.length,n;return e[0]==="#"&&(t===4||t===5?n={r:255&yn[e[1]]*17,g:255&yn[e[2]]*17,b:255&yn[e[3]]*17,a:t===5?yn[e[4]]*17:255}:(t===7||t===9)&&(n={r:yn[e[1]]<<4|yn[e[2]],g:yn[e[3]]<<4|yn[e[4]],b:yn[e[5]]<<4|yn[e[6]],a:t===9?yn[e[7]]<<4|yn[e[8]]:255})),n}const mM=(e,t)=>e<255?t(e):"";function bM(e){var t=gM(e)?fM:hM;return e?"#"+t(e.r)+t(e.g)+t(e.b)+mM(e.a,t):void 0}const yM=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function qp(e,t,n){const r=t*Math.min(n,1-n),s=(o,i=(o+e/30)%12)=>n-r*Math.max(Math.min(i-3,9-i,1),-1);return[s(0),s(8),s(4)]}function vM(e,t,n){const r=(s,o=(s+e/60)%6)=>n-n*t*Math.max(Math.min(o,4-o,1),0);return[r(5),r(3),r(1)]}function xM(e,t,n){const r=qp(e,1,.5);let s;for(t+n>1&&(s=1/(t+n),t*=s,n*=s),s=0;s<3;s++)r[s]*=1-t-n,r[s]+=t;return r}function wM(e,t,n,r,s){return e===s?(t-n)/r+(t.5?d/(2-o-i):d/(o+i),l=wM(n,r,s,d,o),l=l*60+.5),[l|0,c||0,a]}function Kc(e,t,n,r){return(Array.isArray(t)?e(t[0],t[1],t[2]):e(t,n,r)).map(mr)}function qc(e,t,n){return Kc(qp,e,t,n)}function kM(e,t,n){return Kc(xM,e,t,n)}function CM(e,t,n){return Kc(vM,e,t,n)}function Zp(e){return(e%360+360)%360}function SM(e){const t=yM.exec(e);let n=255,r;if(!t)return;t[5]!==r&&(n=t[6]?Co(+t[5]):mr(+t[5]));const s=Zp(+t[2]),o=+t[3]/100,i=+t[4]/100;return t[1]==="hwb"?r=kM(s,o,i):t[1]==="hsv"?r=CM(s,o,i):r=qc(s,o,i),{r:r[0],g:r[1],b:r[2],a:n}}function _M(e,t){var n=Xc(e);n[0]=Zp(n[0]+t),n=qc(n),e.r=n[0],e.g=n[1],e.b=n[2]}function EM(e){if(!e)return;const t=Xc(e),n=t[0],r=Kp(t[1]),s=Kp(t[2]);return e.a<255?`hsla(${n}, ${r}%, ${s}%, ${Rn(e.a)})`:`hsl(${n}, ${r}%, ${s}%)`}const Qp={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Jp={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};function MM(){const e={},t=Object.keys(Jp),n=Object.keys(Qp);let r,s,o,i,a;for(r=0;r>16&255,o>>8&255,o&255]}return e}let aa;function NM(e){aa||(aa=MM(),aa.transparent=[0,0,0,0]);const t=aa[e.toLowerCase()];return t&&{r:t[0],g:t[1],b:t[2],a:t.length===4?t[3]:255}}const TM=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function OM(e){const t=TM.exec(e);let n=255,r,s,o;if(t){if(t[7]!==r){const i=+t[7];n=t[8]?Co(i):pr(i*255,0,255)}return r=+t[1],s=+t[3],o=+t[5],r=255&(t[2]?Co(r):pr(r,0,255)),s=255&(t[4]?Co(s):pr(s,0,255)),o=255&(t[6]?Co(o):pr(o,0,255)),{r,g:s,b:o,a:n}}}function AM(e){return e&&(e.a<255?`rgba(${e.r}, ${e.g}, ${e.b}, ${Rn(e.a)})`:`rgb(${e.r}, ${e.g}, ${e.b})`)}const Zc=e=>e<=.0031308?e*12.92:Math.pow(e,1/2.4)*1.055-.055,Es=e=>e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4);function DM(e,t,n){const r=Es(Rn(e.r)),s=Es(Rn(e.g)),o=Es(Rn(e.b));return{r:mr(Zc(r+n*(Es(Rn(t.r))-r))),g:mr(Zc(s+n*(Es(Rn(t.g))-s))),b:mr(Zc(o+n*(Es(Rn(t.b))-o))),a:e.a+n*(t.a-e.a)}}function la(e,t,n){if(e){let r=Xc(e);r[t]=Math.max(0,Math.min(r[t]+r[t]*n,t===0?360:1)),r=qc(r),e.r=r[0],e.g=r[1],e.b=r[2]}}function Rp(e,t){return e&&Object.assign(t||{},e)}function e0(e){var t={r:0,g:0,b:0,a:255};return Array.isArray(e)?e.length>=3&&(t={r:e[0],g:e[1],b:e[2],a:255},e.length>3&&(t.a=mr(e[3]))):(t=Rp(e,{r:0,g:0,b:0,a:1}),t.a=mr(t.a)),t}function IM(e){return e.charAt(0)==="r"?OM(e):SM(e)}class So{constructor(t){if(t instanceof So)return t;const n=typeof t;let r;n==="object"?r=e0(t):n==="string"&&(r=pM(t)||NM(t)||IM(t)),this._rgb=r,this._valid=!!r}get valid(){return this._valid}get rgb(){var t=Rp(this._rgb);return t&&(t.a=Rn(t.a)),t}set rgb(t){this._rgb=e0(t)}rgbString(){return this._valid?AM(this._rgb):void 0}hexString(){return this._valid?bM(this._rgb):void 0}hslString(){return this._valid?EM(this._rgb):void 0}mix(t,n){if(t){const r=this.rgb,s=t.rgb;let o;const i=n===o?.5:n,a=2*i-1,l=r.a-s.a,c=((a*l===-1?a:(a+l)/(1+a*l))+1)/2;o=1-c,r.r=255&c*r.r+o*s.r+.5,r.g=255&c*r.g+o*s.g+.5,r.b=255&c*r.b+o*s.b+.5,r.a=i*r.a+(1-i)*s.a,this.rgb=r}return this}interpolate(t,n){return t&&(this._rgb=DM(this._rgb,t._rgb,n)),this}clone(){return new So(this.rgb)}alpha(t){return this._rgb.a=mr(t),this}clearer(t){const n=this._rgb;return n.a*=1-t,this}greyscale(){const t=this._rgb,n=ko(t.r*.3+t.g*.59+t.b*.11);return t.r=t.g=t.b=n,this}opaquer(t){const n=this._rgb;return n.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return la(this._rgb,2,t),this}darken(t){return la(this._rgb,2,-t),this}saturate(t){return la(this._rgb,1,t),this}desaturate(t){return la(this._rgb,1,-t),this}rotate(t){return _M(this._rgb,t),this}}function er(){}const LM=(()=>{let e=0;return()=>e++})();function ut(e){return e==null}function Ot(e){if(Array.isArray&&Array.isArray(e))return!0;const t=Object.prototype.toString.call(e);return t.slice(0,7)==="[object"&&t.slice(-6)==="Array]"}function Re(e){return e!==null&&Object.prototype.toString.call(e)==="[object Object]"}function vn(e){return(typeof e=="number"||e instanceof Number)&&isFinite(+e)}function Gn(e,t){return vn(e)?e:t}function et(e,t){return typeof e>"u"?t:e}const PM=(e,t)=>typeof e=="string"&&e.endsWith("%")?parseFloat(e)/100:+e/t,t0=(e,t)=>typeof e=="string"&&e.endsWith("%")?parseFloat(e)/100*t:+e;function bt(e,t,n){if(e&&typeof e.call=="function")return e.apply(n,t)}function ct(e,t,n,r){let s,o,i;if(Ot(e))for(o=e.length,s=0;se,x:e=>e.x,y:e=>e.y};function zM(e){const t=e.split("."),n=[];let r="";for(const s of t)r+=s,r.endsWith("\\")?r=r.slice(0,-1)+".":(n.push(r),r="");return n}function VM(e){const t=zM(e);return n=>{for(const r of t){if(r==="")break;n=n&&n[r]}return n}}function Pr(e,t){return(r0[t]||(r0[t]=VM(t)))(e)}function Qc(e){return e.charAt(0).toUpperCase()+e.slice(1)}const Mo=e=>typeof e<"u",br=e=>typeof e=="function",s0=(e,t)=>{if(e.size!==t.size)return!1;for(const n of e)if(!t.has(n))return!1;return!0};function HM(e){return e.type==="mouseup"||e.type==="click"||e.type==="contextmenu"}const ft=Math.PI,Nt=2*ft,ua=Number.POSITIVE_INFINITY,UM=ft/180,At=ft/2,$r=ft/4,o0=ft*2/3,i0=Math.log10,yr=Math.sign;function fa(e,t,n){return Math.abs(e-t)s-o).pop(),t}function jM(e){return typeof e=="symbol"||typeof e=="object"&&e!==null&&!(Symbol.toPrimitive in e||"toString"in e||"valueOf"in e)}function ha(e){return!jM(e)&&!isNaN(parseFloat(e))&&isFinite(e)}function WM(e,t){const n=Math.round(e);return n-t<=e&&n+t>=e}function GM(e,t,n){let r,s,o;for(r=0,s=e.length;rl&&c=Math.min(t,n)-r&&e<=Math.max(t,n)+r}function Jc(e,t,n){n=n||(i=>e[i]1;)o=s+r>>1,n(o)?s=o:r=o;return{lo:s,hi:r}}const Rc=(e,t,n,r)=>Jc(e,n,r?s=>{const o=e[s][t];return oe[s][t]Jc(e,n,r=>e[r][t]>=n);function ZM(e,t,n){let r=0,s=e.length;for(;rr&&e[s-1]>n;)s--;return r>0||s{const r="_onData"+Qc(n),s=e[n];Object.defineProperty(e,n,{configurable:!0,enumerable:!1,value(...o){const i=s.apply(this,o);return e._chartjs.listeners.forEach(a=>{typeof a[r]=="function"&&a[r](...o)}),i}})})}function u0(e,t){const n=e._chartjs;if(!n)return;const r=n.listeners,s=r.indexOf(t);s!==-1&&r.splice(s,1),!(r.length>0)&&(d0.forEach(o=>{delete e[o]}),delete e._chartjs)}function f0(e){const t=new Set(e);return t.size===e.length?e:Array.from(t)}const h0=(function(){return typeof window>"u"?function(e){return e()}:window.requestAnimationFrame})();function g0(e,t){let n=[],r=!1;return function(...s){n=s,r||(r=!0,h0.call(window,()=>{r=!1,e.apply(t,n)}))}}function JM(e,t){let n;return function(...r){return t?(clearTimeout(n),n=setTimeout(e,t,r)):e.apply(this,r),t}}const ed=e=>e==="start"?"left":e==="end"?"right":"center",Xt=(e,t,n)=>e==="start"?t:e==="end"?n:(t+n)/2,RM=(e,t,n,r)=>e===(r?"left":"right")?n:e==="center"?(t+n)/2:t,pa=e=>e===0||e===1,p0=(e,t,n)=>-(Math.pow(2,10*(e-=1))*Math.sin((e-t)*Nt/n)),m0=(e,t,n)=>Math.pow(2,-10*e)*Math.sin((e-t)*Nt/n)+1,No={linear:e=>e,easeInQuad:e=>e*e,easeOutQuad:e=>-e*(e-2),easeInOutQuad:e=>(e/=.5)<1?.5*e*e:-.5*(--e*(e-2)-1),easeInCubic:e=>e*e*e,easeOutCubic:e=>(e-=1)*e*e+1,easeInOutCubic:e=>(e/=.5)<1?.5*e*e*e:.5*((e-=2)*e*e+2),easeInQuart:e=>e*e*e*e,easeOutQuart:e=>-((e-=1)*e*e*e-1),easeInOutQuart:e=>(e/=.5)<1?.5*e*e*e*e:-.5*((e-=2)*e*e*e-2),easeInQuint:e=>e*e*e*e*e,easeOutQuint:e=>(e-=1)*e*e*e*e+1,easeInOutQuint:e=>(e/=.5)<1?.5*e*e*e*e*e:.5*((e-=2)*e*e*e*e+2),easeInSine:e=>-Math.cos(e*At)+1,easeOutSine:e=>Math.sin(e*At),easeInOutSine:e=>-.5*(Math.cos(ft*e)-1),easeInExpo:e=>e===0?0:Math.pow(2,10*(e-1)),easeOutExpo:e=>e===1?1:-Math.pow(2,-10*e)+1,easeInOutExpo:e=>pa(e)?e:e<.5?.5*Math.pow(2,10*(e*2-1)):.5*(-Math.pow(2,-10*(e*2-1))+2),easeInCirc:e=>e>=1?e:-(Math.sqrt(1-e*e)-1),easeOutCirc:e=>Math.sqrt(1-(e-=1)*e),easeInOutCirc:e=>(e/=.5)<1?-.5*(Math.sqrt(1-e*e)-1):.5*(Math.sqrt(1-(e-=2)*e)+1),easeInElastic:e=>pa(e)?e:p0(e,.075,.3),easeOutElastic:e=>pa(e)?e:m0(e,.075,.3),easeInOutElastic(e){return pa(e)?e:e<.5?.5*p0(e*2,.1125,.45):.5+.5*m0(e*2-1,.1125,.45)},easeInBack(e){return e*e*((1.70158+1)*e-1.70158)},easeOutBack(e){return(e-=1)*e*((1.70158+1)*e+1.70158)+1},easeInOutBack(e){let t=1.70158;return(e/=.5)<1?.5*(e*e*(((t*=1.525)+1)*e-t)):.5*((e-=2)*e*(((t*=1.525)+1)*e+t)+2)},easeInBounce:e=>1-No.easeOutBounce(1-e),easeOutBounce(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375},easeInOutBounce:e=>e<.5?No.easeInBounce(e*2)*.5:No.easeOutBounce(e*2-1)*.5+.5};function b0(e){if(e&&typeof e=="object"){const t=e.toString();return t==="[object CanvasPattern]"||t==="[object CanvasGradient]"}return!1}function y0(e){return b0(e)?e:new So(e)}function td(e){return b0(e)?e:new So(e).saturate(.5).darken(.1).hexString()}const eN=["x","y","borderWidth","radius","tension"],tN=["color","borderColor","backgroundColor"];function nN(e){e.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),e.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>t!=="onProgress"&&t!=="onComplete"&&t!=="fn"}),e.set("animations",{colors:{type:"color",properties:tN},numbers:{type:"number",properties:eN}}),e.describe("animations",{_fallback:"animation"}),e.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>t|0}}}})}function rN(e){e.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})}const v0=new Map;function sN(e,t){t=t||{};const n=e+JSON.stringify(t);let r=v0.get(n);return r||(r=new Intl.NumberFormat(e,t),v0.set(n,r)),r}function nd(e,t,n){return sN(t,n).format(e)}const oN={values(e){return Ot(e)?e:""+e},numeric(e,t,n){if(e===0)return"0";const r=this.chart.options.locale;let s,o=e;if(n.length>1){const c=Math.max(Math.abs(n[0].value),Math.abs(n[n.length-1].value));(c<1e-4||c>1e15)&&(s="scientific"),o=iN(e,n)}const i=i0(Math.abs(o)),a=isNaN(i)?1:Math.max(Math.min(-1*Math.floor(i),20),0),l={notation:s,minimumFractionDigits:a,maximumFractionDigits:a};return Object.assign(l,this.options.ticks.format),nd(e,r,l)}};function iN(e,t){let n=t.length>3?t[2].value-t[1].value:t[1].value-t[0].value;return Math.abs(n)>=1&&e!==Math.floor(e)&&(n=e-Math.floor(e)),n}var x0={formatters:oN};function aN(e){e.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,n)=>n.lineWidth,tickColor:(t,n)=>n.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:x0.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),e.route("scale.ticks","color","","color"),e.route("scale.grid","color","","borderColor"),e.route("scale.border","color","","borderColor"),e.route("scale.title","color","","color"),e.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&t!=="callback"&&t!=="parser",_indexable:t=>t!=="borderDash"&&t!=="tickBorderDash"&&t!=="dash"}),e.describe("scales",{_fallback:"scale"}),e.describe("scale.ticks",{_scriptable:t=>t!=="backdropPadding"&&t!=="callback",_indexable:t=>t!=="backdropPadding"})}const zr=Object.create(null),rd=Object.create(null);function To(e,t){if(!t)return e;const n=t.split(".");for(let r=0,s=n.length;rr.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(r,s)=>td(s.backgroundColor),this.hoverBorderColor=(r,s)=>td(s.borderColor),this.hoverColor=(r,s)=>td(s.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(n)}set(t,n){return sd(this,t,n)}get(t){return To(this,t)}describe(t,n){return sd(rd,t,n)}override(t,n){return sd(zr,t,n)}route(t,n,r,s){const o=To(this,t),i=To(this,r),a="_"+n;Object.defineProperties(o,{[a]:{value:o[n],writable:!0},[n]:{enumerable:!0,get(){const l=this[a],c=i[s];return Re(l)?Object.assign({},c,l):et(l,c)},set(l){this[a]=l}}})}apply(t){t.forEach(n=>n(this))}}var St=new lN({_scriptable:e=>!e.startsWith("on"),_indexable:e=>e!=="events",hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[nN,rN,aN]);function cN(e){return!e||ut(e.size)||ut(e.family)?null:(e.style?e.style+" ":"")+(e.weight?e.weight+" ":"")+e.size+"px "+e.family}function w0(e,t,n,r,s){let o=t[s];return o||(o=t[s]=e.measureText(s).width,n.push(s)),o>r&&(r=o),r}function Vr(e,t,n){const r=e.currentDevicePixelRatio,s=n!==0?Math.max(n/2,.5):0;return Math.round((t-s)*r)/r+s}function k0(e,t){!t&&!e||(t=t||e.getContext("2d"),t.save(),t.resetTransform(),t.clearRect(0,0,e.width,e.height),t.restore())}function C0(e,t,n,r){S0(e,t,n,r,null)}function S0(e,t,n,r,s){let o,i,a,l,c,d,u,f;const g=t.pointStyle,m=t.rotation,p=t.radius;let y=(m||0)*UM;if(g&&typeof g=="object"&&(o=g.toString(),o==="[object HTMLImageElement]"||o==="[object HTMLCanvasElement]")){e.save(),e.translate(n,r),e.rotate(y),e.drawImage(g,-g.width/2,-g.height/2,g.width,g.height),e.restore();return}if(!(isNaN(p)||p<=0)){switch(e.beginPath(),g){default:s?e.ellipse(n,r,s/2,p,0,0,Nt):e.arc(n,r,p,0,Nt),e.closePath();break;case"triangle":d=s?s/2:p,e.moveTo(n+Math.sin(y)*d,r-Math.cos(y)*p),y+=o0,e.lineTo(n+Math.sin(y)*d,r-Math.cos(y)*p),y+=o0,e.lineTo(n+Math.sin(y)*d,r-Math.cos(y)*p),e.closePath();break;case"rectRounded":c=p*.516,l=p-c,i=Math.cos(y+$r)*l,u=Math.cos(y+$r)*(s?s/2-c:l),a=Math.sin(y+$r)*l,f=Math.sin(y+$r)*(s?s/2-c:l),e.arc(n-u,r-a,c,y-ft,y-At),e.arc(n+f,r-i,c,y-At,y),e.arc(n+u,r+a,c,y,y+At),e.arc(n-f,r+i,c,y+At,y+ft),e.closePath();break;case"rect":if(!m){l=Math.SQRT1_2*p,d=s?s/2:l,e.rect(n-d,r-l,2*d,2*l);break}y+=$r;case"rectRot":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),e.moveTo(n-u,r-a),e.lineTo(n+f,r-i),e.lineTo(n+u,r+a),e.lineTo(n-f,r+i),e.closePath();break;case"crossRot":y+=$r;case"cross":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),e.moveTo(n-u,r-a),e.lineTo(n+u,r+a),e.moveTo(n+f,r-i),e.lineTo(n-f,r+i);break;case"star":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),e.moveTo(n-u,r-a),e.lineTo(n+u,r+a),e.moveTo(n+f,r-i),e.lineTo(n-f,r+i),y+=$r,u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),e.moveTo(n-u,r-a),e.lineTo(n+u,r+a),e.moveTo(n+f,r-i),e.lineTo(n-f,r+i);break;case"line":i=s?s/2:Math.cos(y)*p,a=Math.sin(y)*p,e.moveTo(n-i,r-a),e.lineTo(n+i,r+a);break;case"dash":e.moveTo(n,r),e.lineTo(n+Math.cos(y)*(s?s/2:p),r+Math.sin(y)*p);break;case!1:e.closePath();break}e.fill(),t.borderWidth>0&&e.stroke()}}function _0(e,t,n){return n=n||.5,!t||e&&e.x>t.left-n&&e.xt.top-n&&e.y0&&o.strokeColor!=="";let l,c;for(e.save(),e.font=s.string,dN(e,o),l=0;l+e||0;function ad(e,t){const n={},r=Re(t),s=r?Object.keys(t):t,o=Re(e)?r?i=>et(e[i],e[t[i]]):i=>e[i]:()=>e;for(const i of s)n[i]=mN(o(i));return n}function E0(e){return ad(e,{top:"y",right:"x",bottom:"y",left:"x"})}function Ms(e){return ad(e,["topLeft","topRight","bottomLeft","bottomRight"])}function xn(e){const t=E0(e);return t.width=t.left+t.right,t.height=t.top+t.bottom,t}function Kt(e,t){e=e||{},t=t||St.font;let n=et(e.size,t.size);typeof n=="string"&&(n=parseInt(n,10));let r=et(e.style,t.style);r&&!(""+r).match(gN)&&(console.warn('Invalid font style specified: "'+r+'"'),r=void 0);const s={family:et(e.family,t.family),lineHeight:pN(et(e.lineHeight,t.lineHeight),n),size:n,style:r,weight:et(e.weight,t.weight),string:""};return s.string=cN(s),s}function ba(e,t,n,r){let s,o,i;for(s=0,o=e.length;sn&&a===0?0:a+l;return{min:i(r,-Math.abs(o)),max:i(s,o)}}function Ns(e,t){return Object.assign(Object.create(e),t)}function ld(e,t=[""],n,r,s=()=>e[0]){const o=n||e;typeof r>"u"&&(r=A0("_fallback",e));const i={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:e,_rootScopes:o,_fallback:r,_getTarget:s,override:a=>ld([a,...e],t,o,r)};return new Proxy(i,{deleteProperty(a,l){return delete a[l],delete a._keys,delete e[0][l],!0},get(a,l){return N0(a,l,()=>_N(l,t,e,a))},getOwnPropertyDescriptor(a,l){return Reflect.getOwnPropertyDescriptor(a._scopes[0],l)},getPrototypeOf(){return Reflect.getPrototypeOf(e[0])},has(a,l){return D0(a).includes(l)},ownKeys(a){return D0(a)},set(a,l,c){const d=a._storage||(a._storage=s());return a[l]=d[l]=c,delete a._keys,!0}})}function Ts(e,t,n,r){const s={_cacheable:!1,_proxy:e,_context:t,_subProxy:n,_stack:new Set,_descriptors:M0(e,r),setContext:o=>Ts(e,o,n,r),override:o=>Ts(e.override(o),t,n,r)};return new Proxy(s,{deleteProperty(o,i){return delete o[i],delete e[i],!0},get(o,i,a){return N0(o,i,()=>vN(o,i,a))},getOwnPropertyDescriptor(o,i){return o._descriptors.allKeys?Reflect.has(e,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(e,i)},getPrototypeOf(){return Reflect.getPrototypeOf(e)},has(o,i){return Reflect.has(e,i)},ownKeys(){return Reflect.ownKeys(e)},set(o,i,a){return e[i]=a,delete o[i],!0}})}function M0(e,t={scriptable:!0,indexable:!0}){const{_scriptable:n=t.scriptable,_indexable:r=t.indexable,_allKeys:s=t.allKeys}=e;return{allKeys:s,scriptable:n,indexable:r,isScriptable:br(n)?n:()=>n,isIndexable:br(r)?r:()=>r}}const yN=(e,t)=>e?e+Qc(t):t,cd=(e,t)=>Re(t)&&e!=="adapters"&&(Object.getPrototypeOf(t)===null||t.constructor===Object);function N0(e,t,n){if(Object.prototype.hasOwnProperty.call(e,t)||t==="constructor")return e[t];const r=n();return e[t]=r,r}function vN(e,t,n){const{_proxy:r,_context:s,_subProxy:o,_descriptors:i}=e;let a=r[t];return br(a)&&i.isScriptable(t)&&(a=xN(t,a,e,n)),Ot(a)&&a.length&&(a=wN(t,a,e,i.isIndexable)),cd(t,a)&&(a=Ts(a,s,o&&o[t],i)),a}function xN(e,t,n,r){const{_proxy:s,_context:o,_subProxy:i,_stack:a}=n;if(a.has(e))throw new Error("Recursion detected: "+Array.from(a).join("->")+"->"+e);a.add(e);let l=t(o,i||r);return a.delete(e),cd(e,l)&&(l=dd(s._scopes,s,e,l)),l}function wN(e,t,n,r){const{_proxy:s,_context:o,_subProxy:i,_descriptors:a}=n;if(typeof o.index<"u"&&r(e))return t[o.index%t.length];if(Re(t[0])){const l=t,c=s._scopes.filter(d=>d!==l);t=[];for(const d of l){const u=dd(c,s,e,d);t.push(Ts(u,o,i&&i[e],a))}}return t}function T0(e,t,n){return br(e)?e(t,n):e}const kN=(e,t)=>e===!0?t:typeof e=="string"?Pr(t,e):void 0;function CN(e,t,n,r,s){for(const o of t){const i=kN(n,o);if(i){e.add(i);const a=T0(i._fallback,n,s);if(typeof a<"u"&&a!==n&&a!==r)return a}else if(i===!1&&typeof r<"u"&&n!==r)return null}return!1}function dd(e,t,n,r){const s=t._rootScopes,o=T0(t._fallback,n,r),i=[...e,...s],a=new Set;a.add(r);let l=O0(a,i,n,o||n,r);return l===null||typeof o<"u"&&o!==n&&(l=O0(a,i,o,l,r),l===null)?!1:ld(Array.from(a),[""],s,o,()=>SN(t,n,r))}function O0(e,t,n,r,s){for(;n;)n=CN(e,t,n,r,s);return n}function SN(e,t,n){const r=e._getTarget();t in r||(r[t]={});const s=r[t];return Ot(s)&&Re(n)?n:s||{}}function _N(e,t,n,r){let s;for(const o of t)if(s=A0(yN(o,e),n),typeof s<"u")return cd(e,s)?dd(n,r,e,s):s}function A0(e,t){for(const n of t){if(!n)continue;const r=n[e];if(typeof r<"u")return r}}function D0(e){let t=e._keys;return t||(t=e._keys=EN(e._scopes)),t}function EN(e){const t=new Set;for(const n of e)for(const r of Object.keys(n).filter(s=>!s.startsWith("_")))t.add(r);return Array.from(t)}function ud(){return typeof window<"u"&&typeof document<"u"}function fd(e){let t=e.parentNode;return t&&t.toString()==="[object ShadowRoot]"&&(t=t.host),t}function ya(e,t,n){let r;return typeof e=="string"?(r=parseInt(e,10),e.indexOf("%")!==-1&&(r=r/100*t.parentNode[n])):r=e,r}const va=e=>e.ownerDocument.defaultView.getComputedStyle(e,null);function MN(e,t){return va(e).getPropertyValue(t)}const NN=["top","right","bottom","left"];function Hr(e,t,n){const r={};n=n?"-"+n:"";for(let s=0;s<4;s++){const o=NN[s];r[o]=parseFloat(e[t+"-"+o+n])||0}return r.width=r.left+r.right,r.height=r.top+r.bottom,r}const TN=(e,t,n)=>(e>0||t>0)&&(!n||!n.shadowRoot);function ON(e,t){const n=e.touches,r=n&&n.length?n[0]:e,{offsetX:s,offsetY:o}=r;let i=!1,a,l;if(TN(s,o,e.target))a=s,l=o;else{const c=t.getBoundingClientRect();a=r.clientX-c.left,l=r.clientY-c.top,i=!0}return{x:a,y:l,box:i}}function Ur(e,t){if("native"in e)return e;const{canvas:n,currentDevicePixelRatio:r}=t,s=va(n),o=s.boxSizing==="border-box",i=Hr(s,"padding"),a=Hr(s,"border","width"),{x:l,y:c,box:d}=ON(e,n),u=i.left+(d&&a.left),f=i.top+(d&&a.top);let{width:g,height:m}=t;return o&&(g-=i.width+a.width,m-=i.height+a.height),{x:Math.round((l-u)/g*n.width/r),y:Math.round((c-f)/m*n.height/r)}}function AN(e,t,n){let r,s;if(t===void 0||n===void 0){const o=e&&fd(e);if(!o)t=e.clientWidth,n=e.clientHeight;else{const i=o.getBoundingClientRect(),a=va(o),l=Hr(a,"border","width"),c=Hr(a,"padding");t=i.width-c.width-l.width,n=i.height-c.height-l.height,r=ya(a.maxWidth,o,"clientWidth"),s=ya(a.maxHeight,o,"clientHeight")}}return{width:t,height:n,maxWidth:r||ua,maxHeight:s||ua}}const vr=e=>Math.round(e*10)/10;function DN(e,t,n,r){const s=va(e),o=Hr(s,"margin"),i=ya(s.maxWidth,e,"clientWidth")||ua,a=ya(s.maxHeight,e,"clientHeight")||ua,l=AN(e,t,n);let{width:c,height:d}=l;if(s.boxSizing==="content-box"){const f=Hr(s,"border","width"),g=Hr(s,"padding");c-=g.width+f.width,d-=g.height+f.height}return c=Math.max(0,c-o.width),d=Math.max(0,r?c/r:d-o.height),c=vr(Math.min(c,i,l.maxWidth)),d=vr(Math.min(d,a,l.maxHeight)),c&&!d&&(d=vr(c/2)),(t!==void 0||n!==void 0)&&r&&l.height&&d>l.height&&(d=l.height,c=vr(Math.floor(d*r))),{width:c,height:d}}function I0(e,t,n){const r=t||1,s=vr(e.height*r),o=vr(e.width*r);e.height=vr(e.height),e.width=vr(e.width);const i=e.canvas;return i.style&&(n||!i.style.height&&!i.style.width)&&(i.style.height=`${e.height}px`,i.style.width=`${e.width}px`),e.currentDevicePixelRatio!==r||i.height!==s||i.width!==o?(e.currentDevicePixelRatio=r,i.height=s,i.width=o,e.ctx.setTransform(r,0,0,r,0,0),!0):!1}const IN=(function(){let e=!1;try{const t={get passive(){return e=!0,!1}};ud()&&(window.addEventListener("test",null,t),window.removeEventListener("test",null,t))}catch{}return e})();function L0(e,t){const n=MN(e,t),r=n&&n.match(/^(\d+)(\.\d+)?px$/);return r?+r[1]:void 0}const LN=function(e,t){return{x(n){return e+e+t-n},setWidth(n){t=n},textAlign(n){return n==="center"?n:n==="right"?"left":"right"},xPlus(n,r){return n-r},leftForLtr(n,r){return n-r}}},PN=function(){return{x(e){return e},setWidth(e){},textAlign(e){return e},xPlus(e,t){return e+t},leftForLtr(e,t){return e}}};function Os(e,t,n){return e?LN(t,n):PN()}function P0(e,t){let n,r;(t==="ltr"||t==="rtl")&&(n=e.canvas.style,r=[n.getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",t,"important"),e.prevTextDirection=r)}function $0(e,t){t!==void 0&&(delete e.prevTextDirection,e.canvas.style.setProperty("direction",t[0],t[1]))}function xa(e,t,n){return e.options.clip?e[n]:t[n]}function $N(e,t){const{xScale:n,yScale:r}=e;return n&&r?{left:xa(n,t,"left"),right:xa(n,t,"right"),top:xa(r,t,"top"),bottom:xa(r,t,"bottom")}:t}function FN(e,t){const n=t._clip;if(n.disabled)return!1;const r=$N(t,e.chartArea);return{left:n.left===!1?0:r.left-(n.left===!0?0:n.left),right:n.right===!1?e.width:r.right+(n.right===!0?0:n.right),top:n.top===!1?0:r.top-(n.top===!0?0:n.top),bottom:n.bottom===!1?e.height:r.bottom+(n.bottom===!0?0:n.bottom)}}class zN{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,n,r,s){const o=n.listeners[s],i=n.duration;o.forEach(a=>a({chart:t,initial:n.initial,numSteps:i,currentStep:Math.min(r-n.start,i)}))}_refresh(){this._request||(this._running=!0,this._request=h0.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(t=Date.now()){let n=0;this._charts.forEach((r,s)=>{if(!r.running||!r.items.length)return;const o=r.items;let i=o.length-1,a=!1,l;for(;i>=0;--i)l=o[i],l._active?(l._total>r.duration&&(r.duration=l._total),l.tick(t),a=!0):(o[i]=o[o.length-1],o.pop());a&&(s.draw(),this._notify(s,r,t,"progress")),o.length||(r.running=!1,this._notify(s,r,t,"complete"),r.initial=!1),n+=o.length}),this._lastDate=t,n===0&&(this._running=!1)}_getAnims(t){const n=this._charts;let r=n.get(t);return r||(r={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},n.set(t,r)),r}listen(t,n,r){this._getAnims(t).listeners[n].push(r)}add(t,n){!n||!n.length||this._getAnims(t).items.push(...n)}has(t){return this._getAnims(t).items.length>0}start(t){const n=this._charts.get(t);n&&(n.running=!0,n.start=Date.now(),n.duration=n.items.reduce((r,s)=>Math.max(r,s._duration),0),this._refresh())}running(t){if(!this._running)return!1;const n=this._charts.get(t);return!(!n||!n.running||!n.items.length)}stop(t){const n=this._charts.get(t);if(!n||!n.items.length)return;const r=n.items;let s=r.length-1;for(;s>=0;--s)r[s].cancel();n.items=[],this._notify(t,n,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var nr=new zN;const F0="transparent",VN={boolean(e,t,n){return n>.5?t:e},color(e,t,n){const r=y0(e||F0),s=r.valid&&y0(t||F0);return s&&s.valid?s.mix(r,n).hexString():t},number(e,t,n){return e+(t-e)*n}};class HN{constructor(t,n,r,s){const o=n[r];s=ba([t.to,s,o,t.from]);const i=ba([t.from,o,s]);this._active=!0,this._fn=t.fn||VN[t.type||typeof i],this._easing=No[t.easing]||No.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=n,this._prop=r,this._from=i,this._to=s,this._promises=void 0}active(){return this._active}update(t,n,r){if(this._active){this._notify(!1);const s=this._target[this._prop],o=r-this._start,i=this._duration-o;this._start=r,this._duration=Math.floor(Math.max(i,t.duration)),this._total+=o,this._loop=!!t.loop,this._to=ba([t.to,n,s,t.from]),this._from=ba([t.from,s,n])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const n=t-this._start,r=this._duration,s=this._prop,o=this._from,i=this._loop,a=this._to;let l;if(this._active=o!==a&&(i||n1?2-l:l,l=this._easing(Math.min(1,Math.max(0,l))),this._target[s]=this._fn(o,a,l)}wait(){const t=this._promises||(this._promises=[]);return new Promise((n,r)=>{t.push({res:n,rej:r})})}_notify(t){const n=t?"res":"rej",r=this._promises||[];for(let s=0;s{const o=t[s];if(!Re(o))return;const i={};for(const a of n)i[a]=o[a];(Ot(o.properties)&&o.properties||[s]).forEach(a=>{(a===s||!r.has(a))&&r.set(a,i)})})}_animateOptions(t,n){const r=n.options,s=BN(t,r);if(!s)return[];const o=this._createAnimations(s,r);return r.$shared&&UN(t.options.$animations,r).then(()=>{t.options=r},()=>{}),o}_createAnimations(t,n){const r=this._properties,s=[],o=t.$animations||(t.$animations={}),i=Object.keys(n),a=Date.now();let l;for(l=i.length-1;l>=0;--l){const c=i[l];if(c.charAt(0)==="$")continue;if(c==="options"){s.push(...this._animateOptions(t,n));continue}const d=n[c];let u=o[c];const f=r.get(c);if(u)if(f&&u.active()){u.update(f,d,a);continue}else u.cancel();if(!f||!f.duration){t[c]=d;continue}o[c]=u=new HN(f,t,c,d),s.push(u)}return s}update(t,n){if(this._properties.size===0){Object.assign(t,n);return}const r=this._createAnimations(t,n);if(r.length)return nr.add(this._chart,r),!0}}function UN(e,t){const n=[],r=Object.keys(t);for(let s=0;s0||!n&&o<0)return s.index}return null}function j0(e,t){const{chart:n,_cachedMeta:r}=e,s=n._stacks||(n._stacks={}),{iScale:o,vScale:i,index:a}=r,l=o.axis,c=i.axis,d=YN(o,i,r),u=t.length;let f;for(let g=0;gn[r].axis===t).shift()}function qN(e,t){return Ns(e,{active:!1,dataset:void 0,datasetIndex:t,index:t,mode:"default",type:"dataset"})}function ZN(e,t,n){return Ns(e,{active:!1,dataIndex:t,parsed:void 0,raw:void 0,element:n,index:t,mode:"default",type:"data"})}function Ao(e,t){const n=e.controller.index,r=e.vScale&&e.vScale.axis;if(r){t=t||e._parsed;for(const s of t){const o=s._stacks;if(!o||o[r]===void 0||o[r][n]===void 0)return;delete o[r][n],o[r]._visualValues!==void 0&&o[r]._visualValues[n]!==void 0&&delete o[r]._visualValues[n]}}}const pd=e=>e==="reset"||e==="none",W0=(e,t)=>t?e:Object.assign({},e),QN=(e,t,n)=>e&&!t.hidden&&t._stacked&&{keys:H0(n,!0),values:null};class As{constructor(t,n){this.chart=t,this._ctx=t.ctx,this.index=n,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=hd(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Ao(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,n=this._cachedMeta,r=this.getDataset(),s=(u,f,g,m)=>u==="x"?f:u==="r"?m:g,o=n.xAxisID=et(r.xAxisID,gd(t,"x")),i=n.yAxisID=et(r.yAxisID,gd(t,"y")),a=n.rAxisID=et(r.rAxisID,gd(t,"r")),l=n.indexAxis,c=n.iAxisID=s(l,o,i,a),d=n.vAxisID=s(l,i,o,a);n.xScale=this.getScaleForId(o),n.yScale=this.getScaleForId(i),n.rScale=this.getScaleForId(a),n.iScale=this.getScaleForId(c),n.vScale=this.getScaleForId(d)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const n=this._cachedMeta;return t===n.iScale?n.vScale:n.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&u0(this._data,this),t._stacked&&Ao(t)}_dataCheck(){const t=this.getDataset(),n=t.data||(t.data=[]),r=this._data;if(Re(n)){const s=this._cachedMeta;this._data=GN(n,s)}else if(r!==n){if(r){u0(r,this);const s=this._cachedMeta;Ao(s),s._parsed=[]}n&&Object.isExtensible(n)&&QM(n,this),this._syncList=[],this._data=n}}addElements(){const t=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(t.dataset=new this.datasetElementType)}buildOrUpdateElements(t){const n=this._cachedMeta,r=this.getDataset();let s=!1;this._dataCheck();const o=n._stacked;n._stacked=hd(n.vScale,n),n.stack!==r.stack&&(s=!0,Ao(n),n.stack=r.stack),this._resyncElements(t),(s||o!==n._stacked)&&(j0(this,n._parsed),n._stacked=hd(n.vScale,n))}configure(){const t=this.chart.config,n=t.datasetScopeKeys(this._type),r=t.getOptionScopes(this.getDataset(),n,!0);this.options=t.createResolver(r,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(t,n){const{_cachedMeta:r,_data:s}=this,{iScale:o,_stacked:i}=r,a=o.axis;let l=t===0&&n===s.length?!0:r._sorted,c=t>0&&r._parsed[t-1],d,u,f;if(this._parsing===!1)r._parsed=s,r._sorted=!0,f=s;else{Ot(s[t])?f=this.parseArrayData(r,s,t,n):Re(s[t])?f=this.parseObjectData(r,s,t,n):f=this.parsePrimitiveData(r,s,t,n);const g=()=>u[a]===null||c&&u[a]p||u=0;--f)if(!m()){this.updateRangeFromParsed(c,t,g,l);break}}return c}getAllParsedValues(t){const n=this._cachedMeta._parsed,r=[];let s,o,i;for(s=0,o=n.length;s=0&&tthis.getContext(r,s,n),p=c.resolveNamedOptions(f,g,m,u);return p.$shared&&(p.$shared=l,o[i]=Object.freeze(W0(p,l))),p}_resolveAnimations(t,n,r){const s=this.chart,o=this._cachedDataOpts,i=`animation-${n}`,a=o[i];if(a)return a;let l;if(s.options.animation!==!1){const d=this.chart.config,u=d.datasetAnimationScopeKeys(this._type,n),f=d.getOptionScopes(this.getDataset(),u);l=d.createResolver(f,this.getContext(t,r,n))}const c=new z0(s,l&&l.animations);return l&&l._cacheable&&(o[i]=Object.freeze(c)),c}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,n){return!n||pd(t)||this.chart._animationsDisabled}_getSharedOptions(t,n){const r=this.resolveDataElementOptions(t,n),s=this._sharedOptions,o=this.getSharedOptions(r),i=this.includeOptions(n,o)||o!==s;return this.updateSharedOptions(o,n,r),{sharedOptions:o,includeOptions:i}}updateElement(t,n,r,s){pd(s)?Object.assign(t,r):this._resolveAnimations(n,s).update(t,r)}updateSharedOptions(t,n,r){t&&!pd(n)&&this._resolveAnimations(void 0,n).update(t,r)}_setStyle(t,n,r,s){t.active=s;const o=this.getStyle(n,s);this._resolveAnimations(n,r,s).update(t,{options:!s&&this.getSharedOptions(o)||o})}removeHoverStyle(t,n,r){this._setStyle(t,r,"active",!1)}setHoverStyle(t,n,r){this._setStyle(t,r,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const n=this._data,r=this._cachedMeta.data;for(const[a,l,c]of this._syncList)this[a](l,c);this._syncList=[];const s=r.length,o=n.length,i=Math.min(o,s);i&&this.parse(0,i),o>s?this._insertElements(s,o-s,t):o{for(c.length+=n,a=c.length-1;a>=i;a--)c[a]=c[a-n]};for(l(o),a=t;as-o))}return e._cache.$bar}function RN(e){const t=e.iScale,n=JN(t,e.type);let r=t._length,s,o,i,a;const l=()=>{i===32767||i===-32768||(Mo(a)&&(r=Math.min(r,Math.abs(i-a)||r)),a=i)};for(s=0,o=n.length;s0?s[e-1]:null,a=eMath.abs(a)&&(l=a,c=i),t[n.axis]=c,t._custom={barStart:l,barEnd:c,start:s,end:o,min:i,max:a}}function G0(e,t,n,r){return Ot(e)?nT(e,t,n,r):t[n.axis]=n.parse(e,r),t}function Y0(e,t,n,r){const s=e.iScale,o=e.vScale,i=s.getLabels(),a=s===o,l=[];let c,d,u,f;for(c=n,d=n+r;c=n?1:-1)}function sT(e){let t,n,r,s,o;return e.horizontal?(t=e.base>e.x,n="left",r="right"):(t=e.based.controller.options.grouped),o=r.options.stacked,i=[],a=this._cachedMeta.controller.getParsed(n),l=a&&a[r.axis],c=d=>{const u=d._parsed.find(g=>g[r.axis]===l),f=u&&u[d.vScale.axis];if(ut(f)||isNaN(f))return!0};for(const d of s)if(!(n!==void 0&&c(d))&&((o===!1||i.indexOf(d.stack)===-1||o===void 0&&d.stack===void 0)&&i.push(d.stack),d.index===t))break;return i.length||i.push(void 0),i}_getStackCount(t){return this._getStacks(void 0,t).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const t=this.chart.scales,n=this.chart.options.indexAxis;return Object.keys(t).filter(r=>t[r].axis===n).shift()}_getAxis(){const t={},n=this.getFirstScaleIdForIndexAxis();for(const r of this.chart.data.datasets)t[et(this.chart.options.indexAxis==="x"?r.xAxisID:r.yAxisID,n)]=!0;return Object.keys(t)}_getStackIndex(t,n,r){const s=this._getStacks(t,r),o=n!==void 0?s.indexOf(n):-1;return o===-1?s.length-1:o}_getRuler(){const t=this.options,n=this._cachedMeta,r=n.iScale,s=[];let o,i;for(o=0,i=n.data.length;oga(x,a,l,!0)?1:Math.max(M,M*n,C,C*n),m=(x,M,C)=>ga(x,a,l,!0)?-1:Math.min(M,M*n,C,C*n),p=g(0,c,u),y=g(At,d,f),b=m(ft,c,u),_=m(ft+At,d,f);r=(p-b)/2,s=(y-_)/2,o=-(p+b)/2,i=-(y+_)/2}return{ratioX:r,ratioY:s,offsetX:o,offsetY:i}}class Do extends As{constructor(t,n){super(t,n),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,n){const r=this.getDataset().data,s=this._cachedMeta;if(this._parsing===!1)s._parsed=r;else{let o=l=>+r[l];if(Re(r[t])){const{key:l="value"}=this._parsing;o=c=>+Pr(r[c],l)}let i,a;for(i=t,a=t+n;i0&&!isNaN(t)?Nt*(Math.abs(t)/n):0}getLabelAndValue(t){const n=this._cachedMeta,r=this.chart,s=r.data.labels||[],o=nd(n._parsed[t],r.options.locale);return{label:s[t]||"",value:o}}getMaxBorderWidth(t){let n=0;const r=this.chart;let s,o,i,a,l;if(!t){for(s=0,o=r.data.datasets.length;st!=="spacing",_indexable:t=>t!=="spacing"&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")}),$e(Do,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const n=t.data,{labels:{pointStyle:r,textAlign:s,color:o,useBorderRadius:i,borderRadius:a}}=t.legend.options;return n.labels.length&&n.datasets.length?n.labels.map((l,c)=>{const u=t.getDatasetMeta(0).controller.getStyle(c);return{text:l,fillStyle:u.backgroundColor,fontColor:o,hidden:!t.getDataVisibility(c),lineDash:u.borderDash,lineDashOffset:u.borderDashOffset,lineJoin:u.borderJoinStyle,lineWidth:u.borderWidth,strokeStyle:u.borderColor,textAlign:s,pointStyle:r,borderRadius:i&&(a||u.borderRadius),index:c}}):[]}},onClick(t,n,r){r.chart.toggleDataVisibility(n.index),r.chart.update()}}}});function Br(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class bd{constructor(t){$e(this,"options");this.options=t||{}}static override(t){Object.assign(bd.prototype,t)}init(){}formats(){return Br()}parse(){return Br()}format(){return Br()}add(){return Br()}diff(){return Br()}startOf(){return Br()}endOf(){return Br()}}var cT={_date:bd};function dT(e,t,n,r){const{controller:s,data:o,_sorted:i}=e,a=s._cachedMeta.iScale,l=e.dataset&&e.dataset.options?e.dataset.options.spanGaps:null;if(a&&t===a.axis&&t!=="r"&&i&&o.length){const c=a._reversePixels?qM:Rc;if(r){if(s._sharedOptions){const d=o[0],u=typeof d.getRange=="function"&&d.getRange(t);if(u){const f=c(o,t,n-u),g=c(o,t,n+u);return{lo:f.lo,hi:g.hi}}}}else{const d=c(o,t,n);if(l){const{vScale:u}=s._cachedMeta,{_parsed:f}=e,g=f.slice(0,d.lo+1).reverse().findIndex(p=>!ut(p[u.axis]));d.lo-=Math.max(0,g);const m=f.slice(d.hi).findIndex(p=>!ut(p[u.axis]));d.hi+=Math.max(0,m)}return d}}return{lo:0,hi:o.length-1}}function ka(e,t,n,r,s){const o=e.getSortedVisibleDatasetMetas(),i=n[t];for(let a=0,l=o.length;a{l[i]&&l[i](t[n],s)&&(o.push({element:l,datasetIndex:c,index:d}),a=a||l.inRange(t.x,t.y,s))}),r&&!a?[]:o}var gT={modes:{index(e,t,n,r){const s=Ur(t,e),o=n.axis||"x",i=n.includeInvisible||!1,a=n.intersect?yd(e,s,o,r,i):vd(e,s,o,!1,r,i),l=[];return a.length?(e.getSortedVisibleDatasetMetas().forEach(c=>{const d=a[0].index,u=c.data[d];u&&!u.skip&&l.push({element:u,datasetIndex:c.index,index:d})}),l):[]},dataset(e,t,n,r){const s=Ur(t,e),o=n.axis||"xy",i=n.includeInvisible||!1;let a=n.intersect?yd(e,s,o,r,i):vd(e,s,o,!1,r,i);if(a.length>0){const l=a[0].datasetIndex,c=e.getDatasetMeta(l).data;a=[];for(let d=0;dn.pos===t)}function Q0(e,t){return e.filter(n=>Z0.indexOf(n.pos)===-1&&n.box.axis===t)}function Lo(e,t){return e.sort((n,r)=>{const s=t?r:n,o=t?n:r;return s.weight===o.weight?s.index-o.index:s.weight-o.weight})}function pT(e){const t=[];let n,r,s,o,i,a;for(n=0,r=(e||[]).length;nc.box.fullSize),!0),r=Lo(Io(t,"left"),!0),s=Lo(Io(t,"right")),o=Lo(Io(t,"top"),!0),i=Lo(Io(t,"bottom")),a=Q0(t,"x"),l=Q0(t,"y");return{fullSize:n,leftAndTop:r.concat(o),rightAndBottom:s.concat(l).concat(i).concat(a),chartArea:Io(t,"chartArea"),vertical:r.concat(s).concat(l),horizontal:o.concat(i).concat(a)}}function J0(e,t,n,r){return Math.max(e[n],t[n])+Math.max(e[r],t[r])}function R0(e,t){e.top=Math.max(e.top,t.top),e.left=Math.max(e.left,t.left),e.bottom=Math.max(e.bottom,t.bottom),e.right=Math.max(e.right,t.right)}function vT(e,t,n,r){const{pos:s,box:o}=n,i=e.maxPadding;if(!Re(s)){n.size&&(e[s]-=n.size);const u=r[n.stack]||{size:0,count:1};u.size=Math.max(u.size,n.horizontal?o.height:o.width),n.size=u.size/u.count,e[s]+=n.size}o.getPadding&&R0(i,o.getPadding());const a=Math.max(0,t.outerWidth-J0(i,e,"left","right")),l=Math.max(0,t.outerHeight-J0(i,e,"top","bottom")),c=a!==e.w,d=l!==e.h;return e.w=a,e.h=l,n.horizontal?{same:c,other:d}:{same:d,other:c}}function xT(e){const t=e.maxPadding;function n(r){const s=Math.max(t[r]-e[r],0);return e[r]+=s,s}e.y+=n("top"),e.x+=n("left"),n("right"),n("bottom")}function wT(e,t){const n=t.maxPadding;function r(s){const o={left:0,top:0,right:0,bottom:0};return s.forEach(i=>{o[i]=Math.max(t[i],n[i])}),o}return r(e?["left","right"]:["top","bottom"])}function Po(e,t,n,r){const s=[];let o,i,a,l,c,d;for(o=0,i=e.length,c=0;o{typeof p.beforeLayout=="function"&&p.beforeLayout()});const d=l.reduce((p,y)=>y.box.options&&y.box.options.display===!1?p:p+1,0)||1,u=Object.freeze({outerWidth:t,outerHeight:n,padding:s,availableWidth:o,availableHeight:i,vBoxMaxWidth:o/2/d,hBoxMaxHeight:i/2}),f=Object.assign({},s);R0(f,xn(r));const g=Object.assign({maxPadding:f,w:o,h:i,x:s.left,y:s.top},s),m=bT(l.concat(c),u);Po(a.fullSize,g,u,m),Po(l,g,u,m),Po(c,g,u,m)&&Po(l,g,u,m),xT(g),em(a.leftAndTop,g,u,m),g.x+=g.w,g.y+=g.h,em(a.rightAndBottom,g,u,m),e.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},ct(a.chartArea,p=>{const y=p.box;Object.assign(y,e.chartArea),y.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})})}};class tm{acquireContext(t,n){}releaseContext(t){return!1}addEventListener(t,n,r){}removeEventListener(t,n,r){}getDevicePixelRatio(){return 1}getMaximumSize(t,n,r,s){return n=Math.max(0,n||t.width),r=r||t.height,{width:n,height:Math.max(0,s?Math.floor(n/s):r)}}isAttached(t){return!0}updateConfig(t){}}class kT extends tm{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const Sa="$chartjs",CT={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},nm=e=>e===null||e==="";function ST(e,t){const n=e.style,r=e.getAttribute("height"),s=e.getAttribute("width");if(e[Sa]={initial:{height:r,width:s,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",n.boxSizing=n.boxSizing||"border-box",nm(s)){const o=L0(e,"width");o!==void 0&&(e.width=o)}if(nm(r))if(e.style.height==="")e.height=e.width/(t||2);else{const o=L0(e,"height");o!==void 0&&(e.height=o)}return e}const rm=IN?{passive:!0}:!1;function _T(e,t,n){e&&e.addEventListener(t,n,rm)}function ET(e,t,n){e&&e.canvas&&e.canvas.removeEventListener(t,n,rm)}function MT(e,t){const n=CT[e.type]||e.type,{x:r,y:s}=Ur(e,t);return{type:n,chart:t,native:e,x:r!==void 0?r:null,y:s!==void 0?s:null}}function _a(e,t){for(const n of e)if(n===t||n.contains(t))return!0}function NT(e,t,n){const r=e.canvas,s=new MutationObserver(o=>{let i=!1;for(const a of o)i=i||_a(a.addedNodes,r),i=i&&!_a(a.removedNodes,r);i&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}function TT(e,t,n){const r=e.canvas,s=new MutationObserver(o=>{let i=!1;for(const a of o)i=i||_a(a.removedNodes,r),i=i&&!_a(a.addedNodes,r);i&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}const $o=new Map;let sm=0;function om(){const e=window.devicePixelRatio;e!==sm&&(sm=e,$o.forEach((t,n)=>{n.currentDevicePixelRatio!==e&&t()}))}function OT(e,t){$o.size||window.addEventListener("resize",om),$o.set(e,t)}function AT(e){$o.delete(e),$o.size||window.removeEventListener("resize",om)}function DT(e,t,n){const r=e.canvas,s=r&&fd(r);if(!s)return;const o=g0((a,l)=>{const c=s.clientWidth;n(a,l),c{const l=a[0],c=l.contentRect.width,d=l.contentRect.height;c===0&&d===0||o(c,d)});return i.observe(s),OT(e,o),i}function xd(e,t,n){n&&n.disconnect(),t==="resize"&&AT(e)}function IT(e,t,n){const r=e.canvas,s=g0(o=>{e.ctx!==null&&n(MT(o,e))},e);return _T(r,t,s),s}class LT extends tm{acquireContext(t,n){const r=t&&t.getContext&&t.getContext("2d");return r&&r.canvas===t?(ST(t,n),r):null}releaseContext(t){const n=t.canvas;if(!n[Sa])return!1;const r=n[Sa].initial;["height","width"].forEach(o=>{const i=r[o];ut(i)?n.removeAttribute(o):n.setAttribute(o,i)});const s=r.style||{};return Object.keys(s).forEach(o=>{n.style[o]=s[o]}),n.width=n.width,delete n[Sa],!0}addEventListener(t,n,r){this.removeEventListener(t,n);const s=t.$proxies||(t.$proxies={}),i={attach:NT,detach:TT,resize:DT}[n]||IT;s[n]=i(t,n,r)}removeEventListener(t,n){const r=t.$proxies||(t.$proxies={}),s=r[n];if(!s)return;({attach:xd,detach:xd,resize:xd}[n]||ET)(t,n,s),r[n]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,n,r,s){return DN(t,n,r,s)}isAttached(t){const n=t&&fd(t);return!!(n&&n.isConnected)}}function PT(e){return!ud()||typeof OffscreenCanvas<"u"&&e instanceof OffscreenCanvas?kT:LT}let jr=(Ha=class{constructor(){$e(this,"x");$e(this,"y");$e(this,"active",!1);$e(this,"options");$e(this,"$animations")}tooltipPosition(t){const{x:n,y:r}=this.getProps(["x","y"],t);return{x:n,y:r}}hasValue(){return ha(this.x)&&ha(this.y)}getProps(t,n){const r=this.$animations;if(!n||!r)return this;const s={};return t.forEach(o=>{s[o]=r[o]&&r[o].active()?r[o]._to:this[o]}),s}},$e(Ha,"defaults",{}),$e(Ha,"defaultRoutes"),Ha);function $T(e,t){const n=e.options.ticks,r=FT(e),s=Math.min(n.maxTicksLimit||r,r),o=n.major.enabled?VT(t):[],i=o.length,a=o[0],l=o[i-1],c=[];if(i>s)return HT(t,c,o,i/s),c;const d=zT(o,t,s);if(i>0){let u,f;const g=i>1?Math.round((l-a)/(i-1)):null;for(Ea(t,c,d,ut(g)?0:a-g,a),u=0,f=i-1;us)return l}return Math.max(s,1)}function VT(e){const t=[];let n,r;for(n=0,r=e.length;ne==="left"?"right":e==="right"?"left":e,im=(e,t,n)=>t==="top"||t==="left"?e[t]+n:e[t]-n,am=(e,t)=>Math.min(t||e,e);function lm(e,t){const n=[],r=e.length/t,s=e.length;let o=0;for(;oi+a)))return l}function WT(e,t){ct(e,n=>{const r=n.gc,s=r.length/2;let o;if(s>t){for(o=0;or?r:n,r=s&&n>r?n:r,{min:Gn(n,Gn(r,n)),max:Gn(r,Gn(n,r))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){bt(this.options.beforeUpdate,[this])}update(t,n,r){const{beginAtZero:s,grace:o,ticks:i}=this.options,a=i.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=n,this._margins=r=Object.assign({left:0,right:0,top:0,bottom:0},r),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+r.left+r.right:this.height+r.top+r.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=bN(this,o,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const l=a=o||r<=1||!this.isHorizontal()){this.labelRotation=s;return}const d=this._getLabelSizes(),u=d.widest.width,f=d.highest.height,g=rn(this.chart.width-u,0,this.maxWidth);a=t.offset?this.maxWidth/r:g/(r-1),u+6>a&&(a=g/(r-(t.offset?.5:1)),l=this.maxHeight-Fo(t.grid)-n.padding-cm(t.title,this.chart.options.font),c=Math.sqrt(u*u+f*f),i=YM(Math.min(Math.asin(rn((d.highest.height+6)/a,-1,1)),Math.asin(rn(l/c,-1,1))-Math.asin(rn(f/c,-1,1)))),i=Math.max(s,Math.min(o,i))),this.labelRotation=i}afterCalculateLabelRotation(){bt(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){bt(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:n,options:{ticks:r,title:s,grid:o}}=this,i=this._isVisible(),a=this.isHorizontal();if(i){const l=cm(s,n.options.font);if(a?(t.width=this.maxWidth,t.height=Fo(o)+l):(t.height=this.maxHeight,t.width=Fo(o)+l),r.display&&this.ticks.length){const{first:c,last:d,widest:u,highest:f}=this._getLabelSizes(),g=r.padding*2,m=tr(this.labelRotation),p=Math.cos(m),y=Math.sin(m);if(a){const b=r.mirror?0:y*u.width+p*f.height;t.height=Math.min(this.maxHeight,t.height+b+g)}else{const b=r.mirror?0:p*u.width+y*f.height;t.width=Math.min(this.maxWidth,t.width+b+g)}this._calculatePadding(c,d,y,p)}}this._handleMargins(),a?(this.width=this._length=n.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=n.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,n,r,s){const{ticks:{align:o,padding:i},position:a}=this.options,l=this.labelRotation!==0,c=a!=="top"&&this.axis==="x";if(this.isHorizontal()){const d=this.getPixelForTick(0)-this.left,u=this.right-this.getPixelForTick(this.ticks.length-1);let f=0,g=0;l?c?(f=s*t.width,g=r*n.height):(f=r*t.height,g=s*n.width):o==="start"?g=n.width:o==="end"?f=t.width:o!=="inner"&&(f=t.width/2,g=n.width/2),this.paddingLeft=Math.max((f-d+i)*this.width/(this.width-d),0),this.paddingRight=Math.max((g-u+i)*this.width/(this.width-u),0)}else{let d=n.height/2,u=t.height/2;o==="start"?(d=0,u=t.height):o==="end"&&(d=n.height,u=0),this.paddingTop=d+i,this.paddingBottom=u+i}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){bt(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:n}=this.options;return n==="top"||n==="bottom"||t==="x"}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){this.beforeTickToLabelConversion(),this.generateTickLabels(t);let n,r;for(n=0,r=t.length;n({width:i[w]||0,height:a[w]||0});return{first:S(0),last:S(n-1),widest:S(k),highest:S(N),widths:i,heights:a}}getLabelForValue(t){return t}getPixelForValue(t,n){return NaN}getValueForPixel(t){}getPixelForTick(t){const n=this.ticks;return t<0||t>n.length-1?null:this.getPixelForValue(n[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const n=this._startPixel+t*this._length;return KM(this._alignToPixels?Vr(this.chart,n,0):n)}getDecimalForPixel(t){const n=(t-this._startPixel)/this._length;return this._reversePixels?1-n:n}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:n}=this;return t<0&&n<0?n:t>0&&n>0?t:0}getContext(t){const n=this.ticks||[];if(t>=0&&ta*s?a/r:l/s:l*s0}_computeGridLineItems(t){const n=this.axis,r=this.chart,s=this.options,{grid:o,position:i,border:a}=s,l=o.offset,c=this.isHorizontal(),u=this.ticks.length+(l?1:0),f=Fo(o),g=[],m=a.setContext(this.getContext()),p=m.display?m.width:0,y=p/2,b=function(O){return Vr(r,O,p)};let _,x,M,C,k,N,S,w,E,D,P,L;if(i==="top")_=b(this.bottom),N=this.bottom-f,w=_-y,D=b(t.top)+y,L=t.bottom;else if(i==="bottom")_=b(this.top),D=t.top,L=b(t.bottom)-y,N=_+y,w=this.top+f;else if(i==="left")_=b(this.right),k=this.right-f,S=_-y,E=b(t.left)+y,P=t.right;else if(i==="right")_=b(this.left),E=t.left,P=b(t.right)-y,k=_+y,S=this.left+f;else if(n==="x"){if(i==="center")_=b((t.top+t.bottom)/2+.5);else if(Re(i)){const O=Object.keys(i)[0],T=i[O];_=b(this.chart.scales[O].getPixelForValue(T))}D=t.top,L=t.bottom,N=_+y,w=N+f}else if(n==="y"){if(i==="center")_=b((t.left+t.right)/2);else if(Re(i)){const O=Object.keys(i)[0],T=i[O];_=b(this.chart.scales[O].getPixelForValue(T))}k=_-y,S=k-f,E=t.left,P=t.right}const $=et(s.ticks.maxTicksLimit,u),A=Math.max(1,Math.ceil(u/$));for(x=0;x0&&(X-=Y/2);break}G={left:X,top:V,width:Y+U.width,height:W+U.height,color:A.backdropColor}}y.push({label:M,font:w,textOffset:P,options:{rotation:p,color:T,strokeColor:z,strokeWidth:H,textAlign:j,textBaseline:L,translation:[C,k],backdrop:G}})}return y}_getXAxisLabelAlignment(){const{position:t,ticks:n}=this.options;if(-tr(this.labelRotation))return t==="top"?"left":"right";let s="center";return n.align==="start"?s="left":n.align==="end"?s="right":n.align==="inner"&&(s="inner"),s}_getYAxisLabelAlignment(t){const{position:n,ticks:{crossAlign:r,mirror:s,padding:o}}=this.options,i=this._getLabelSizes(),a=t+o,l=i.widest.width;let c,d;return n==="left"?s?(d=this.right+o,r==="near"?c="left":r==="center"?(c="center",d+=l/2):(c="right",d+=l)):(d=this.right-a,r==="near"?c="right":r==="center"?(c="center",d-=l/2):(c="left",d=this.left)):n==="right"?s?(d=this.left+o,r==="near"?c="right":r==="center"?(c="center",d-=l/2):(c="left",d-=l)):(d=this.left+a,r==="near"?c="left":r==="center"?(c="center",d+=l/2):(c="right",d=this.right)):c="right",{textAlign:c,x:d}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,n=this.options.position;if(n==="left"||n==="right")return{top:0,left:this.left,bottom:t.height,right:this.right};if(n==="top"||n==="bottom")return{top:this.top,left:0,bottom:this.bottom,right:t.width}}drawBackground(){const{ctx:t,options:{backgroundColor:n},left:r,top:s,width:o,height:i}=this;n&&(t.save(),t.fillStyle=n,t.fillRect(r,s,o,i),t.restore())}getLineWidthForValue(t){const n=this.options.grid;if(!this._isVisible()||!n.display)return 0;const s=this.ticks.findIndex(o=>o.value===t);return s>=0?n.setContext(this.getContext(s)).lineWidth:0}drawGrid(t){const n=this.options.grid,r=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let o,i;const a=(l,c,d)=>{!d.width||!d.color||(r.save(),r.lineWidth=d.width,r.strokeStyle=d.color,r.setLineDash(d.borderDash||[]),r.lineDashOffset=d.borderDashOffset,r.beginPath(),r.moveTo(l.x,l.y),r.lineTo(c.x,c.y),r.stroke(),r.restore())};if(n.display)for(o=0,i=s.length;o{this.draw(o)}}]:[{z:r,draw:o=>{this.drawBackground(),this.drawGrid(o),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:n,draw:o=>{this.drawLabels(o)}}]}getMatchingVisibleMetas(t){const n=this.chart.getSortedVisibleDatasetMetas(),r=this.axis+"AxisID",s=[];let o,i;for(o=0,i=n.length;o{const r=n.split("."),s=r.pop(),o=[e].concat(r).join("."),i=t[n].split("."),a=i.pop(),l=i.join(".");St.route(o,s,l,a)})}function QT(e){return"id"in e&&"defaults"in e}class JT{constructor(){this.controllers=new Ma(As,"datasets",!0),this.elements=new Ma(jr,"elements"),this.plugins=new Ma(Object,"plugins"),this.scales=new Ma(Ds,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,n,r){[...n].forEach(s=>{const o=r||this._getRegistryForType(s);r||o.isForType(s)||o===this.plugins&&s.id?this._exec(t,o,s):ct(s,i=>{const a=r||this._getRegistryForType(i);this._exec(t,a,i)})})}_exec(t,n,r){const s=Qc(t);bt(r["before"+s],[],r),n[t](r),bt(r["after"+s],[],r)}_getRegistryForType(t){for(let n=0;no.filter(a=>!i.some(l=>a.plugin.id===l.plugin.id));this._notify(s(n,r),t,"stop"),this._notify(s(r,n),t,"start")}}function eO(e){const t={},n=[],r=Object.keys(Xn.plugins.items);for(let o=0;o1&&dm(e[0].toLowerCase());if(r)return r}throw new Error(`Cannot determine type of '${e}' axis. Please provide 'axis' or 'position' option.`)}function um(e,t,n){if(n[t+"AxisID"]===e)return{axis:t}}function aO(e,t){if(t.data&&t.data.datasets){const n=t.data.datasets.filter(r=>r.xAxisID===e||r.yAxisID===e);if(n.length)return um(e,"x",n[0])||um(e,"y",n[0])}return{}}function lO(e,t){const n=zr[e.type]||{scales:{}},r=t.scales||{},s=wd(e.type,t),o=Object.create(null);return Object.keys(r).forEach(i=>{const a=r[i];if(!Re(a))return console.error(`Invalid scale configuration for scale: ${i}`);if(a._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${i}`);const l=kd(i,a,aO(i,e),St.scales[a.type]),c=oO(l,s),d=n.scales||{};o[i]=Eo(Object.create(null),[{axis:l},a,d[l],d[c]])}),e.data.datasets.forEach(i=>{const a=i.type||e.type,l=i.indexAxis||wd(a,t),d=(zr[a]||{}).scales||{};Object.keys(d).forEach(u=>{const f=sO(u,l),g=i[f+"AxisID"]||f;o[g]=o[g]||Object.create(null),Eo(o[g],[{axis:f},r[g],d[u]])})}),Object.keys(o).forEach(i=>{const a=o[i];Eo(a,[St.scales[a.type],St.scale])}),o}function fm(e){const t=e.options||(e.options={});t.plugins=et(t.plugins,{}),t.scales=lO(e,t)}function hm(e){return e=e||{},e.datasets=e.datasets||[],e.labels=e.labels||[],e}function cO(e){return e=e||{},e.data=hm(e.data),fm(e),e}const gm=new Map,pm=new Set;function Na(e,t){let n=gm.get(e);return n||(n=t(),gm.set(e,n),pm.add(n)),n}const zo=(e,t,n)=>{const r=Pr(t,n);r!==void 0&&e.add(r)};class dO{constructor(t){this._config=cO(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=hm(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),fm(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return Na(t,()=>[[`datasets.${t}`,""]])}datasetAnimationScopeKeys(t,n){return Na(`${t}.transition.${n}`,()=>[[`datasets.${t}.transitions.${n}`,`transitions.${n}`],[`datasets.${t}`,""]])}datasetElementScopeKeys(t,n){return Na(`${t}-${n}`,()=>[[`datasets.${t}.elements.${n}`,`datasets.${t}`,`elements.${n}`,""]])}pluginScopeKeys(t){const n=t.id,r=this.type;return Na(`${r}-plugin-${n}`,()=>[[`plugins.${n}`,...t.additionalOptionScopes||[]]])}_cachedScopes(t,n){const r=this._scopeCache;let s=r.get(t);return(!s||n)&&(s=new Map,r.set(t,s)),s}getOptionScopes(t,n,r){const{options:s,type:o}=this,i=this._cachedScopes(t,r),a=i.get(n);if(a)return a;const l=new Set;n.forEach(d=>{t&&(l.add(t),d.forEach(u=>zo(l,t,u))),d.forEach(u=>zo(l,s,u)),d.forEach(u=>zo(l,zr[o]||{},u)),d.forEach(u=>zo(l,St,u)),d.forEach(u=>zo(l,rd,u))});const c=Array.from(l);return c.length===0&&c.push(Object.create(null)),pm.has(n)&&i.set(n,c),c}chartOptionScopes(){const{options:t,type:n}=this;return[t,zr[n]||{},St.datasets[n]||{},{type:n},St,rd]}resolveNamedOptions(t,n,r,s=[""]){const o={$shared:!0},{resolver:i,subPrefixes:a}=mm(this._resolverCache,t,s);let l=i;if(fO(i,n)){o.$shared=!1,r=br(r)?r():r;const c=this.createResolver(t,r,a);l=Ts(i,r,c)}for(const c of n)o[c]=l[c];return o}createResolver(t,n,r=[""],s){const{resolver:o}=mm(this._resolverCache,t,r);return Re(n)?Ts(o,n,void 0,s):o}}function mm(e,t,n){let r=e.get(t);r||(r=new Map,e.set(t,r));const s=n.join();let o=r.get(s);return o||(o={resolver:ld(t,n),subPrefixes:n.filter(a=>!a.toLowerCase().includes("hover"))},r.set(s,o)),o}const uO=e=>Re(e)&&Object.getOwnPropertyNames(e).some(t=>br(e[t]));function fO(e,t){const{isScriptable:n,isIndexable:r}=M0(e);for(const s of t){const o=n(s),i=r(s),a=(i||o)&&e[s];if(o&&(br(a)||uO(a))||i&&Ot(a))return!0}return!1}var hO="4.5.1";const gO=["top","bottom","left","right","chartArea"];function bm(e,t){return e==="top"||e==="bottom"||gO.indexOf(e)===-1&&t==="x"}function ym(e,t){return function(n,r){return n[e]===r[e]?n[t]-r[t]:n[e]-r[e]}}function vm(e){const t=e.chart,n=t.options.animation;t.notifyPlugins("afterRender"),bt(n&&n.onComplete,[e],t)}function pO(e){const t=e.chart,n=t.options.animation;bt(n&&n.onProgress,[e],t)}function xm(e){return ud()&&typeof e=="string"?e=document.getElementById(e):e&&e.length&&(e=e[0]),e&&e.canvas&&(e=e.canvas),e}const Ta={},wm=e=>{const t=xm(e);return Object.values(Ta).filter(n=>n.canvas===t).pop()};function mO(e,t,n){const r=Object.keys(e);for(const s of r){const o=+s;if(o>=t){const i=e[s];delete e[s],(n>0||o>t)&&(e[o+n]=i)}}}function bO(e,t,n,r){return!n||e.type==="mouseout"?null:r?t:e}let Vo=(wr=class{static register(...t){Xn.add(...t),km()}static unregister(...t){Xn.remove(...t),km()}constructor(t,n){const r=this.config=new dO(n),s=xm(t),o=wm(s);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const i=r.createResolver(r.chartOptionScopes(),this.getContext());this.platform=new(r.platform||PT(s)),this.platform.updateConfig(r);const a=this.platform.acquireContext(s,i.aspectRatio),l=a&&a.canvas,c=l&&l.height,d=l&&l.width;if(this.id=LM(),this.ctx=a,this.canvas=l,this.width=d,this.height=c,this._options=i,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new RT,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=JM(u=>this.update(u),i.resizeDelay||0),this._dataChanges=[],Ta[this.id]=this,!a||!l){console.error("Failed to create chart: can't acquire context from the given item");return}nr.listen(this,"complete",vm),nr.listen(this,"progress",pO),this._initialize(),this.attached&&this.update()}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:n},width:r,height:s,_aspectRatio:o}=this;return ut(t)?n&&o?o:s?r/s:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return Xn}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():I0(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return k0(this.canvas,this.ctx),this}stop(){return nr.stop(this),this}resize(t,n){nr.running(this)?this._resizeBeforeDraw={width:t,height:n}:this._resize(t,n)}_resize(t,n){const r=this.options,s=this.canvas,o=r.maintainAspectRatio&&this.aspectRatio,i=this.platform.getMaximumSize(s,t,n,o),a=r.devicePixelRatio||this.platform.getDevicePixelRatio(),l=this.width?"resize":"attach";this.width=i.width,this.height=i.height,this._aspectRatio=this.aspectRatio,I0(this,a,!0)&&(this.notifyPlugins("resize",{size:i}),bt(r.onResize,[this,i],this),this.attached&&this._doResize(l)&&this.render())}ensureScalesHaveIDs(){const n=this.options.scales||{};ct(n,(r,s)=>{r.id=s})}buildOrUpdateScales(){const t=this.options,n=t.scales,r=this.scales,s=Object.keys(r).reduce((i,a)=>(i[a]=!1,i),{});let o=[];n&&(o=o.concat(Object.keys(n).map(i=>{const a=n[i],l=kd(i,a),c=l==="r",d=l==="x";return{options:a,dposition:c?"chartArea":d?"bottom":"left",dtype:c?"radialLinear":d?"category":"linear"}}))),ct(o,i=>{const a=i.options,l=a.id,c=kd(l,a),d=et(a.type,i.dtype);(a.position===void 0||bm(a.position,c)!==bm(i.dposition))&&(a.position=i.dposition),s[l]=!0;let u=null;if(l in r&&r[l].type===d)u=r[l];else{const f=Xn.getScale(d);u=new f({id:l,type:d,ctx:this.ctx,chart:this}),r[u.id]=u}u.init(a,t)}),ct(s,(i,a)=>{i||delete r[a]}),ct(r,i=>{wn.configure(this,i,i.options),wn.addBox(this,i)})}_updateMetasets(){const t=this._metasets,n=this.data.datasets.length,r=t.length;if(t.sort((s,o)=>s.index-o.index),r>n){for(let s=n;sn.length&&delete this._stacks,t.forEach((r,s)=>{n.filter(o=>o===r._dataset).length===0&&this._destroyDatasetMeta(s)})}buildOrUpdateControllers(){const t=[],n=this.data.datasets;let r,s;for(this._removeUnreferencedMetasets(),r=0,s=n.length;r{this.getDatasetMeta(n).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const n=this.config;n.update();const r=this._options=n.createResolver(n.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!r.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0})===!1)return;const o=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let i=0;for(let c=0,d=this.data.datasets.length;c{c.reset()}),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(ym("z","_idx"));const{_active:a,_lastEvent:l}=this;l?this._eventHandler(l,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){ct(this.scales,t=>{wn.removeBox(this,t)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,n=new Set(Object.keys(this._listeners)),r=new Set(t.events);(!s0(n,r)||!!this._responsiveListeners!==t.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,n=this._getUniformDataChanges()||[];for(const{method:r,start:s,count:o}of n){const i=r==="_removeElements"?-o:o;mO(t,s,i)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const n=this.data.datasets.length,r=o=>new Set(t.filter(i=>i[0]===o).map((i,a)=>a+","+i.splice(1).join(","))),s=r(0);for(let o=1;oo.split(",")).map(o=>({method:o[1],start:+o[2],count:+o[3]}))}_updateLayout(t){if(this.notifyPlugins("beforeLayout",{cancelable:!0})===!1)return;wn.update(this,this.width,this.height,t);const n=this.chartArea,r=n.width<=0||n.height<=0;this._layers=[],ct(this.boxes,s=>{r&&s.position==="chartArea"||(s.configure&&s.configure(),this._layers.push(...s._layers()))},this),this._layers.forEach((s,o)=>{s._idx=o}),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})!==!1){for(let n=0,r=this.data.datasets.length;n=0;--n)this._drawDataset(t[n]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const n=this.ctx,r={meta:t,index:t.index,cancelable:!0},s=FN(this,t);this.notifyPlugins("beforeDatasetDraw",r)!==!1&&(s&&od(n,s),t.controller.draw(),s&&id(n),r.cancelable=!1,this.notifyPlugins("afterDatasetDraw",r))}isPointInArea(t){return _0(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,n,r,s){const o=gT.modes[n];return typeof o=="function"?o(this,t,r,s):[]}getDatasetMeta(t){const n=this.data.datasets[t],r=this._metasets;let s=r.filter(o=>o&&o._dataset===n).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:n&&n.order||0,index:t,_dataset:n,_parsed:[],_sorted:!1},r.push(s)),s}getContext(){return this.$context||(this.$context=Ns(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const n=this.data.datasets[t];if(!n)return!1;const r=this.getDatasetMeta(t);return typeof r.hidden=="boolean"?!r.hidden:!n.hidden}setDatasetVisibility(t,n){const r=this.getDatasetMeta(t);r.hidden=!n}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,n,r){const s=r?"show":"hide",o=this.getDatasetMeta(t),i=o.controller._resolveAnimations(void 0,s);Mo(n)?(o.data[n].hidden=!r,this.update()):(this.setDatasetVisibility(t,r),i.update(o,{visible:r}),this.update(a=>a.datasetIndex===t?s:void 0))}hide(t,n){this._updateVisibility(t,n,!1)}show(t,n){this._updateVisibility(t,n,!0)}_destroyDatasetMeta(t){const n=this._metasets[t];n&&n.controller&&n.controller._destroy(),delete this._metasets[t]}_stop(){let t,n;for(this.stop(),nr.remove(this),t=0,n=this.data.datasets.length;t{n.addEventListener(this,o,i),t[o]=i},s=(o,i,a)=>{o.offsetX=i,o.offsetY=a,this._eventHandler(o)};ct(this.options.events,o=>r(o,s))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,n=this.platform,r=(l,c)=>{n.addEventListener(this,l,c),t[l]=c},s=(l,c)=>{t[l]&&(n.removeEventListener(this,l,c),delete t[l])},o=(l,c)=>{this.canvas&&this.resize(l,c)};let i;const a=()=>{s("attach",a),this.attached=!0,this.resize(),r("resize",o),r("detach",i)};i=()=>{this.attached=!1,s("resize",o),this._stop(),this._resize(0,0),r("attach",a)},n.isAttached(this.canvas)?a():i()}unbindEvents(){ct(this._listeners,(t,n)=>{this.platform.removeEventListener(this,n,t)}),this._listeners={},ct(this._responsiveListeners,(t,n)=>{this.platform.removeEventListener(this,n,t)}),this._responsiveListeners=void 0}updateHoverStyle(t,n,r){const s=r?"set":"remove";let o,i,a,l;for(n==="dataset"&&(o=this.getDatasetMeta(t[0].datasetIndex),o.controller["_"+s+"DatasetHoverStyle"]()),a=0,l=t.length;a{const a=this.getDatasetMeta(o);if(!a)throw new Error("No dataset found at index "+o);return{datasetIndex:o,element:a.data[i],index:i}});!ca(r,n)&&(this._active=r,this._lastEvent=null,this._updateHoverStyles(r,n))}notifyPlugins(t,n,r){return this._plugins.notify(this,t,n,r)}isPluginEnabled(t){return this._plugins._cache.filter(n=>n.plugin.id===t).length===1}_updateHoverStyles(t,n,r){const s=this.options.hover,o=(l,c)=>l.filter(d=>!c.some(u=>d.datasetIndex===u.datasetIndex&&d.index===u.index)),i=o(n,t),a=r?t:o(t,n);i.length&&this.updateHoverStyle(i,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,n){const r={event:t,replay:n,cancelable:!0,inChartArea:this.isPointInArea(t)},s=i=>(i.options.events||this.options.events).includes(t.native.type);if(this.notifyPlugins("beforeEvent",r,s)===!1)return;const o=this._handleEvent(t,n,r.inChartArea);return r.cancelable=!1,this.notifyPlugins("afterEvent",r,s),(o||r.changed)&&this.render(),this}_handleEvent(t,n,r){const{_active:s=[],options:o}=this,i=n,a=this._getActiveElements(t,s,r,i),l=HM(t),c=bO(t,this._lastEvent,r,l);r&&(this._lastEvent=null,bt(o.onHover,[t,a,this],this),l&&bt(o.onClick,[t,a,this],this));const d=!ca(a,s);return(d||n)&&(this._active=a,this._updateHoverStyles(a,s,n)),this._lastEvent=c,d}_getActiveElements(t,n,r,s){if(t.type==="mouseout")return[];if(!r)return n;const o=this.options.hover;return this.getElementsAtEventForMode(t,o.mode,o,s)}},$e(wr,"defaults",St),$e(wr,"instances",Ta),$e(wr,"overrides",zr),$e(wr,"registry",Xn),$e(wr,"version",hO),$e(wr,"getChart",wm),wr);function km(){return ct(Vo.instances,e=>e._plugins.invalidate())}function yO(e,t,n){const{startAngle:r,x:s,y:o,outerRadius:i,innerRadius:a,options:l}=t,{borderWidth:c,borderJoinStyle:d}=l,u=Math.min(c/i,Yn(r-n));if(e.beginPath(),e.arc(s,o,i-c/2,r+u/2,n-u/2),a>0){const f=Math.min(c/a,Yn(r-n));e.arc(s,o,a+c/2,n-f/2,r+f/2,!0)}else{const f=Math.min(c/2,i*Yn(r-n));if(d==="round")e.arc(s,o,f,n-ft/2,r+ft/2,!0);else if(d==="bevel"){const g=2*f*f,m=-g*Math.cos(n+ft/2)+s,p=-g*Math.sin(n+ft/2)+o,y=g*Math.cos(r+ft/2)+s,b=g*Math.sin(r+ft/2)+o;e.lineTo(m,p),e.lineTo(y,b)}}e.closePath(),e.moveTo(0,0),e.rect(0,0,e.canvas.width,e.canvas.height),e.clip("evenodd")}function vO(e,t,n){const{startAngle:r,pixelMargin:s,x:o,y:i,outerRadius:a,innerRadius:l}=t;let c=s/a;e.beginPath(),e.arc(o,i,a,r-c,n+c),l>s?(c=s/l,e.arc(o,i,l,n+c,r-c,!0)):e.arc(o,i,s,n+At,r-At),e.closePath(),e.clip()}function xO(e){return ad(e,["outerStart","outerEnd","innerStart","innerEnd"])}function wO(e,t,n,r){const s=xO(e.options.borderRadius),o=(n-t)/2,i=Math.min(o,r*t/2),a=l=>{const c=(n-Math.min(o,l))*r/2;return rn(l,0,Math.min(o,c))};return{outerStart:a(s.outerStart),outerEnd:a(s.outerEnd),innerStart:rn(s.innerStart,0,i),innerEnd:rn(s.innerEnd,0,i)}}function Is(e,t,n,r){return{x:n+e*Math.cos(t),y:r+e*Math.sin(t)}}function Oa(e,t,n,r,s,o){const{x:i,y:a,startAngle:l,pixelMargin:c,innerRadius:d}=t,u=Math.max(t.outerRadius+r+n-c,0),f=d>0?d+r+n+c:0;let g=0;const m=s-l;if(r){const A=d>0?d-r:0,O=u>0?u-r:0,T=(A+O)/2,z=T!==0?m*T/(T+r):m;g=(m-z)/2}const p=Math.max(.001,m*u-n/ft)/u,y=(m-p)/2,b=l+y+g,_=s-y-g,{outerStart:x,outerEnd:M,innerStart:C,innerEnd:k}=wO(t,f,u,_-b),N=u-x,S=u-M,w=b+x/N,E=_-M/S,D=f+C,P=f+k,L=b+C/D,$=_-k/P;if(e.beginPath(),o){const A=(w+E)/2;if(e.arc(i,a,u,w,A),e.arc(i,a,u,A,E),M>0){const H=Is(S,E,i,a);e.arc(H.x,H.y,M,E,_+At)}const O=Is(P,_,i,a);if(e.lineTo(O.x,O.y),k>0){const H=Is(P,$,i,a);e.arc(H.x,H.y,k,_+At,$+Math.PI)}const T=(_-k/f+(b+C/f))/2;if(e.arc(i,a,f,_-k/f,T,!0),e.arc(i,a,f,T,b+C/f,!0),C>0){const H=Is(D,L,i,a);e.arc(H.x,H.y,C,L+Math.PI,b-At)}const z=Is(N,b,i,a);if(e.lineTo(z.x,z.y),x>0){const H=Is(N,w,i,a);e.arc(H.x,H.y,x,b-At,w)}}else{e.moveTo(i,a);const A=Math.cos(w)*u+i,O=Math.sin(w)*u+a;e.lineTo(A,O);const T=Math.cos(E)*u+i,z=Math.sin(E)*u+a;e.lineTo(T,z)}e.closePath()}function kO(e,t,n,r,s){const{fullCircles:o,startAngle:i,circumference:a}=t;let l=t.endAngle;if(o){Oa(e,t,n,r,l,s);for(let c=0;c=ft&&g===0&&d!=="miter"&&yO(e,t,p),o||(Oa(e,t,n,r,p,s),e.stroke())}class Ho extends jr{constructor(n){super();$e(this,"circumference");$e(this,"endAngle");$e(this,"fullCircles");$e(this,"innerRadius");$e(this,"outerRadius");$e(this,"pixelMargin");$e(this,"startAngle");this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,n&&Object.assign(this,n)}inRange(n,r,s){const o=this.getProps(["x","y"],s),{angle:i,distance:a}=c0(o,{x:n,y:r}),{startAngle:l,endAngle:c,innerRadius:d,outerRadius:u,circumference:f}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],s),g=(this.options.spacing+this.options.borderWidth)/2,m=et(f,c-l),p=ga(i,l,c)&&l!==c,y=m>=Nt||p,b=Fr(a,d+g,u+g);return y&&b}getCenterPoint(n){const{x:r,y:s,startAngle:o,endAngle:i,innerRadius:a,outerRadius:l}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],n),{offset:c,spacing:d}=this.options,u=(o+i)/2,f=(a+l+d+c)/2;return{x:r+Math.cos(u)*f,y:s+Math.sin(u)*f}}tooltipPosition(n){return this.getCenterPoint(n)}draw(n){const{options:r,circumference:s}=this,o=(r.offset||0)/4,i=(r.spacing||0)/2,a=r.circular;if(this.pixelMargin=r.borderAlign==="inner"?.33:0,this.fullCircles=s>Nt?Math.floor(s/Nt):0,s===0||this.innerRadius<0||this.outerRadius<0)return;n.save();const l=(this.startAngle+this.endAngle)/2;n.translate(Math.cos(l)*o,Math.sin(l)*o);const c=1-Math.sin(Math.min(ft,s||0)),d=o*c;n.fillStyle=r.backgroundColor,n.strokeStyle=r.borderColor,kO(n,this,d,i,a),CO(n,this,d,i,a),n.restore()}}$e(Ho,"id","arc"),$e(Ho,"defaults",{borderAlign:"center",borderColor:"#fff",borderDash:[],borderDashOffset:0,borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0,selfJoin:!1}),$e(Ho,"defaultRoutes",{backgroundColor:"backgroundColor"}),$e(Ho,"descriptors",{_scriptable:!0,_indexable:n=>n!=="borderDash"});function Cm(e,t){const{x:n,y:r,base:s,width:o,height:i}=e.getProps(["x","y","base","width","height"],t);let a,l,c,d,u;return e.horizontal?(u=i/2,a=Math.min(n,s),l=Math.max(n,s),c=r-u,d=r+u):(u=o/2,a=n-u,l=n+u,c=Math.min(r,s),d=Math.max(r,s)),{left:a,top:c,right:l,bottom:d}}function xr(e,t,n,r){return e?0:rn(t,n,r)}function SO(e,t,n){const r=e.options.borderWidth,s=e.borderSkipped,o=E0(r);return{t:xr(s.top,o.top,0,n),r:xr(s.right,o.right,0,t),b:xr(s.bottom,o.bottom,0,n),l:xr(s.left,o.left,0,t)}}function _O(e,t,n){const{enableBorderRadius:r}=e.getProps(["enableBorderRadius"]),s=e.options.borderRadius,o=Ms(s),i=Math.min(t,n),a=e.borderSkipped,l=r||Re(s);return{topLeft:xr(!l||a.top||a.left,o.topLeft,0,i),topRight:xr(!l||a.top||a.right,o.topRight,0,i),bottomLeft:xr(!l||a.bottom||a.left,o.bottomLeft,0,i),bottomRight:xr(!l||a.bottom||a.right,o.bottomRight,0,i)}}function EO(e){const t=Cm(e),n=t.right-t.left,r=t.bottom-t.top,s=SO(e,n/2,r/2),o=_O(e,n/2,r/2);return{outer:{x:t.left,y:t.top,w:n,h:r,radius:o},inner:{x:t.left+s.l,y:t.top+s.t,w:n-s.l-s.r,h:r-s.t-s.b,radius:{topLeft:Math.max(0,o.topLeft-Math.max(s.t,s.l)),topRight:Math.max(0,o.topRight-Math.max(s.t,s.r)),bottomLeft:Math.max(0,o.bottomLeft-Math.max(s.b,s.l)),bottomRight:Math.max(0,o.bottomRight-Math.max(s.b,s.r))}}}}function Cd(e,t,n,r){const s=t===null,o=n===null,a=e&&!(s&&o)&&Cm(e,r);return a&&(s||Fr(t,a.left,a.right))&&(o||Fr(n,a.top,a.bottom))}function MO(e){return e.topLeft||e.topRight||e.bottomLeft||e.bottomRight}function NO(e,t){e.rect(t.x,t.y,t.w,t.h)}function Sd(e,t,n={}){const r=e.x!==n.x?-t:0,s=e.y!==n.y?-t:0,o=(e.x+e.w!==n.x+n.w?t:0)-r,i=(e.y+e.h!==n.y+n.h?t:0)-s;return{x:e.x+r,y:e.y+s,w:e.w+o,h:e.h+i,radius:e.radius}}class Aa extends jr{constructor(t){super(),this.options=void 0,this.horizontal=void 0,this.base=void 0,this.width=void 0,this.height=void 0,this.inflateAmount=void 0,t&&Object.assign(this,t)}draw(t){const{inflateAmount:n,options:{borderColor:r,backgroundColor:s}}=this,{inner:o,outer:i}=EO(this),a=MO(i.radius)?ma:NO;t.save(),(i.w!==o.w||i.h!==o.h)&&(t.beginPath(),a(t,Sd(i,n,o)),t.clip(),a(t,Sd(o,-n,i)),t.fillStyle=r,t.fill("evenodd")),t.beginPath(),a(t,Sd(o,n)),t.fillStyle=s,t.fill(),t.restore()}inRange(t,n,r){return Cd(this,t,n,r)}inXRange(t,n){return Cd(this,t,null,n)}inYRange(t,n){return Cd(this,null,t,n)}getCenterPoint(t){const{x:n,y:r,base:s,horizontal:o}=this.getProps(["x","y","base","horizontal"],t);return{x:o?(n+s)/2:n,y:o?r:(r+s)/2}}getRange(t){return t==="x"?this.width/2:this.height/2}}$e(Aa,"id","bar"),$e(Aa,"defaults",{borderSkipped:"start",borderWidth:0,borderRadius:0,inflateAmount:"auto",pointStyle:void 0}),$e(Aa,"defaultRoutes",{backgroundColor:"backgroundColor",borderColor:"borderColor"});const Sm=(e,t)=>{let{boxHeight:n=t,boxWidth:r=t}=e;return e.usePointStyle&&(n=Math.min(n,t),r=e.pointStyleWidth||Math.min(r,t)),{boxWidth:r,boxHeight:n,itemHeight:Math.max(t,n)}},TO=(e,t)=>e!==null&&t!==null&&e.datasetIndex===t.datasetIndex&&e.index===t.index;class _m extends jr{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,n,r){this.maxWidth=t,this.maxHeight=n,this._margins=r,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let n=bt(t.generateLabels,[this.chart],this)||[];t.filter&&(n=n.filter(r=>t.filter(r,this.chart.data))),t.sort&&(n=n.sort((r,s)=>t.sort(r,s,this.chart.data))),this.options.reverse&&n.reverse(),this.legendItems=n}fit(){const{options:t,ctx:n}=this;if(!t.display){this.width=this.height=0;return}const r=t.labels,s=Kt(r.font),o=s.size,i=this._computeTitleHeight(),{boxWidth:a,itemHeight:l}=Sm(r,o);let c,d;n.font=s.string,this.isHorizontal()?(c=this.maxWidth,d=this._fitRows(i,o,a,l)+10):(d=this.maxHeight,c=this._fitCols(i,s,a,l)+10),this.width=Math.min(c,t.maxWidth||this.maxWidth),this.height=Math.min(d,t.maxHeight||this.maxHeight)}_fitRows(t,n,r,s){const{ctx:o,maxWidth:i,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.lineWidths=[0],d=s+a;let u=t;o.textAlign="left",o.textBaseline="middle";let f=-1,g=-d;return this.legendItems.forEach((m,p)=>{const y=r+n/2+o.measureText(m.text).width;(p===0||c[c.length-1]+y+2*a>i)&&(u+=d,c[c.length-(p>0?0:1)]=0,g+=d,f++),l[p]={left:0,top:g,row:f,width:y,height:s},c[c.length-1]+=y+a}),u}_fitCols(t,n,r,s){const{ctx:o,maxHeight:i,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.columnSizes=[],d=i-t;let u=a,f=0,g=0,m=0,p=0;return this.legendItems.forEach((y,b)=>{const{itemWidth:_,itemHeight:x}=OO(r,n,o,y,s);b>0&&g+x+2*a>d&&(u+=f+a,c.push({width:f,height:g}),m+=f+a,p++,f=g=0),l[b]={left:m,top:g,col:p,width:_,height:x},f=Math.max(f,_),g+=x+a}),u+=f,c.push({width:f,height:g}),u}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:n,options:{align:r,labels:{padding:s},rtl:o}}=this,i=Os(o,this.left,this.width);if(this.isHorizontal()){let a=0,l=Xt(r,this.left+s,this.right-this.lineWidths[a]);for(const c of n)a!==c.row&&(a=c.row,l=Xt(r,this.left+s,this.right-this.lineWidths[a])),c.top+=this.top+t+s,c.left=i.leftForLtr(i.x(l),c.width),l+=c.width+s}else{let a=0,l=Xt(r,this.top+t+s,this.bottom-this.columnSizes[a].height);for(const c of n)c.col!==a&&(a=c.col,l=Xt(r,this.top+t+s,this.bottom-this.columnSizes[a].height)),c.top=l,c.left+=this.left+s,c.left=i.leftForLtr(i.x(c.left),c.width),l+=c.height+s}}isHorizontal(){return this.options.position==="top"||this.options.position==="bottom"}draw(){if(this.options.display){const t=this.ctx;od(t,this),this._draw(),id(t)}}_draw(){const{options:t,columnSizes:n,lineWidths:r,ctx:s}=this,{align:o,labels:i}=t,a=St.color,l=Os(t.rtl,this.left,this.width),c=Kt(i.font),{padding:d}=i,u=c.size,f=u/2;let g;this.drawTitle(),s.textAlign=l.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=c.string;const{boxWidth:m,boxHeight:p,itemHeight:y}=Sm(i,u),b=function(k,N,S){if(isNaN(m)||m<=0||isNaN(p)||p<0)return;s.save();const w=et(S.lineWidth,1);if(s.fillStyle=et(S.fillStyle,a),s.lineCap=et(S.lineCap,"butt"),s.lineDashOffset=et(S.lineDashOffset,0),s.lineJoin=et(S.lineJoin,"miter"),s.lineWidth=w,s.strokeStyle=et(S.strokeStyle,a),s.setLineDash(et(S.lineDash,[])),i.usePointStyle){const E={radius:p*Math.SQRT2/2,pointStyle:S.pointStyle,rotation:S.rotation,borderWidth:w},D=l.xPlus(k,m/2),P=N+f;S0(s,E,D,P,i.pointStyleWidth&&m)}else{const E=N+Math.max((u-p)/2,0),D=l.leftForLtr(k,m),P=Ms(S.borderRadius);s.beginPath(),Object.values(P).some(L=>L!==0)?ma(s,{x:D,y:E,w:m,h:p,radius:P}):s.rect(D,E,m,p),s.fill(),w!==0&&s.stroke()}s.restore()},_=function(k,N,S){Oo(s,S.text,k,N+y/2,c,{strikethrough:S.hidden,textAlign:l.textAlign(S.textAlign)})},x=this.isHorizontal(),M=this._computeTitleHeight();x?g={x:Xt(o,this.left+d,this.right-r[0]),y:this.top+d+M,line:0}:g={x:this.left+d,y:Xt(o,this.top+M+d,this.bottom-n[0].height),line:0},P0(this.ctx,t.textDirection);const C=y+d;this.legendItems.forEach((k,N)=>{s.strokeStyle=k.fontColor,s.fillStyle=k.fontColor;const S=s.measureText(k.text).width,w=l.textAlign(k.textAlign||(k.textAlign=i.textAlign)),E=m+f+S;let D=g.x,P=g.y;l.setWidth(this.width),x?N>0&&D+E+d>this.right&&(P=g.y+=C,g.line++,D=g.x=Xt(o,this.left+d,this.right-r[g.line])):N>0&&P+C>this.bottom&&(D=g.x=D+n[g.line].width+d,g.line++,P=g.y=Xt(o,this.top+M+d,this.bottom-n[g.line].height));const L=l.x(D);if(b(L,P,k),D=RM(w,D+m+f,x?D+E:this.right,t.rtl),_(l.x(D),P,k),x)g.x+=E+d;else if(typeof k.text!="string"){const $=c.lineHeight;g.y+=Em(k,$)+d}else g.y+=C}),$0(this.ctx,t.textDirection)}drawTitle(){const t=this.options,n=t.title,r=Kt(n.font),s=xn(n.padding);if(!n.display)return;const o=Os(t.rtl,this.left,this.width),i=this.ctx,a=n.position,l=r.size/2,c=s.top+l;let d,u=this.left,f=this.width;if(this.isHorizontal())f=Math.max(...this.lineWidths),d=this.top+c,u=Xt(t.align,u,this.right-f);else{const m=this.columnSizes.reduce((p,y)=>Math.max(p,y.height),0);d=c+Xt(t.align,this.top,this.bottom-m-t.labels.padding-this._computeTitleHeight())}const g=Xt(a,u,u+f);i.textAlign=o.textAlign(ed(a)),i.textBaseline="middle",i.strokeStyle=n.color,i.fillStyle=n.color,i.font=r.string,Oo(i,n.text,g,d,r)}_computeTitleHeight(){const t=this.options.title,n=Kt(t.font),r=xn(t.padding);return t.display?n.lineHeight+r.height:0}_getLegendItemAt(t,n){let r,s,o;if(Fr(t,this.left,this.right)&&Fr(n,this.top,this.bottom)){for(o=this.legendHitBoxes,r=0;ro.length>i.length?o:i)),t+n.size/2+r.measureText(s).width}function DO(e,t,n){let r=e;return typeof t.text!="string"&&(r=Em(t,n)),r}function Em(e,t){const n=e.text?e.text.length:0;return t*n}function IO(e,t){return!!((e==="mousemove"||e==="mouseout")&&(t.onHover||t.onLeave)||t.onClick&&(e==="click"||e==="mouseup"))}var Mm={id:"legend",_element:_m,start(e,t,n){const r=e.legend=new _m({ctx:e.ctx,options:n,chart:e});wn.configure(e,r,n),wn.addBox(e,r)},stop(e){wn.removeBox(e,e.legend),delete e.legend},beforeUpdate(e,t,n){const r=e.legend;wn.configure(e,r,n),r.options=n},afterUpdate(e){const t=e.legend;t.buildLabels(),t.adjustHitBoxes()},afterEvent(e,t){t.replay||e.legend.handleEvent(t.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(e,t,n){const r=t.datasetIndex,s=n.chart;s.isDatasetVisible(r)?(s.hide(r),t.hidden=!0):(s.show(r),t.hidden=!1)},onHover:null,onLeave:null,labels:{color:e=>e.chart.options.color,boxWidth:40,padding:10,generateLabels(e){const t=e.data.datasets,{labels:{usePointStyle:n,pointStyle:r,textAlign:s,color:o,useBorderRadius:i,borderRadius:a}}=e.legend.options;return e._getSortedDatasetMetas().map(l=>{const c=l.controller.getStyle(n?0:void 0),d=xn(c.borderWidth);return{text:t[l.index].label,fillStyle:c.backgroundColor,fontColor:o,hidden:!l.visible,lineCap:c.borderCapStyle,lineDash:c.borderDash,lineDashOffset:c.borderDashOffset,lineJoin:c.borderJoinStyle,lineWidth:(d.width+d.height)/4,strokeStyle:c.borderColor,pointStyle:r||c.pointStyle,rotation:c.rotation,textAlign:s||c.textAlign,borderRadius:i&&(a||c.borderRadius),datasetIndex:l.index}},this)}},title:{color:e=>e.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:e=>!e.startsWith("on"),labels:{_scriptable:e=>!["generateLabels","filter","sort"].includes(e)}}};class Nm extends jr{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,n){const r=this.options;if(this.left=0,this.top=0,!r.display){this.width=this.height=this.right=this.bottom=0;return}this.width=this.right=t,this.height=this.bottom=n;const s=Ot(r.text)?r.text.length:1;this._padding=xn(r.padding);const o=s*Kt(r.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return t==="top"||t==="bottom"}_drawArgs(t){const{top:n,left:r,bottom:s,right:o,options:i}=this,a=i.align;let l=0,c,d,u;return this.isHorizontal()?(d=Xt(a,r,o),u=n+t,c=o-r):(i.position==="left"?(d=r+t,u=Xt(a,s,n),l=ft*-.5):(d=o-t,u=Xt(a,n,s),l=ft*.5),c=s-n),{titleX:d,titleY:u,maxWidth:c,rotation:l}}draw(){const t=this.ctx,n=this.options;if(!n.display)return;const r=Kt(n.font),o=r.lineHeight/2+this._padding.top,{titleX:i,titleY:a,maxWidth:l,rotation:c}=this._drawArgs(o);Oo(t,n.text,0,0,r,{color:n.color,maxWidth:l,rotation:c,textAlign:ed(n.align),textBaseline:"middle",translation:[i,a]})}}function LO(e,t){const n=new Nm({ctx:e.ctx,options:t,chart:e});wn.configure(e,n,t),wn.addBox(e,n),e.titleBlock=n}var PO={id:"title",_element:Nm,start(e,t,n){LO(e,n)},stop(e){const t=e.titleBlock;wn.removeBox(e,t),delete e.titleBlock},beforeUpdate(e,t,n){const r=e.titleBlock;wn.configure(e,r,n),r.options=n},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Uo={average(e){if(!e.length)return!1;let t,n,r=new Set,s=0,o=0;for(t=0,n=e.length;ta+l)/r.size,y:s/o}},nearest(e,t){if(!e.length)return!1;let n=t.x,r=t.y,s=Number.POSITIVE_INFINITY,o,i,a;for(o=0,i=e.length;o-1?e.split(` -`):e}function $O(e,t){const{element:n,datasetIndex:r,index:s}=t,o=e.getDatasetMeta(r).controller,{label:i,value:a}=o.getLabelAndValue(s);return{chart:e,label:i,parsed:o.getParsed(s),raw:e.data.datasets[r].data[s],formattedValue:a,dataset:o.getDataset(),dataIndex:s,datasetIndex:r,element:n}}function Tm(e,t){const n=e.chart.ctx,{body:r,footer:s,title:o}=e,{boxWidth:i,boxHeight:a}=t,l=Kt(t.bodyFont),c=Kt(t.titleFont),d=Kt(t.footerFont),u=o.length,f=s.length,g=r.length,m=xn(t.padding);let p=m.height,y=0,b=r.reduce((M,C)=>M+C.before.length+C.lines.length+C.after.length,0);if(b+=e.beforeBody.length+e.afterBody.length,u&&(p+=u*c.lineHeight+(u-1)*t.titleSpacing+t.titleMarginBottom),b){const M=t.displayColors?Math.max(a,l.lineHeight):l.lineHeight;p+=g*M+(b-g)*l.lineHeight+(b-1)*t.bodySpacing}f&&(p+=t.footerMarginTop+f*d.lineHeight+(f-1)*t.footerSpacing);let _=0;const x=function(M){y=Math.max(y,n.measureText(M).width+_)};return n.save(),n.font=c.string,ct(e.title,x),n.font=l.string,ct(e.beforeBody.concat(e.afterBody),x),_=t.displayColors?i+2+t.boxPadding:0,ct(r,M=>{ct(M.before,x),ct(M.lines,x),ct(M.after,x)}),_=0,n.font=d.string,ct(e.footer,x),n.restore(),y+=m.width,{width:y,height:p}}function FO(e,t){const{y:n,height:r}=t;return ne.height-r/2?"bottom":"center"}function zO(e,t,n,r){const{x:s,width:o}=r,i=n.caretSize+n.caretPadding;if(e==="left"&&s+o+i>t.width||e==="right"&&s-o-i<0)return!0}function VO(e,t,n,r){const{x:s,width:o}=n,{width:i,chartArea:{left:a,right:l}}=e;let c="center";return r==="center"?c=s<=(a+l)/2?"left":"right":s<=o/2?c="left":s>=i-o/2&&(c="right"),zO(c,e,t,n)&&(c="center"),c}function Om(e,t,n){const r=n.yAlign||t.yAlign||FO(e,n);return{xAlign:n.xAlign||t.xAlign||VO(e,t,n,r),yAlign:r}}function HO(e,t){let{x:n,width:r}=e;return t==="right"?n-=r:t==="center"&&(n-=r/2),n}function UO(e,t,n){let{y:r,height:s}=e;return t==="top"?r+=n:t==="bottom"?r-=s+n:r-=s/2,r}function Am(e,t,n,r){const{caretSize:s,caretPadding:o,cornerRadius:i}=e,{xAlign:a,yAlign:l}=n,c=s+o,{topLeft:d,topRight:u,bottomLeft:f,bottomRight:g}=Ms(i);let m=HO(t,a);const p=UO(t,l,c);return l==="center"?a==="left"?m+=c:a==="right"&&(m-=c):a==="left"?m-=Math.max(d,f)+s:a==="right"&&(m+=Math.max(u,g)+s),{x:rn(m,0,r.width-t.width),y:rn(p,0,r.height-t.height)}}function Da(e,t,n){const r=xn(n.padding);return t==="center"?e.x+e.width/2:t==="right"?e.x+e.width-r.right:e.x+r.left}function Dm(e){return Kn([],rr(e))}function BO(e,t,n){return Ns(e,{tooltip:t,tooltipItems:n,type:"tooltip"})}function Im(e,t){const n=t&&t.dataset&&t.dataset.tooltip&&t.dataset.tooltip.callbacks;return n?e.override(n):e}const Lm={beforeTitle:er,title(e){if(e.length>0){const t=e[0],n=t.chart.data.labels,r=n?n.length:0;if(this&&this.options&&this.options.mode==="dataset")return t.dataset.label||"";if(t.label)return t.label;if(r>0&&t.dataIndex"u"?Lm[t].call(n,r):s}class _d extends jr{constructor(t){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=t.chart,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this._cachedAnimations;if(t)return t;const n=this.chart,r=this.options.setContext(this.getContext()),s=r.enabled&&n.options.animation&&r.animations,o=new z0(this.chart,s);return s._cacheable&&(this._cachedAnimations=Object.freeze(o)),o}getContext(){return this.$context||(this.$context=BO(this.chart.getContext(),this,this._tooltipItems))}getTitle(t,n){const{callbacks:r}=n,s=sn(r,"beforeTitle",this,t),o=sn(r,"title",this,t),i=sn(r,"afterTitle",this,t);let a=[];return a=Kn(a,rr(s)),a=Kn(a,rr(o)),a=Kn(a,rr(i)),a}getBeforeBody(t,n){return Dm(sn(n.callbacks,"beforeBody",this,t))}getBody(t,n){const{callbacks:r}=n,s=[];return ct(t,o=>{const i={before:[],lines:[],after:[]},a=Im(r,o);Kn(i.before,rr(sn(a,"beforeLabel",this,o))),Kn(i.lines,sn(a,"label",this,o)),Kn(i.after,rr(sn(a,"afterLabel",this,o))),s.push(i)}),s}getAfterBody(t,n){return Dm(sn(n.callbacks,"afterBody",this,t))}getFooter(t,n){const{callbacks:r}=n,s=sn(r,"beforeFooter",this,t),o=sn(r,"footer",this,t),i=sn(r,"afterFooter",this,t);let a=[];return a=Kn(a,rr(s)),a=Kn(a,rr(o)),a=Kn(a,rr(i)),a}_createItems(t){const n=this._active,r=this.chart.data,s=[],o=[],i=[];let a=[],l,c;for(l=0,c=n.length;lt.filter(d,u,f,r))),t.itemSort&&(a=a.sort((d,u)=>t.itemSort(d,u,r))),ct(a,d=>{const u=Im(t.callbacks,d);s.push(sn(u,"labelColor",this,d)),o.push(sn(u,"labelPointStyle",this,d)),i.push(sn(u,"labelTextColor",this,d))}),this.labelColors=s,this.labelPointStyles=o,this.labelTextColors=i,this.dataPoints=a,a}update(t,n){const r=this.options.setContext(this.getContext()),s=this._active;let o,i=[];if(!s.length)this.opacity!==0&&(o={opacity:0});else{const a=Uo[r.position].call(this,s,this._eventPosition);i=this._createItems(r),this.title=this.getTitle(i,r),this.beforeBody=this.getBeforeBody(i,r),this.body=this.getBody(i,r),this.afterBody=this.getAfterBody(i,r),this.footer=this.getFooter(i,r);const l=this._size=Tm(this,r),c=Object.assign({},a,l),d=Om(this.chart,r,c),u=Am(r,c,d,this.chart);this.xAlign=d.xAlign,this.yAlign=d.yAlign,o={opacity:1,x:u.x,y:u.y,width:l.width,height:l.height,caretX:a.x,caretY:a.y}}this._tooltipItems=i,this.$context=void 0,o&&this._resolveAnimations().update(this,o),t&&r.external&&r.external.call(this,{chart:this.chart,tooltip:this,replay:n})}drawCaret(t,n,r,s){const o=this.getCaretPosition(t,r,s);n.lineTo(o.x1,o.y1),n.lineTo(o.x2,o.y2),n.lineTo(o.x3,o.y3)}getCaretPosition(t,n,r){const{xAlign:s,yAlign:o}=this,{caretSize:i,cornerRadius:a}=r,{topLeft:l,topRight:c,bottomLeft:d,bottomRight:u}=Ms(a),{x:f,y:g}=t,{width:m,height:p}=n;let y,b,_,x,M,C;return o==="center"?(M=g+p/2,s==="left"?(y=f,b=y-i,x=M+i,C=M-i):(y=f+m,b=y+i,x=M-i,C=M+i),_=y):(s==="left"?b=f+Math.max(l,d)+i:s==="right"?b=f+m-Math.max(c,u)-i:b=this.caretX,o==="top"?(x=g,M=x-i,y=b-i,_=b+i):(x=g+p,M=x+i,y=b+i,_=b-i),C=x),{x1:y,x2:b,x3:_,y1:x,y2:M,y3:C}}drawTitle(t,n,r){const s=this.title,o=s.length;let i,a,l;if(o){const c=Os(r.rtl,this.x,this.width);for(t.x=Da(this,r.titleAlign,r),n.textAlign=c.textAlign(r.titleAlign),n.textBaseline="middle",i=Kt(r.titleFont),a=r.titleSpacing,n.fillStyle=r.titleColor,n.font=i.string,l=0;l_!==0)?(t.beginPath(),t.fillStyle=o.multiKeyBackground,ma(t,{x:p,y:m,w:c,h:l,radius:b}),t.fill(),t.stroke(),t.fillStyle=i.backgroundColor,t.beginPath(),ma(t,{x:y,y:m+1,w:c-2,h:l-2,radius:b}),t.fill()):(t.fillStyle=o.multiKeyBackground,t.fillRect(p,m,c,l),t.strokeRect(p,m,c,l),t.fillStyle=i.backgroundColor,t.fillRect(y,m+1,c-2,l-2))}t.fillStyle=this.labelTextColors[r]}drawBody(t,n,r){const{body:s}=this,{bodySpacing:o,bodyAlign:i,displayColors:a,boxHeight:l,boxWidth:c,boxPadding:d}=r,u=Kt(r.bodyFont);let f=u.lineHeight,g=0;const m=Os(r.rtl,this.x,this.width),p=function(S){n.fillText(S,m.x(t.x+g),t.y+f/2),t.y+=f+o},y=m.textAlign(i);let b,_,x,M,C,k,N;for(n.textAlign=i,n.textBaseline="middle",n.font=u.string,t.x=Da(this,y,r),n.fillStyle=r.bodyColor,ct(this.beforeBody,p),g=a&&y!=="right"?i==="center"?c/2+d:c+2+d:0,M=0,k=s.length;M0&&n.stroke()}_updateAnimationTarget(t){const n=this.chart,r=this.$animations,s=r&&r.x,o=r&&r.y;if(s||o){const i=Uo[t.position].call(this,this._active,this._eventPosition);if(!i)return;const a=this._size=Tm(this,t),l=Object.assign({},i,this._size),c=Om(n,t,l),d=Am(t,l,c,n);(s._to!==d.x||o._to!==d.y)&&(this.xAlign=c.xAlign,this.yAlign=c.yAlign,this.width=a.width,this.height=a.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,d))}}_willRender(){return!!this.opacity}draw(t){const n=this.options.setContext(this.getContext());let r=this.opacity;if(!r)return;this._updateAnimationTarget(n);const s={width:this.width,height:this.height},o={x:this.x,y:this.y};r=Math.abs(r)<.001?0:r;const i=xn(n.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;n.enabled&&a&&(t.save(),t.globalAlpha=r,this.drawBackground(o,t,s,n),P0(t,n.textDirection),o.y+=i.top,this.drawTitle(o,t,n),this.drawBody(o,t,n),this.drawFooter(o,t,n),$0(t,n.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,n){const r=this._active,s=t.map(({datasetIndex:a,index:l})=>{const c=this.chart.getDatasetMeta(a);if(!c)throw new Error("Cannot find a dataset at index "+a);return{datasetIndex:a,element:c.data[l],index:l}}),o=!ca(r,s),i=this._positionChanged(s,n);(o||i)&&(this._active=s,this._eventPosition=n,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,n,r=!0){if(n&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,o=this._active||[],i=this._getActiveElements(t,o,n,r),a=this._positionChanged(i,t),l=n||!ca(i,o)||a;return l&&(this._active=i,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,n))),l}_getActiveElements(t,n,r,s){const o=this.options;if(t.type==="mouseout")return[];if(!s)return n.filter(a=>this.chart.data.datasets[a.datasetIndex]&&this.chart.getDatasetMeta(a.datasetIndex).controller.getParsed(a.index)!==void 0);const i=this.chart.getElementsAtEventForMode(t,o.mode,o,r);return o.reverse&&i.reverse(),i}_positionChanged(t,n){const{caretX:r,caretY:s,options:o}=this,i=Uo[o.position].call(this,t,n);return i!==!1&&(r!==i.x||s!==i.y)}}$e(_d,"positioners",Uo);var Pm={id:"tooltip",_element:_d,positioners:Uo,afterInit(e,t,n){n&&(e.tooltip=new _d({chart:e,options:n}))},beforeUpdate(e,t,n){e.tooltip&&e.tooltip.initialize(n)},reset(e,t,n){e.tooltip&&e.tooltip.initialize(n)},afterDraw(e){const t=e.tooltip;if(t&&t._willRender()){const n={tooltip:t};if(e.notifyPlugins("beforeTooltipDraw",{...n,cancelable:!0})===!1)return;t.draw(e.ctx),e.notifyPlugins("afterTooltipDraw",n)}},afterEvent(e,t){if(e.tooltip){const n=t.replay;e.tooltip.handleEvent(t.event,n,t.inChartArea)&&(t.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(e,t)=>t.bodyFont.size,boxWidth:(e,t)=>t.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Lm},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:e=>e!=="filter"&&e!=="itemSort"&&e!=="external",_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};const jO=(e,t,n,r)=>(typeof t=="string"?(n=e.push(t)-1,r.unshift({index:n,label:t})):isNaN(t)&&(n=null),n);function WO(e,t,n,r){const s=e.indexOf(t);if(s===-1)return jO(e,t,n,r);const o=e.lastIndexOf(t);return s!==o?n:s}const GO=(e,t)=>e===null?null:rn(Math.round(e),0,t);function $m(e){const t=this.getLabels();return e>=0&&en.length-1?null:this.getPixelForValue(n[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}}$e(Ed,"id","category"),$e(Ed,"defaults",{ticks:{callback:$m}});function YO(e,t){const n=[],{bounds:s,step:o,min:i,max:a,precision:l,count:c,maxTicks:d,maxDigits:u,includeBounds:f}=e,g=o||1,m=d-1,{min:p,max:y}=t,b=!ut(i),_=!ut(a),x=!ut(c),M=(y-p)/(u+1);let C=a0((y-p)/m/g)*g,k,N,S,w;if(C<1e-14&&!b&&!_)return[{value:p},{value:y}];w=Math.ceil(y/C)-Math.floor(p/C),w>m&&(C=a0(w*C/m/g)*g),ut(l)||(k=Math.pow(10,l),C=Math.ceil(C*k)/k),s==="ticks"?(N=Math.floor(p/C)*C,S=Math.ceil(y/C)*C):(N=p,S=y),b&&_&&o&&WM((a-i)/o,C/1e3)?(w=Math.round(Math.min((a-i)/C,d)),C=(a-i)/w,N=i,S=a):x?(N=b?i:N,S=_?a:S,w=c-1,C=(S-N)/w):(w=(S-N)/C,fa(w,Math.round(w),C/1e3)?w=Math.round(w):w=Math.ceil(w));const E=Math.max(l0(C),l0(N));k=Math.pow(10,ut(l)?E:l),N=Math.round(N*k)/k,S=Math.round(S*k)/k;let D=0;for(b&&(f&&N!==i?(n.push({value:i}),Na)break;n.push({value:P})}return _&&f&&S!==a?n.length&&fa(n[n.length-1].value,a,Fm(a,M,e))?n[n.length-1].value=a:n.push({value:a}):(!_||S===a)&&n.push({value:S}),n}function Fm(e,t,{horizontal:n,minRotation:r}){const s=tr(r),o=(n?Math.sin(s):Math.cos(s))||.001,i=.75*t*(""+e).length;return Math.min(t/o,i)}class XO extends Ds{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(t,n){return ut(t)||(typeof t=="number"||t instanceof Number)&&!isFinite(+t)?null:+t}handleTickRangeOptions(){const{beginAtZero:t}=this.options,{minDefined:n,maxDefined:r}=this.getUserBounds();let{min:s,max:o}=this;const i=l=>s=n?s:l,a=l=>o=r?o:l;if(t){const l=yr(s),c=yr(o);l<0&&c<0?a(0):l>0&&c>0&&i(0)}if(s===o){let l=o===0?1:Math.abs(o*.05);a(o+l),t||i(s-l)}this.min=s,this.max=o}getTickLimit(){const t=this.options.ticks;let{maxTicksLimit:n,stepSize:r}=t,s;return r?(s=Math.ceil(this.max/r)-Math.floor(this.min/r)+1,s>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${r} would result generating up to ${s} ticks. Limiting to 1000.`),s=1e3)):(s=this.computeTickLimit(),n=n||11),n&&(s=Math.min(n,s)),s}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,n=t.ticks;let r=this.getTickLimit();r=Math.max(2,r);const s={maxTicks:r,bounds:t.bounds,min:t.min,max:t.max,precision:n.precision,step:n.stepSize,count:n.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:n.minRotation||0,includeBounds:n.includeBounds!==!1},o=this._range||this,i=YO(s,o);return t.bounds==="ticks"&&GM(i,this,"value"),t.reverse?(i.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),i}configure(){const t=this.ticks;let n=this.min,r=this.max;if(super.configure(),this.options.offset&&t.length){const s=(r-n)/Math.max(t.length-1,1)/2;n-=s,r+=s}this._startValue=n,this._endValue=r,this._valueRange=r-n}getLabelForValue(t){return nd(t,this.chart.options.locale,this.options.ticks.format)}}class Md extends XO{determineDataLimits(){const{min:t,max:n}=this.getMinMax(!0);this.min=vn(t)?t:0,this.max=vn(n)?n:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),n=t?this.width:this.height,r=tr(this.options.ticks.minRotation),s=(t?Math.sin(r):Math.cos(r))||.001,o=this._resolveTickFontOptions(0);return Math.ceil(n/Math.min(40,o.lineHeight/s))}getPixelForValue(t){return t===null?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}$e(Md,"id","linear"),$e(Md,"defaults",{ticks:{callback:x0.formatters.numeric}});const Ia={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},on=Object.keys(Ia);function zm(e,t){return e-t}function Vm(e,t){if(ut(t))return null;const n=e._adapter,{parser:r,round:s,isoWeekday:o}=e._parseOpts;let i=t;return typeof r=="function"&&(i=r(i)),vn(i)||(i=typeof r=="string"?n.parse(i,r):n.parse(i)),i===null?null:(s&&(i=s==="week"&&(ha(o)||o===!0)?n.startOf(i,"isoWeek",o):n.startOf(i,s)),+i)}function Hm(e,t,n,r){const s=on.length;for(let o=on.indexOf(e);o=on.indexOf(n);o--){const i=on[o];if(Ia[i].common&&e._adapter.diff(s,r,i)>=t-1)return i}return on[n?on.indexOf(n):0]}function qO(e){for(let t=on.indexOf(e)+1,n=on.length;t=t?n[r]:n[s];e[o]=!0}}function ZO(e,t,n,r){const s=e._adapter,o=+s.startOf(t[0].value,r),i=t[t.length-1].value;let a,l;for(a=o;a<=i;a=+s.add(a,1,r))l=n[a],l>=0&&(t[l].major=!0);return t}function Bm(e,t,n){const r=[],s={},o=t.length;let i,a;for(i=0;i+t.value))}initOffsets(t=[]){let n=0,r=0,s,o;this.options.offset&&t.length&&(s=this.getDecimalForValue(t[0]),t.length===1?n=1-s:n=(this.getDecimalForValue(t[1])-s)/2,o=this.getDecimalForValue(t[t.length-1]),t.length===1?r=o:r=(o-this.getDecimalForValue(t[t.length-2]))/2);const i=t.length<3?.5:.25;n=rn(n,0,i),r=rn(r,0,i),this._offsets={start:n,end:r,factor:1/(n+1+r)}}_generate(){const t=this._adapter,n=this.min,r=this.max,s=this.options,o=s.time,i=o.unit||Hm(o.minUnit,n,r,this._getLabelCapacity(n)),a=et(s.ticks.stepSize,1),l=i==="week"?o.isoWeekday:!1,c=ha(l)||l===!0,d={};let u=n,f,g;if(c&&(u=+t.startOf(u,"isoWeek",l)),u=+t.startOf(u,c?"day":i),t.diff(r,n,i)>1e5*a)throw new Error(n+" and "+r+" are too far apart with stepSize of "+a+" "+i);const m=s.ticks.source==="data"&&this.getDataTimestamps();for(f=u,g=0;f+p)}getLabelForValue(t){const n=this._adapter,r=this.options.time;return r.tooltipFormat?n.format(t,r.tooltipFormat):n.format(t,r.displayFormats.datetime)}format(t,n){const s=this.options.time.displayFormats,o=this._unit,i=n||s[o];return this._adapter.format(t,i)}_tickFormatFunction(t,n,r,s){const o=this.options,i=o.ticks.callback;if(i)return bt(i,[t,n,r],this);const a=o.time.displayFormats,l=this._unit,c=this._majorUnit,d=l&&a[l],u=c&&a[c],f=r[n],g=c&&u&&f&&f.major;return this._adapter.format(t,s||(g?u:d))}generateTickLabels(t){let n,r,s;for(n=0,r=t.length;n0?a:1}getDataTimestamps(){let t=this._cache.data||[],n,r;if(t.length)return t;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(n=0,r=s.length;n=e[r].pos&&t<=e[s].pos&&({lo:r,hi:s}=Rc(e,"pos",t)),{pos:o,time:a}=e[r],{pos:i,time:l}=e[s]):(t>=e[r].time&&t<=e[s].time&&({lo:r,hi:s}=Rc(e,"time",t)),{time:o,pos:a}=e[r],{time:i,pos:l}=e[s]);const c=i-o;return c?a+(l-a)*(t-o)/c:a}class jm extends La{constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),n=this._table=this.buildLookupTable(t);this._minPos=Pa(n,this.min),this._tableRange=Pa(n,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:n,max:r}=this,s=[],o=[];let i,a,l,c,d;for(i=0,a=t.length;i=n&&c<=r&&s.push(c);if(s.length<2)return[{time:n,pos:0},{time:r,pos:1}];for(i=0,a=s.length;is-o)}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const n=this.getDataTimestamps(),r=this.getLabelTimestamps();return n.length&&r.length?t=this.normalize(n.concat(r)):t=n.length?n:r,t=this._cache.all=t,t}getDecimalForValue(t){return(Pa(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const n=this._offsets,r=this.getDecimalForPixel(t)/n.factor-n.end;return Pa(this._table,r*this._tableRange+this._minPos,!0)}}$e(jm,"id","timeseries"),$e(jm,"defaults",La.defaults);const Wm="label";function Gm(e,t){typeof e=="function"?e(t):e&&(e.current=t)}function QO(e,t){const n=e.options;n&&t&&Object.assign(n,t)}function Ym(e,t){e.labels=t}function Xm(e,t,n=Wm){const r=[];e.datasets=t.map(s=>{const o=e.datasets.find(i=>i[n]===s[n]);return!o||!s.data||r.includes(o)?{...s}:(r.push(o),Object.assign(o,s),o)})}function JO(e,t=Wm){const n={labels:[],datasets:[]};return Ym(n,e.labels),Xm(n,e.datasets,t),n}function RO(e,t){const{height:n=150,width:r=300,redraw:s=!1,datasetIdKey:o,type:i,data:a,options:l,plugins:c=[],fallbackContent:d,updateMode:u,...f}=e,g=v.useRef(null),m=v.useRef(null),p=()=>{g.current&&(m.current=new Vo(g.current,{type:i,data:JO(a,o),options:l&&{...l},plugins:c}),Gm(t,m.current))},y=()=>{Gm(t,null),m.current&&(m.current.destroy(),m.current=null)};return v.useEffect(()=>{!s&&m.current&&l&&QO(m.current,l)},[s,l]),v.useEffect(()=>{!s&&m.current&&Ym(m.current.config.data,a.labels)},[s,a.labels]),v.useEffect(()=>{!s&&m.current&&a.datasets&&Xm(m.current.config.data,a.datasets,o)},[s,a.datasets]),v.useEffect(()=>{m.current&&(s?(y(),setTimeout(p)):m.current.update(u))},[s,l,a.labels,a.datasets,u]),v.useEffect(()=>{m.current&&(y(),setTimeout(p))},[i]),v.useEffect(()=>(p(),()=>y()),[]),h.jsx("canvas",{ref:g,role:"img",height:n,width:r,...f,children:d})}const e5=v.forwardRef(RO);function Km(e,t){return Vo.register(t),v.forwardRef((n,r)=>h.jsx(e5,{...n,ref:r,type:e}))}const t5=Km("bar",wa),n5=Km("doughnut",Do);Vo.register(Ed,Md,Aa,PO,Pm,Mm);const r5=({labels:e,datasets:t,height:n=200,showLegend:r=!1,showGrid:s=!1,showYGrid:o=!1,showYAxis:i=!1,className:a="",id:l,editorAttrs:c})=>{const d=v.useMemo(()=>({labels:e??[],datasets:(t??[]).map((m,p)=>({label:m.label,data:m.data,backgroundColor:m.backgroundColor||(p===0?"#7C3AED":"#DDD6FE"),borderRadius:m.borderRadius??4,barThickness:16,maxBarThickness:20,yAxisID:m.yAxisID??"y"}))}),[e,t]),u=v.useMemo(()=>(t??[]).some(m=>m.yAxisID==="y1"),[t]),f=v.useMemo(()=>({responsive:!0,maintainAspectRatio:!1,animation:{duration:750,easing:"easeOutQuart"},animations:{x:{duration:0},y:{from:m=>{if(m.type==="data")return m.chart.scales.y.getPixelForValue(0)}}},transitions:{active:{animation:{duration:200}},resize:{animation:{duration:0}}},plugins:{legend:{display:r,position:"top"},tooltip:{backgroundColor:"#1e293b",titleColor:"#fff",bodyColor:"#fff",padding:12,cornerRadius:8}},scales:{x:{grid:{display:s},border:{display:!1},ticks:{color:"#94a3b8",font:{size:11}}},y:{type:"linear",position:"left",grid:{display:o,color:"#e2e8f0"},border:{display:!1},ticks:{display:i,color:"#94a3b8",font:{size:11},padding:8},beginAtZero:!0,grace:"5%"},...u?{y1:{type:"linear",position:"right",grid:{display:!1},border:{display:!1},ticks:{display:i,color:"#94a3b8",font:{size:11},padding:8},beginAtZero:!0,grace:"5%"}}:{}}}),[r,s,o,i,u]),g=typeof n=="number"?n:/^\d+$/.test(String(n))?`${n}px`:n;return h.jsx("div",{className:`bar-chart-container ${a}`,style:{height:g},id:l,...c,children:h.jsx(t5,{data:d,options:f})})},s5=v.memo(r5,(e,t)=>JSON.stringify(e.labels)===JSON.stringify(t.labels)&&JSON.stringify(e.datasets)===JSON.stringify(t.datasets)&&e.height===t.height&&e.showLegend===t.showLegend&&e.showGrid===t.showGrid&&e.showYGrid===t.showYGrid&&e.showYAxis===t.showYAxis&&e.className===t.className&&e.id===t.id&&e.editorAttrs===t.editorAttrs);Vo.register(Ho,Pm,Mm);const o5=({data:e,centerLabel:t,centerValue:n,size:r=200,cutout:s="70%",showLegend:o=!1,className:i="",id:a,editorAttrs:l})=>{const c=v.useMemo(()=>({labels:(e??[]).map(f=>f.name),datasets:[{data:(e??[]).map(f=>f.value),backgroundColor:(e??[]).map(f=>f.color),borderWidth:0,hoverOffset:4}]}),[e]),d=v.useMemo(()=>({responsive:!0,maintainAspectRatio:!1,cutout:s,plugins:{legend:{display:o},tooltip:{backgroundColor:"#1e293b",titleColor:"#fff",bodyColor:"#fff",padding:12,cornerRadius:8,callbacks:{label:f=>` $${f.parsed.toLocaleString()}`}}}}),[s,o]),u=typeof r=="number"?r:/^\d+$/.test(String(r))?`${r}px`:r;return h.jsx("div",{className:`donut-chart-wrapper ${i}`,id:a,...l,children:h.jsxs("div",{className:"relative",style:{width:u,height:u},children:[h.jsx(n5,{data:c,options:d}),(t||n)&&h.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center pointer-events-none",children:[t&&h.jsx("span",{className:"text-xs text-slate-500 dark:text-slate-400",children:t}),n&&h.jsx("span",{className:"text-lg font-bold text-slate-900 dark:text-white",children:n})]})]})})},i5=({id:e,className:t,style:n,children:r,editorAttrs:s})=>h.jsx("div",{id:e,className:t,style:n,...s,children:r}),a5=({children:e,cols:t=1,responsive:n,gap:r,rowGap:s,colGap:o,autoRows:i,autoCols:a,flow:l="row",className:c="",style:d,onClick:u,id:f,editorAttrs:g})=>{const m=["grid"];if(t&&m.push(`grid-cols-${t}`),n&&(n.sm&&m.push(`sm:grid-cols-${n.sm}`),n.md&&m.push(`md:grid-cols-${n.md}`),n.lg&&m.push(`lg:grid-cols-${n.lg}`),n.xl&&m.push(`xl:grid-cols-${n.xl}`),n["2xl"]&&m.push(`2xl:grid-cols-${n["2xl"]}`)),r!==void 0&&r>0&&m.push(`gap-${r}`),s!==void 0&&s>0&&m.push(`gap-y-${s}`),o!==void 0&&o>0&&m.push(`gap-x-${o}`),i){const y={auto:"auto-rows-auto",min:"auto-rows-min",max:"auto-rows-max",fr:"auto-rows-fr"};m.push(y[i])}if(a){const y={auto:"auto-cols-auto",min:"auto-cols-min",max:"auto-cols-max",fr:"auto-cols-fr"};m.push(y[a])}const p={row:"grid-flow-row",col:"grid-flow-col",dense:"grid-flow-dense","row-dense":"grid-flow-row-dense","col-dense":"grid-flow-col-dense"};return m.push(p[l]),c&&m.push(c),h.jsx(I,{className:m.join(" "),style:d,onClick:u,id:f,...g,children:e})},l5=({children:e,direction:t="row",justify:n="start",align:r="stretch",wrap:s="nowrap",gap:o=0,grow:i=!1,shrink:a=!0,className:l="",style:c,onClick:d,id:u,editorAttrs:f})=>{const g=["flex"],m={row:"flex-row","row-reverse":"flex-row-reverse",col:"flex-col","col-reverse":"flex-col-reverse"};g.push(m[t]);const p={wrap:"flex-wrap",nowrap:"flex-nowrap","wrap-reverse":"flex-wrap-reverse"};g.push(p[s]);const y={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"};g.push(y[n]);const b={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"};return g.push(b[r]),o>0&&g.push(`gap-${o}`),i===!0?g.push("flex-grow"):typeof i=="number"&&i>0&&g.push(`flex-grow-${i}`),a===!1?g.push("shrink-0"):a===!0?g.push("flex-shrink"):typeof a=="number"&&a>0&&g.push(`flex-shrink-${a}`),l&&g.push(l),h.jsx(I,{className:g.join(" "),style:c,onClick:d,id:u,...f,children:e})},c5=({title:e,subtitle:t,children:n,padding:r="none",background:s="none",maxWidth:o,centered:i=!1,border:a=!1,shadow:l="none",rounded:c=!1,className:d="",style:u,onClick:f,id:g,editorAttrs:m})=>{const p=[],y={none:"",sm:"p-2",md:"p-4",lg:"p-6",xl:"p-8"};y[r]&&p.push(y[r]);const b={none:"",white:"bg-white dark:bg-gray-700",gray:"bg-gray-50 dark:bg-gray-800",primary:"bg-blue-50 dark:bg-blue-900/20",secondary:"bg-gray-50 dark:bg-gray-800"};if(s!=="none"&&p.push(b[s]),o){const x={sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-xl","2xl":"max-w-2xl","4xl":"max-w-4xl","6xl":"max-w-6xl","7xl":"max-w-7xl",full:"max-w-full"};p.push(x[o])}i&&p.push("mx-auto"),a&&p.push("border","border-gray-200","dark:border-gray-700");const _={none:"",sm:"shadow-sm",md:"shadow-md",lg:"shadow-lg",xl:"shadow-xl"};return l!=="none"&&p.push(_[l]),c&&p.push("rounded-lg"),d&&p.push(d),h.jsxs(zd,{className:p.join(" "),style:u,onClick:f,id:g,...m,children:[(e||t)&&h.jsxs("div",{className:"mb-4",children:[e&&h.jsx("h2",{className:"text-2xl font-bold text-gray-900 dark:text-white",children:e}),t&&h.jsx("p",{className:"mt-1 text-sm text-gray-600 dark:text-gray-400",children:t})]}),n]})},d5=({leftWidth:e="250px",rightWidth:t="300px",leftSlot:n,centerSlot:r,rightSlot:s,className:o="",style:i,id:a,editorAttrs:l})=>{const c=`flex flex-row w-full h-full ${o}`.trim(),d={width:e,flexShrink:0},u={flex:1,minWidth:0},f={width:t,flexShrink:0};return h.jsxs(I,{className:c,style:i,id:a,...l,children:[h.jsx(I,{className:"flex flex-col",style:d,children:n}),h.jsx(I,{className:"flex flex-col",style:u,children:r}),h.jsx(I,{className:"flex flex-col",style:f,children:s})]})},u5={language:"json",theme:"vs-dark",options:{automaticLayout:!0,minimap:{enabled:!0},scrollBeyondLastLine:!1,fontSize:14,tabSize:2,formatOnPaste:!0,formatOnType:!0,wordWrap:"on",folding:!0,lineNumbers:"on",renderWhitespace:"selection"}},qm=[{uri:`${window.location.origin}/schemas/layout.json`,fileMatch:["*layout*.json"],schema:{type:"object",properties:{version:{type:"string"},endpoint:{type:"string"},components:{type:"array"}},required:["version","endpoint","components"]}}],f5={"vs/nls":{availableLanguages:{"*":"ko"}}};function h5(e){e.languages.json.jsonDefaults.setDiagnosticsOptions({validate:!0,schemas:qm,enableSchemaRequest:!0,schemaValidation:"error",schemaRequest:"error"})}const g5={identifier:"sirsoft-admin_basic",vendor:"sirsoft",name:{ko:"Admin Basic",en:"Admin Basic"},version:"1.0.4",license:"MIT",description:{ko:"그누보드7 기본 관리자 템플릿",en:"Gnuboard7 default admin template"},type:"admin",locales:["ko","en"],author:{name:"sirsoft",email:"contact@sirsoft.com",url:"https://sirsoft.com"},release_date:"2026-05-15",g7_version:">=7.0.0",dependencies:{modules:{},plugins:{}},assets:{css:["assets/css/main.css","assets/css/ui-system.css"],js:["dist/components.js"],fonts:["assets/fonts/"],images:["assets/images/"]},components:{basic:["a","button","checkbox","div","form","h1","h2","h3","hr","icon","img","input","label","li","nav","p","section","select","span","svg","table","tbody","td","textarea","th","thead","tr","ul"],composite:["action-menu","admin-footer","admin-header","admin-sidebar","alert-dialog","breadcrumb","card","confirm-dialog","data-grid","dialog","dropdown","empty-state","filter-group","icon-button","loading-spinner","modal","page-header","pagination","product-card","search-bar","stat-card","status-badge","tab-navigation","template-card","toast"],layout:["container","flex","grid","section"]},preview:{thumbnail:"preview/thumbnail.png",screenshots:["preview/screenshot1.png","preview/screenshot2.png"]},error_config:{layouts:{401:"errors/401",403:"errors/403",404:"errors/404",500:"errors/500",503:"errors/503",maintenance:"errors/maintenance"}},github_url:"https://github.com/gnuboard/g7-template-sirsoft-admin_basic",github_changelog_url:"https://github.com/gnuboard/g7-template-sirsoft-admin_basic/blob/main/CHANGELOG.md",externals:[{id:"fontawesome",type:"style",url:"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css",preconnect:"https://cdnjs.cloudflare.com"},{id:"pretendard",type:"webfont",url:"https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css",preconnect:"https://cdn.jsdelivr.net",crossorigin:"anonymous"},{id:"flag-icons",type:"style",url:"https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css"}]},sr=window.G7Core?.createLogger?.("Handler:SetTheme")??{log:(...e)=>console.log("[Handler:SetTheme]",...e),warn:(...e)=>console.warn("[Handler:SetTheme]",...e),error:(...e)=>console.error("[Handler:SetTheme]",...e)},Nd=["auto","light","dark"],Zm="g7_color_scheme",p5=e=>e==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":e,$a=e=>{const t=p5(e);document.documentElement.setAttribute("data-theme",t),t==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")};function m5(){try{const e=localStorage.getItem(Zm),t=e&&Nd.includes(e)?e:"auto";$a(t);try{window.G7Core?.state?.setGlobal?.({theme:t})}catch(n){sr.warn("Failed to set global theme state:",n)}sr.log("Initial theme applied:",t)}catch(e){sr.warn("Failed to load initial theme:",e),$a("auto")}}async function b5(e,t){const n=e?.target;if(n&&typeof n=="string"&&Nd.includes(n)){$a(n);try{window.G7Core?.state?.setGlobal?.({theme:n})}catch(r){sr.warn("Failed to set global theme state:",r)}return}m5()}async function y5(e,t){let n;if(e?.target&&typeof e.target=="string"&&!e.target.includes("{{")&&(n=e.target),!n||typeof n!="string"){sr.warn("Invalid theme:",n);return}if(!Nd.includes(n)){sr.warn("Unsupported theme:",n);return}const r=n;try{localStorage.setItem(Zm,r)}catch(s){sr.error("Failed to save theme to localStorage:",s);return}$a(r);try{window.G7Core?.state?.setGlobal?.({theme:r})}catch(s){sr.warn("Failed to set global theme state:",s)}sr.log("Theme changed to:",r)}const Bo=window.G7Core?.createLogger?.("Handler:InitMenu")??{log:(...e)=>console.log("[Handler:InitMenu]",...e),warn:(...e)=>console.warn("[Handler:InitMenu]",...e),error:(...e)=>console.error("[Handler:InitMenu]",...e)};function Qm(e,t){for(const n of e){if(n.slug===t)return n;if(n.children&&n.children.length>0){const r=Qm(n.children,t);if(r)return r}}return null}function Jm(e){return new URLSearchParams(window.location.search).get(e)}function v5(e){return window.G7Core?.state?.getDataSource?.(e)}async function x5(e,t=30,n=100){for(let r=0;r0)return o;await new Promise(i=>setTimeout(i,n))}return null}async function w5(e,t){const n=window.G7Core,r=Jm("menu"),s=Jm("mode");if(!r){Bo.log("[initMenuFromUrl] No menu parameter in URL");return}if(!n?.state?.set){Bo.warn("[initMenuFromUrl] G7Core.state.set not available");return}const o=await x5("menus");if(!o){Bo.warn("[initMenuFromUrl] menus data not available after waiting");return}const i=Qm(o,r);if(!i){Bo.warn("[initMenuFromUrl] Menu not found with slug:",r);return}const a=s==="edit"?"edit":"view";n.state.set({selectedMenuId:i.id,selectedMenu:i,panelMode:a}),a==="edit"&&n.state.set({formData:{name:i.name,slug:i.slug,url:i.url,icon:i.icon,parent_id:i.parent_id,module_id:i.module_id,is_active:i.is_active}}),Bo.log("[initMenuFromUrl] Menu initialized from URL:",{slug:r,mode:a,menuId:i.id})}const Jt=window.G7Core?.createLogger?.("Handler:FilterVisibility")??{log:(...e)=>console.log("[Handler:FilterVisibility]",...e),warn:(...e)=>console.warn("[Handler:FilterVisibility]",...e),error:(...e)=>console.error("[Handler:FilterVisibility]",...e)},Fa="g7_filter_visibility_";async function k5(e,t){const{storageKey:n,defaultFilters:r=[],stateKey:s="visibleFilters"}=e?.params||{};if(!n){Jt.warn("[initFilterVisibility] storageKey is required");return}const o=`${Fa}${n}`;try{const i=localStorage.getItem(o);let a;if(i){const l=JSON.parse(i);Array.isArray(l)?(a=l,Jt.log(`[initFilterVisibility] Loaded from localStorage: ${a.join(", ")}`)):(a=r,Jt.warn("[initFilterVisibility] Invalid saved data format, using defaults"))}else a=r,Jt.log(`[initFilterVisibility] No saved data, using defaults: ${r.join(", ")}`);t?.setState&&t.setState({[s]:a})}catch(i){Jt.error("[initFilterVisibility] Failed to load filter visibility:",i),t?.setState&&t.setState({[s]:r})}}async function C5(e,t){const{storageKey:n,filters:r}=e?.params||{};if(!n){Jt.warn("[saveFilterVisibility] storageKey is required");return}if(!Array.isArray(r)){Jt.warn("[saveFilterVisibility] filters must be an array");return}const s=`${Fa}${n}`;try{localStorage.setItem(s,JSON.stringify(r)),Jt.log(`[saveFilterVisibility] Saved to localStorage: ${r.join(", ")}`)}catch(o){Jt.error("[saveFilterVisibility] Failed to save filter visibility:",o)}}async function S5(e,t){const{storageKey:n,filterId:r,stateKey:s="visibleFilters"}=e?.params||{};if(!n||!r){Jt.warn("[toggleFilterVisibility] storageKey and filterId are required");return}const o=t?.state?.[s]||[];let i;o.includes(r)?i=o.filter(l=>l!==r):i=[...o,r],t?.setState&&t.setState({[s]:i});const a=`${Fa}${n}`;try{localStorage.setItem(a,JSON.stringify(i)),Jt.log(`[toggleFilterVisibility] Toggled ${r}: ${i.join(", ")}`)}catch(l){Jt.error("[toggleFilterVisibility] Failed to save:",l)}}async function _5(e,t){const{storageKey:n,defaultFilters:r=[],stateKey:s="visibleFilters"}=e?.params||{};if(!n){Jt.warn("[resetFilterVisibility] storageKey is required");return}const o=`${Fa}${n}`;try{localStorage.setItem(o,JSON.stringify(r)),t?.setState&&t.setState({[s]:r}),Jt.log(`[resetFilterVisibility] Reset to defaults: ${r.join(", ")}`)}catch(i){Jt.error("[resetFilterVisibility] Failed to reset:",i)}}const Ls=window.G7Core?.createLogger?.("Handler:MultilingualTag")??{log:(...e)=>console.log("[Handler:MultilingualTag]",...e),warn:(...e)=>console.warn("[Handler:MultilingualTag]",...e),error:(...e)=>console.error("[Handler:MultilingualTag]",...e)};async function E5(e,t){const n=window.G7Core;if(!n){Ls.error("G7Core not available");return}const r=n.state?.getGlobal?.()?.multilingualTagEdit;if(!r){Ls.warn("No multilingualTagEdit state found"),n.modal?.close?.();return}const{fieldName:s,editingIndex:o,values:i,statePath:a,currentTags:l}=r;if(!Object.values(i||{}).some(g=>typeof g=="string"&&g.trim()!=="")){Ls.warn("No values to save"),n.modal?.close?.(),Td(n);return}const d=Array.isArray(l)?l:[];let u;o==null?u=[...d,i]:u=d.map((g,m)=>m===o?i:g);const f=a||(s?.startsWith("form.")?s:`form.${s}`);n.state?.setParentLocal?.({[f]:u}),n.modal?.close?.(),Td(n)}async function M5(e,t){const n=window.G7Core;if(!n){Ls.error("G7Core not available");return}n.modal?.close?.(),Td(n)}async function N5(e,t){const n=window.G7Core;if(!n){Ls.error("G7Core not available");return}const r=e?.params?.locale;if(!r){Ls.warn("No locale specified");return}const s=t?.event;let o="";s?.target?.value!==void 0?o=s.target.value:s?.value!==void 0&&(o=s.value);const a=(n.state?.getGlobal?.()||{}).multilingualTagEdit||{},l={...a.values||{},[r]:o};n.dispatch?.({handler:"setState",params:{target:"global",multilingualTagEdit:{...a,values:l}}})}function Td(e){e.dispatch?.({handler:"setState",params:{target:"global",multilingualTagEdit:null}})}function Rm(e){const t=e.getFullYear(),n=String(e.getMonth()+1).padStart(2,"0"),r=String(e.getDate()).padStart(2,"0"),s=String(e.getHours()).padStart(2,"0"),o=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0");return`${t}-${n}-${r}T${s}:${o}:${i}`}function T5(e){const t=new Date,n=new Date(t.getFullYear(),t.getMonth(),t.getDate(),23,59,59),r=Rm(n),s=new Date(t.getFullYear(),t.getMonth(),t.getDate(),0,0,0);switch(e){case"today":break;case"week":s.setDate(s.getDate()-6);break;case"month":s.setMonth(s.getMonth()-1);break;case"3months":s.setMonth(s.getMonth()-3);break;case"6months":s.setMonth(s.getMonth()-6);break;case"1year":s.setFullYear(s.getFullYear()-1);break}return{startDate:Rm(s),endDate:r}}function O5(e){const{preset:t}=e.params||{preset:"today"},{startDate:n,endDate:r}=T5(t);return{startDate:n,endDate:r,preset:t}}const jo=window.G7Core?.createLogger?.("Handler:Sidebar")??{log:(...e)=>console.log("[Handler:Sidebar]",...e),warn:(...e)=>console.warn("[Handler:Sidebar]",...e),error:(...e)=>console.error("[Handler:Sidebar]",...e)},eb="g7_admin_sidebar_collapsed";function tb(){try{return localStorage.getItem(eb)==="1"}catch{return!1}}function A5(e){try{localStorage.setItem(eb,e?"1":"0")}catch(t){jo.error("Failed to save sidebar state to localStorage:",t)}try{window.G7Core?.state?.setGlobal?.({sidebarCollapsed:e})}catch(t){jo.warn("Failed to set global sidebar state:",t)}}function nb(){const e=tb();try{window.G7Core?.state?.setGlobal?.({sidebarCollapsed:e})}catch(t){jo.warn("Failed to restore sidebar state:",t)}jo.log("Initial sidebar collapsed state restored:",e)}async function D5(e,t){nb()}async function I5(e,t){const r=!(()=>{try{return!!window.G7Core?.state?.getGlobal?.()?.sidebarCollapsed}catch{return tb()}})();A5(r),jo.log("Sidebar collapsed toggled to:",r)}const Od=window.G7Core?.createLogger?.("Handler:DownloadAttachment")??{log:(...e)=>console.log("[Handler:DownloadAttachment]",...e),warn:(...e)=>console.warn("[Handler:DownloadAttachment]",...e),error:(...e)=>console.error("[Handler:DownloadAttachment]",...e)};async function L5(e,t){const n=window.G7Core,{url:r,filename:s}=e?.params||{};if(!r){Od.warn("다운로드 URL 이 없어 요청을 건너뜁니다.");return}if(!n?.api?.get){Od.error("G7Core.api.get 이 초기화되지 않아 다운로드를 실행할 수 없습니다.");return}try{const o=await n.api.get(r,{responseType:"blob"});if(o){const i=URL.createObjectURL(o),a=document.createElement("a");a.href=i,a.download=s||"",document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)}}catch(o){Od.error("첨부파일 다운로드 실패",o),n?.toast?.error?.(n?.t?.("common.download_failed")??"Download failed")}}const za=window.G7Core?.createLogger?.("Handler:DetectAssetUrlMode")??{log:(...e)=>console.log("[Handler:DetectAssetUrlMode]",...e),warn:(...e)=>console.warn("[Handler:DetectAssetUrlMode]",...e),error:(...e)=>console.error("[Handler:DetectAssetUrlMode]",...e)},P5="G7_ASSET_PROBE_OK";async function rb(e){try{const t=await fetch(e,{cache:"no-store",credentials:"omit"});if(!t.ok)return!1;const n=t.headers.get("content-type")??"";return/javascript|ecmascript/i.test(n)?(await t.text()).includes(P5):!1}catch{return!1}}async function sb(){const[e,t]=await Promise.all([rb("/api/system/asset-probe.js"),rb("/api/system/asset-probe")]);return e&&t?"extension":!e&&t?"extensionless":"unavailable"}function $5(){return window.G7Config?.settings?.general?.asset_url_mode??"extension"}async function F5(e){try{const t=await sb();if(t==="unavailable")return;const n=$5();if(t===n)return;window.G7Core?.state?.setGlobal?.({assetUrlModeDrift:{detected:t,stored:n}})}catch(t){za.warn("자산 URL 방식 대조 실패 (대시보드 동작에는 영향 없음):",t)}}async function z5(e,t){const n=window.G7Core,r=t?.setState??n?.state?.setLocal,s=o=>{typeof r=="function"?r({asset_url_mode_detect_status:o}):za.warn("setState 를 찾지 못해 감지 상태를 폼에 반영하지 못했습니다")};try{s("detecting");const o=await sb();if(o==="unavailable"){s("unavailable");return}typeof r=="function"?r({"general.asset_url_mode":o,asset_url_mode_detect_status:o}):za.warn("setState 를 찾지 못해 감지 결과를 폼에 반영하지 못했습니다")}catch(o){za.error("자산 URL 방식 감지 실패:",o),s("unavailable")}}const Va={detectAssetUrlMode:z5,checkAssetUrlModeDrift:F5,setTheme:y5,initTheme:b5,scrollToSection:fu,initMenuFromUrl:w5,initFilterVisibility:k5,saveFilterVisibility:C5,toggleFilterVisibility:S5,resetFilterVisibility:_5,saveMultilingualTag:E5,cancelMultilingualTag:M5,updateMultilingualTagValue:N5,setDateRange:O5,toggleSidebar:I5,initSidebar:D5,downloadAttachment:L5},Wo=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic:IdentityLauncher")??{log:(...e)=>console.log("[Template:sirsoft-admin_basic:IdentityLauncher]",...e),warn:(...e)=>console.warn("[Template:sirsoft-admin_basic:IdentityLauncher]",...e),error:(...e)=>console.error("[Template:sirsoft-admin_basic:IdentityLauncher]",...e)};function V5(){const t=window.G7Core?.api?.getToken?.()??null;return t?{Authorization:`Bearer ${t}`}:{}}function H5(){try{const e=typeof window<"u"?window.localStorage?.getItem("g7_locale"):null;return e?{"Accept-Language":e}:{}}catch{return{}}}async function U5(e,t=null){const n={purpose:e.purpose};e.provider_id&&(n.provider_id=e.provider_id),t&&(n.target=t);const r=e.challenge_start_url||"/api/identity/challenges",s=await fetch(r,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json",...H5(),...V5()},body:JSON.stringify(n)}),o=await s.json().catch(()=>null);if(!s.ok||!o?.success){const a=o?.message??`Challenge 시작 실패 (HTTP ${s.status})`;throw new Error(a)}const i=o.data??o;return{id:i.id,expires_at:i.expires_at,render_hint:i.render_hint??e.render_hint??"text_code",public_payload:i.public_payload,redirect_url:i.redirect_url,max_attempts:typeof i.max_attempts=="number"?i.max_attempts:void 0}}async function B5(e){const t=window.G7Core,n=t?.identity;if(!t?.dispatch||!t?.state||!n)return Wo.error("G7Core 또는 G7Core.identity 가 초기화되지 않아 launcher 를 실행할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};if(e.render_hint==="external_redirect"||e.redirect_url)return n.redirectExternally(e);const r=e.target&&(e.target.email||e.target.phone)?e.target:null;let s;try{s=await U5(e,r)}catch(m){Wo.error("Challenge 시작 실패:",m);try{await t.dispatch({handler:"toast",params:{type:"error",message:m instanceof Error?m.message:"본인인증 시작에 실패했습니다."}})}catch{}return{status:"failed",failureCode:"CHALLENGE_START_FAILED",reason:m instanceof Error?m.message:String(m)}}if(s.redirect_url)return n.redirectExternally({...e,render_hint:"external_redirect",redirect_url:s.redirect_url});const o=s.expires_at?new Date(s.expires_at).getTime():0,i=o>0?Math.max(0,Math.floor((o-Date.now())/1e3)):0,a=typeof s.max_attempts=="number"?s.max_attempts:0;t.state.set({identityChallenge:{policy_key:e.policy_key,purpose:e.purpose,provider_id:e.provider_id??null,render_hint:s.render_hint,challenge_id:s.id,expires_at:s.expires_at,public_payload:s.public_payload??{},target:r,code:"",error:null,attempts:0,maxAttempts:a,remainingSeconds:i,resendCooldown:0}});let l=o,c=0;const d=window.setInterval(()=>{const m=l>0?Math.max(0,Math.floor((l-Date.now())/1e3)):0;c=Math.max(0,c-1),t.state.set({identityChallenge:{remainingSeconds:m,resendCooldown:c}})},1e3);let u=null;try{u=t.state.subscribe?.(m=>{const p=m?.identityChallenge?.expires_at;if(typeof p=="string"){const b=new Date(p).getTime();Number.isFinite(b)&&b!==l&&(l=b)}const y=m?.identityChallenge?.resendCooldown;typeof y=="number"&&y>c&&(c=y)})??null}catch{}const f=n.createDeferred();try{await t.dispatch({handler:"openModal",target:"identity-challenge-modal"})}catch(m){return Wo.error("openModal 실패 — 풀페이지로 폴백:",m),window.clearInterval(d),u?.(),n.redirectExternally({...e,render_hint:"external_redirect",redirect_url:`/admin/identity/challenge?challenge_id=${encodeURIComponent(s.id)}&return=${encodeURIComponent(window.location.href)}`})}const g=await f;return window.clearInterval(d),u?.(),g}function j5(){const e=window.G7Core?.identity;if(!e?.setLauncher){Wo.warn("G7Core.identity 가 아직 초기화되지 않아 launcher 등록을 건너뜁니다. 코어 부트스트랩 순서를 확인하세요.");return}e.setLauncher(B5),Wo.log("IDV launcher registered (sirsoft-admin_basic)")}function W5(e){const t=e.icons;return Array.isArray(t)?t.filter(n=>!!n&&typeof n=="object"&&typeof n.value=="string"):[]}function G5(e,t){const n=e.label;return typeof n=="string"?n.startsWith("$t:")?t(n.slice(3)):n:e.value}function Y5(e,t,n){if(n==="")return!0;const r=n.toLowerCase();return e.value.toLowerCase().includes(r)||t.toLowerCase().includes(r)?!0:(e.keywords??[]).some(s=>s.toLowerCase().includes(r))}function X5({entry:e}){const t=e.preview;return t?.html?h.jsx("span",{className:"g7le-icon-preview",dangerouslySetInnerHTML:{__html:t.html},style:Dd}):t?.className?h.jsx("span",{className:`g7le-icon-preview ${t.className}`,style:Dd}):h.jsx("i",{className:`fas fa-${e.value}`,style:Dd,"aria-hidden":"true"})}function K5({control:e,value:t,onChange:n,t:r}){const s=W5(e),o=t==null?"":String(t),i=typeof e.iconColumns=="number"?e.iconColumns:6,a=e.iconSearchPlaceholder,l=typeof a=="string"&&a.startsWith("$t:")?r(a.slice(3)):r("layout_editor.icon_picker.search_placeholder"),[c,d]=v.useState("");if(s.length===0)return h.jsx("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:ob,children:h.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-free","data-testid":"g7le-widget-icon-picker-free",value:o,placeholder:r("layout_editor.icon_picker.free_input_hint"),onChange:g=>n(g.target.value===""?void 0:g.target.value),style:ib})});const u=g=>G5(g,r),f=s.filter(g=>Y5(g,u(g),c));return h.jsxs("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:ob,children:[h.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-search","data-testid":"g7le-widget-icon-picker-search",value:c,placeholder:l,onChange:g=>d(g.target.value),style:ib}),h.jsxs("div",{className:"g7le-icon-grid","data-testid":"g7le-widget-icon-picker-grid",style:{...q5,gridTemplateColumns:`repeat(${i}, minmax(0, 1fr))`},children:[h.jsx("button",{type:"button","data-testid":"g7le-icon-cell-none",title:r("layout_editor.icon_picker.none"),onClick:()=>n(void 0),"aria-pressed":o==="",style:o===""?ab:Ad,children:h.jsx("span",{style:Z5,children:"—"})}),f.map(g=>{const m=g.value===o;return h.jsx("button",{type:"button","data-testid":`g7le-icon-cell-${g.value}`,title:u(g),"aria-pressed":m,onClick:()=>n(g.value),style:m?ab:Ad,children:h.jsx(X5,{entry:g})},g.value)})]}),f.length===0&&h.jsx("div",{"data-testid":"g7le-widget-icon-picker-empty",style:Q5,children:r("layout_editor.icon_picker.no_results")})]})}const ob={display:"flex",flexDirection:"column",gap:6,width:"100%"},ib={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%"},q5={display:"grid",gap:4,maxHeight:220,overflowY:"auto",padding:2},Ad={display:"flex",alignItems:"center",justifyContent:"center",minHeight:34,padding:4,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",cursor:"pointer"},ab={...Ad,border:"2px solid #2563eb",background:"#eff6ff"},Dd={display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:16,lineHeight:1},Z5={fontSize:10,color:"#475569",fontFamily:"monospace",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},Q5={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"4px 2px"},lb=130,Dn=20;function J5(e){const t=e?.arrayProp;return typeof t=="string"&&t?t:"tabs"}function R5(e){let t=0;for(const n of e){const r=typeof n.id=="number"?n.id:Number(n.id);Number.isFinite(r)&&r>t&&(t=r)}return t+1}function eA({node:e,params:t,cellBoxes:n,t:r,onPatchNode:s}){const o=J5(t),i=v.useMemo(()=>{const g=(e.props??{})[o];return typeof g=="string"||!Array.isArray(g)?null:g},[e,o]),a=v.useMemo(()=>{const g=new Map,m=`props.${o}.`;for(const p of n??[]){if(!p.path.startsWith(m))continue;const y=Number(p.path.slice(m.length));Number.isInteger(y)&&g.set(y,{top:p.top,left:p.left,width:p.width,height:p.height})}return g},[n,o]),l=v.useCallback(g=>{const m=e.props??{};s({...e,props:{...m,[o]:g}})},[e,o,s]),c=v.useCallback(g=>{if(!i)return;const p={id:R5(i),label:r("editor.tabnav_inplace.new_tab")},y=i.slice();y.splice(g,0,p),l(y)},[i,l,r]),d=v.useCallback(g=>{if(!i)return;const m=i.slice();m.splice(g,1),l(m)},[i,l]),u=v.useCallback((g,m)=>{if(!i)return;const p=g+m;if(p<0||p>=i.length)return;const y=i.slice(),[b]=y.splice(g,1);y.splice(p,0,b),l(y)},[i,l]);if(!i||a.size===0)return null;const f={position:"absolute",width:Dn,height:Dn,display:"flex",alignItems:"center",justifyContent:"center",fontSize:12,lineHeight:1,border:"1px solid rgba(0,0,0,0.15)",borderRadius:4,background:"#ffffff",color:"#1f2937",cursor:"pointer",boxShadow:"0 1px 2px rgba(0,0,0,0.18)",pointerEvents:"auto",padding:0,zIndex:lb};return h.jsx("div",{className:"g7le-tabnav-inplace","data-testid":"g7le-tabnav-inplace",style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:lb},children:i.map((g,m)=>{const p=a.get(m);if(!p)return null;const y=p.left+p.width;return h.jsxs("div",{"data-testid":`g7le-tabnav-aff-${m}`,children:[h.jsx("button",{type:"button","data-testid":`g7le-tabnav-remove-${m}`,title:r("editor.tabnav_inplace.remove"),"aria-label":r("editor.tabnav_inplace.remove"),style:{...f,left:y-Dn/2,top:p.top-Dn/2,color:"#b91c1c"},onClick:b=>{b.preventDefault(),b.stopPropagation(),d(m)},children:"✕"}),m>0&&h.jsx("button",{type:"button","data-testid":`g7le-tabnav-move-left-${m}`,title:r("editor.tabnav_inplace.move_left"),"aria-label":r("editor.tabnav_inplace.move_left"),style:{...f,left:p.left,top:p.top+p.height-Dn/2},onClick:b=>{b.preventDefault(),b.stopPropagation(),u(m,-1)},children:"◀"}),m{b.preventDefault(),b.stopPropagation(),u(m,1)},children:"▶"}),m===0&&h.jsx("button",{type:"button","data-testid":"g7le-tabnav-add-0",title:r("editor.tabnav_inplace.add"),"aria-label":r("editor.tabnav_inplace.add"),style:{...f,left:p.left-Dn,top:p.top+p.height/2-Dn/2,color:"#15803d"},onClick:b=>{b.preventDefault(),b.stopPropagation(),c(0)},children:"+"}),h.jsx("button",{type:"button","data-testid":`g7le-tabnav-add-${m+1}`,title:r("editor.tabnav_inplace.add"),"aria-label":r("editor.tabnav_inplace.add"),style:{...f,left:y-Dn/2,top:p.top+p.height/2-Dn/2,color:"#15803d"},onClick:b=>{b.preventDefault(),b.stopPropagation(),c(m+1)},children:"+"})]},`tab-aff-${g.id}-${m}`)})})}const Id=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic:EditorWidgets")??{log:(...e)=>console.log("[Template:sirsoft-admin_basic:EditorWidgets]",...e),warn:(...e)=>console.warn("[Template:sirsoft-admin_basic:EditorWidgets]",...e)};function tA(){if(typeof window>"u")return;const e=window.G7Core?.layoutEditor;if(!e?.registerWidget){Id.warn("G7Core.layoutEditor 미노출 — icon-picker 위젯 등록 보류");return}e.registerWidget("icon-picker",K5),Id.log("편집기 위젯 'icon-picker' 등록됨(Font Awesome 검색 그리드)"),e.registerCanvasOverlay&&(e.registerCanvasOverlay("tabnav",eA),Id.log("편집기 캔버스 오버레이 'tabnav' 등록됨(TabNavigation 탭 인플레이스)"))}const Ld=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic")??{log:(...e)=>console.log("[Template:sirsoft-admin_basic]",...e),warn:(...e)=>console.warn("[Template:sirsoft-admin_basic]",...e),error:(...e)=>console.error("[Template:sirsoft-admin_basic]",...e)};typeof window<"u"&&(window.G7TemplateHandlers=Va);function cb(){if(typeof window<"u"){let e=0;const t=50,n=()=>{const r=window.G7Core?.getActionDispatcher?.();r?(Object.entries(Va).forEach(([s,o])=>{r.registerHandler(s,o)}),Ld.log(`${Object.keys(Va).length} custom handler(s) registered:`,Object.keys(Va)),j5(),nb()):(e++,e<=t?(Ld.warn(`ActionDispatcher not found, retrying... (${e}/${t})`),setTimeout(n,100)):Ld.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};document.readyState==="complete"?n():window.addEventListener("load",n)}}return tA(),cb(),R.A=Kr,R.Accordion=fc,R.ActionMenu=Ps,R.AdminFooter=Yy,R.AdminHeader=By,R.AdminSidebar=Uy,R.Alert=wu,R.AlertDialog=Dy,R.Badge=Wd,R.BarChart=s5,R.Breadcrumb=bu,R.Button=ie,R.Card=Mb,R.CardGrid=py,R.CategoryTree=l_,R.Checkbox=_n,R.ChipCheckbox=KE,R.Code=Cb,R.CodeEditor=lx,R.ColumnSelector=px,R.ConfirmDialog=pu,R.Container=i5,R.DataGrid=dy,R.Dialog=gu,R.Div=I,R.DonutChart=o5,R.Dropdown=by,R.DropdownButton=qE,R.DropdownMenuItem=ZE,R.DynamicFieldList=iM,R.EmptyState=zy,R.ExtensionBadge=gh,R.FileInput=$d,R.FileUploader=zp,R.FilterGroup=wy,R.FilterVisibilitySelector=BE,R.Flex=l5,R.Form=Ya,R.FormField=uM,R.Fragment=_b,R.Grid=a5,R.H1=ja,R.H2=ar,R.H3=Xr,R.H4=Fd,R.Hr=Eb,R.HtmlContent=il,R.HtmlEditor=zE,R.I=yt,R.Icon=se,R.IconButton=Ty,R.IconName=ce,R.IconSelect=n_,R.ImageGallery=Ji,R.Img=Sn,R.Input=_t,R.Label=dn,R.LanguageSelector=Gy,R.LayoutEditorHeader=Qy,R.LayoutFileList=Jy,R.LayoutHistoryPanel=dx,R.LayoutWarnings=UE,R.Li=Ga,R.LoadingSpinner=Hy,R.LoginForm=ux,R.Modal=Jo,R.MultilingualInput=Qa,R.MultilingualLocaleContext=Wc,R.MultilingualTabPanel=dM,R.MultilingualTagInput=dC,R.Nav=Xo,R.NotificationCenter=jy,R.Ol=mb,R.Optgroup=xb,R.Option=vb,R.P=wt,R.PageHeader=Xy,R.PageLoading=vx,R.PageTransitionIndicator=bx,R.Pagination=Ko,R.PermissionTree=i_,R.Pre=Sb,R.ProductCard=yy,R.RadioGroup=PE,R.RichSelect=YE,R.SearchBar=xy,R.SearchableDropdown=uC,R.Section=zd,R.SectionHeader=qy,R.SectionLayout=c5,R.Select=qr,R.SlotContainer=GE,R.SortableMenuItem=rg,R.SortableMenuList=e_,R.Span=Q,R.StatCard=vy,R.StatusBadge=qo,R.Svg=Qn,R.TabNavigation=ky,R.TabNavigationScroll=My,R.Table=Xa,R.TagInput=aC,R.TagSelect=fC,R.Tbody=qa,R.Td=Rt,R.TemplateCard=Zy,R.Textarea=ir,R.Tfoot=Za,R.Th=Zr,R.Thead=Ka,R.ThemeToggle=hx,R.ThreeColumnLayout=d5,R.Toast=$y,R.Toggle=Ja,R.Tr=lr,R.Ul=Wa,R.UnifiedIcon=kb,R.UserProfile=Wy,R.VersionList=Fu,R.configureJSONLanguageService=h5,R.defaultJSONSchemas=qm,R.executeImageDownload=Vc,R.initTemplate=cb,R.jsonEditorDefaults=u5,R.monacoInitOptions=f5,R.templateMetadata=g5,R.useCurrentMultilingualLocale=Gp,R.useImageGallery=Sp,Object.defineProperty(R,Symbol.toStringTag,{value:"Module"}),R})({},ReactJSXRuntime,React,ReactDOM); + `,role:"option","aria-selected":S,"data-item-value":k.value,"data-item-index":N,"data-item-selected":S,children:c?.item?Dp(c.item,{item:k,index:N,isSelected:S},`item-${N}`):a||C(k,S)},k.value)})})})]})};function NE(t){const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null}const OE=({value:t,checked:e=!1,icon:n,label:r,variant:s="blue",customColor:o,className:i="",style:a,onChange:l,id:c,editorAttrs:d})=>{const[u,f]=v.useState(!1),g="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium border cursor-pointer transition-all";if(o){const b=NE(o),_=document.documentElement.classList.contains("dark");let x;return e?x={backgroundColor:o,color:"#ffffff",borderColor:o,opacity:u?.85:1}:x=b?{backgroundColor:`rgba(${b.r}, ${b.g}, ${b.b}, ${u?_?.25:.12:_?.15:.06})`,color:o,borderColor:`rgba(${b.r}, ${b.g}, ${b.b}, ${_?.3:.2})`}:{},h.jsxs(sn,{className:`${g} ${i}`,style:{...x,...a},onMouseEnter:()=>f(!0),onMouseLeave:()=>f(!1),id:c,...d,children:[h.jsx(kt,{type:"checkbox",value:t,checked:e,onChange:E=>{l?.(E)},className:"hidden"}),n&&h.jsx(re,{name:n,className:"w-4 h-4"}),h.jsx(I,{children:r})]})}const y=e?{green:"bg-green-600 dark:bg-green-600 text-white dark:text-white border-green-700 dark:border-green-500 hover:bg-green-700 dark:hover:bg-green-500",orange:"bg-orange-600 dark:bg-orange-600 text-white dark:text-white border-orange-700 dark:border-orange-500 hover:bg-orange-700 dark:hover:bg-orange-500",yellow:"bg-yellow-600 dark:bg-yellow-600 text-white dark:text-white border-yellow-700 dark:border-yellow-500 hover:bg-yellow-700 dark:hover:bg-yellow-500",gray:"bg-gray-600 dark:bg-gray-500 text-white dark:text-white border-gray-700 dark:border-gray-400 hover:bg-gray-700 dark:hover:bg-gray-400",blue:"bg-blue-600 dark:bg-blue-600 text-white dark:text-white border-blue-700 dark:border-blue-500 hover:bg-blue-700 dark:hover:bg-blue-500"}[s]:{green:"bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 border-green-200 dark:border-green-800 hover:bg-green-100 dark:hover:bg-green-900/30",orange:"bg-orange-50 dark:bg-orange-900/20 text-orange-600 dark:text-orange-400 border-orange-200 dark:border-orange-800 hover:bg-orange-100 dark:hover:bg-orange-900/30",yellow:"bg-yellow-50 dark:bg-yellow-900/20 text-yellow-600 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800 hover:bg-yellow-100 dark:hover:bg-yellow-900/30",gray:"bg-gray-50 dark:bg-gray-700 text-gray-600 dark:text-gray-300 border-gray-200 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600",blue:"bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 border-blue-200 dark:border-blue-800 hover:bg-blue-100 dark:hover:bg-blue-900/30"}[s];return h.jsxs(sn,{className:`${g} ${y} ${i}`,style:a,id:c,...d,children:[h.jsx(kt,{type:"checkbox",value:t,checked:e,onChange:b=>{l?.(b)},className:"hidden"}),n&&h.jsx(re,{name:n,className:"w-4 h-4"}),h.jsx(I,{children:r})]})},TE=({label:t,icon:e,iconPosition:n="left",position:r="right",className:s="",style:o,children:i})=>{const[a,l]=v.useState(!1),[c,d]=v.useState({top:0,left:0}),u=v.useRef(null),f=v.useRef(null),g=v.useCallback(()=>{if(!u.current)return;const p=u.current.getBoundingClientRect(),y=224;let b=r==="right"?p.right-y:p.left;const _=p.bottom+8;b<8&&(b=8),b+y>window.innerWidth-8&&(b=window.innerWidth-y-8),d({top:_,left:b})},[r]);v.useEffect(()=>{a&&g()},[a,g]),v.useEffect(()=>{if(!a)return;const p=()=>{l(!1)};return window.addEventListener("scroll",p,!0),window.addEventListener("resize",p),()=>{window.removeEventListener("scroll",p,!0),window.removeEventListener("resize",p)}},[a]),v.useEffect(()=>{const p=y=>{const b=y.target,_=u.current?.contains(b),x=f.current?.contains(b);!_&&!x&&l(!1)};return a&&document.addEventListener("mousedown",p),()=>{document.removeEventListener("mousedown",p)}},[a]);const m=a?Tn.createPortal(h.jsx("div",{ref:f,className:"fixed z-[9999] w-56 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden",style:{top:c.top,left:c.left},onClick:()=>l(!1),children:i}),document.body):null;return h.jsxs(I,{className:`relative inline-block ${s}`,style:o,children:[h.jsxs(oe,{ref:u,onClick:()=>l(!a),className:"flex items-center gap-2",children:[n==="left"&&e&&h.jsx(re,{name:e,className:"w-4 h-4"}),t&&h.jsx(Q,{className:"text-sm font-medium",children:t}),n==="right"&&e&&h.jsx(re,{name:e,className:"w-4 h-4"})]}),m]})},AE=({label:t,icon:e,variant:n="default",disabled:r=!1,divider:s=!1,className:o="",style:i,onClick:a})=>{if(s)return h.jsx(I,{className:"h-px bg-gray-200 dark:bg-gray-700 my-1",style:i});const l=n==="danger"?"text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20":"text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700",c=r?"opacity-50 cursor-not-allowed":"cursor-pointer",d=()=>{!r&&a&&a()};return h.jsxs(I,{onClick:d,className:`flex items-center gap-3 px-4 py-3 transition-colors ${l} ${c} ${o}`,style:i,children:[e&&h.jsx(re,{name:e,className:"w-4 h-4"}),t&&h.jsx(Q,{className:"text-sm font-medium",children:t})]})},DE=()=>window.G7Core,$c=v.createContext(null),Ip=()=>{const t=v.useContext($c);if(!t){const e=DE(),n=e?.locale?.current?.()||"ko",r=e?.locale?.supported?.()||["ko","en"];return{activeLocale:n,getActiveLocale:()=>e?.locale?.current?.()||"ko",supportedLocales:r,isInsideMultilingualTabPanel:!1}}return{...t,isInsideMultilingualTabPanel:!0}},IE=()=>window.G7Core,LE=({value:t,onChange:e,placeholder:n,readOnly:r,disabled:s,hasError:o})=>{const{activeLocale:i,getActiveLocale:a}=Ip(),l=typeof t=="string"?{[i]:t}:t||{},c=v.useCallback(u=>{const f=a();e({...l,[f]:u.target.value})},[l,a,e]),d=o?"border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500":"border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500";return h.jsx(kt,{type:"text",value:l[i]||"",onChange:c,placeholder:n,readOnly:r,disabled:s,className:`w-full px-3 py-2 text-sm border ${d} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2`})},PE=(t,e)=>{if(!e)return!1;const n=e.startsWith("!"),r=n?e.slice(1):e,s=!!t[r];return n?!s:s},$E=({column:t,item:e,index:n,onValueChange:r,readOnly:s,error:o})=>{const i=e[t.key],a=s||t.readOnly||PE(e,t.readOnlyCondition)||t.disabled,l=!!o,c=l?"border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500":"border-gray-300 dark:border-gray-600 focus:ring-blue-500 focus:border-blue-500",d=v.useCallback(f=>{r(t.key,f)},[t.key,r]),u=f=>h.jsxs(I,{className:"w-full",children:[f,l&&h.jsx(Q,{className:"text-xs text-red-500 dark:text-red-400 mt-1 block",children:o})]});switch(t.type){case"input":return u(h.jsx(kt,{type:"text",value:i||"",onChange:f=>d(f.target.value),placeholder:t.placeholder,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${t.className||""}`}));case"number":return u(h.jsx(kt,{type:"number",value:i??"",onChange:f=>d(f.target.value?Number(f.target.value):null),placeholder:t.placeholder,min:t.min,max:t.max,step:t.step,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${t.className||""}`}));case"select":return u(h.jsxs(Pr,{value:i??"",onChange:f=>d(f.target.value),disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 ${t.className||""}`,children:[h.jsx("option",{value:"",children:"선택하세요"}),t.options?.map(f=>h.jsx("option",{value:f.value,disabled:f.disabled,children:f.label},f.value))]}));case"textarea":return u(h.jsx(Qn,{value:i||"",onChange:f=>d(f.target.value),placeholder:t.placeholder,readOnly:a,disabled:a,className:`w-full px-3 py-2 text-sm border ${c} rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 resize-none ${t.className||""}`,rows:2}));case"multilingual":return u(h.jsx(LE,{value:i,onChange:f=>d(f),placeholder:t.placeholder,readOnly:a,disabled:a,hasError:l}));case"custom":return t.render?h.jsx(h.Fragment,{children:t.render(e,n,d)}):h.jsx(Q,{className:"text-gray-500 dark:text-gray-400",children:"-"});default:return h.jsx(Q,{className:"text-gray-900 dark:text-white text-sm",children:String(i??"")})}},FE=(t,e,n)=>{if(!t)return;if(n==="multilingual"){const o=IE()?.locale?.current?.()||"ko",i=`${e}.${o}`;return t[i]?.[0]}return t[e]?.[0]},zE=({item:t,index:e,id:n,columns:r,enableDrag:s=!0,showIndex:o=!0,canRemove:i=!0,rowActions:a,readOnly:l=!1,onValueChange:c,onRemove:d,className:u="",level:f=0,errors:g})=>{const{attributes:m,listeners:p,setNodeRef:y,transform:b,transition:_,isDragging:x}=Ii({id:n,disabled:!s||l}),E={transform:Pn.Transform.toString(b),transition:_,opacity:x?.5:1},C=v.useCallback((S,w)=>{c?.(S,w)},[c]),k=v.useCallback(S=>{S.onClick?.(t,e)},[t,e]),N=f>0?{paddingLeft:`${f*24}px`}:{};return h.jsxs(I,{ref:y,style:E,className:`flex items-center gap-2 py-2 px-2 border-b border-gray-100 dark:border-gray-700 last:border-b-0 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors ${x?"bg-blue-50 dark:bg-blue-900/20 shadow-lg rounded-lg z-10":""} ${u}`,children:[s&&!l&&h.jsx(I,{...m,...p,className:"flex-shrink-0 w-6 h-6 flex items-center justify-center cursor-grab active:cursor-grabbing text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400",style:N,children:h.jsx(re,{name:"grip-vertical",className:"w-4 h-4"})}),o&&h.jsx(I,{className:"flex-shrink-0 w-8 text-center text-sm text-gray-500 dark:text-gray-400 font-medium",children:e+1}),r.map(S=>{const w=FE(g,S.key,S.type);return h.jsx(I,{className:`flex-1 min-w-0 ${S.width||""}`,style:S.width?{flex:"none",width:S.width}:{},children:h.jsx($E,{column:S,item:t,index:e,onValueChange:C,readOnly:l,error:w})},S.key)}),a&&a.length>0&&!l&&h.jsx(I,{className:"flex-shrink-0 flex items-center gap-1",children:a.map(S=>{const w=typeof S.disabled=="function"?S.disabled(t,e):S.disabled;return h.jsx(oe,{type:"button",onClick:()=>k(S),disabled:w,className:`p-1.5 rounded transition-colors ${S.danger?"text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 hover:text-red-600":"text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300"} disabled:opacity-50 disabled:cursor-not-allowed`,title:S.label,children:S.icon?h.jsx(re,{name:S.icon,className:"w-4 h-4"}):h.jsx(Q,{className:"text-xs",children:S.label})},S.key)})}),i&&!a&&!l&&h.jsx(I,{className:"flex-shrink-0",children:h.jsx(oe,{type:"button",onClick:d,className:"p-1.5 text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-600 dark:hover:text-gray-300 rounded transition-colors",title:"삭제",children:h.jsx(re,{name:"minus",className:"w-4 h-4"})})})]})},Lp=()=>window.G7Core,Fc=()=>`item_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,VE=t=>{const e={_id:Fc()};return t.forEach(n=>{if(n.type==="multilingual"){const s=Lp()?.locale?.all?.()||["ko","en"],o={};s.forEach(i=>{o[i]=""}),e[n.key]=o}else n.type==="number"?e[n.key]=null:(n.type,e[n.key]="")}),e},HE=(t,e)=>{if(!t)return{};const n=`fields.${e}.`,r={};return Object.keys(t).forEach(s=>{if(s.startsWith(n)){const o=s.substring(n.length);r[o]=t[s]}}),r},UE=({items:t=[],columns:e,onChange:n,onAddItem:r,onRemoveItem:s,onReorder:o,addLabel:i="항목 추가",enableDrag:a=!0,showIndex:l=!0,minItems:c=0,maxItems:d,rowActions:u,emptyMessage:f="항목이 없습니다.",className:g="",headerClassName:m="",rowClassName:p="",name:y,readOnly:b=!1,createDefaultItem:_,childrenKey:x,itemIdKey:E="_id",errors:C,id:k,editorAttrs:N})=>{const S=v.useRef(null),w=v.useRef([]),M=v.useRef(null),T=v.useMemo(()=>{let W=!1;for(const U of t)if(U[E]==null){W=!0;break}return W?t.map(U=>U[E]!=null?U:{...U,[E]:Fc()}):t},[t,E]);w.current=T,v.useEffect(()=>{M.current=null},[T]);const P=v.useCallback(W=>String(W[E]??""),[E]);v.useEffect(()=>{if(y){const W=Lp();W?.form&&(S.current=W.form)}},[y]);const L=Wl(qr(Xs,{activationConstraint:{distance:8}}),qr(Ys,{coordinateGetter:Li})),$=v.useMemo(()=>T.map(W=>P(W)),[T,P]),A=v.useCallback(W=>{const{active:U,over:B}=W;if(B&&U.id!==B.id){const G=M.current??w.current,V=G.map(Z=>String(Z[E]??"")),K=V.indexOf(String(U.id)),X=V.indexOf(String(B.id));if(K!==-1&&X!==-1){const Z=Qr([...G],K,X);M.current=Z,n?.(Z),o?.(Z)}}},[n,o,E]),D=v.useCallback((W,U,B)=>{const V=[...M.current??w.current];V[W]={...V[W],[U]:B},M.current=V,n?.(V)},[n]),O=v.useCallback(()=>{const W=M.current??w.current;if(d&&W.length>=d)return;if(r){r();return}const U=_?_():VE(e);U[E]||(U[E]=Fc());const B=[...W,U];M.current=B,n?.(B)},[d,r,_,e,E,n]),z=v.useCallback(W=>{const U=M.current??w.current;if(U.length<=c)return;const B=U[W];if(s){s(W,B);return}const G=U.filter((V,K)=>K!==W);M.current=G,n?.(G)},[c,s,n]),H=T.length>c,j=!d||T.lengthh.jsxs(I,{className:`flex-1 min-w-0 text-xs font-medium text-gray-500 dark:text-gray-400 uppercase ${W.width||""}`,style:W.width?{flex:"none",width:W.width}:{},children:[W.label,W.required&&h.jsx(Q,{className:"text-red-500 dark:text-red-400 ml-0.5",children:"*"})]},W.key)),(u||!b&&H)&&h.jsx(I,{className:"flex-shrink-0 w-8"})]}),T.length>0?h.jsx(tc,{sensors:L,collisionDetection:Gl,onDragEnd:A,children:h.jsx(Di,{items:$,strategy:nc,children:h.jsx(I,{className:"border-x border-gray-200 dark:border-gray-700",children:T.map((W,U)=>{const B=P(W),G=HE(C,U);return h.jsx(zE,{id:B,item:W,index:U,columns:e,enableDrag:a,showIndex:l,canRemove:H,rowActions:u,readOnly:b,onValueChange:(V,K)=>D(U,V,K),onRemove:()=>z(U),className:p,errors:G},B)})})})}):h.jsx(I,{className:"border-x border-gray-200 dark:border-gray-700 py-8 text-center text-gray-500 dark:text-gray-400 text-sm",children:f}),!b&&j&&h.jsx(I,{className:"relative border border-gray-200 dark:border-gray-700 border-t-0 rounded-b-lg",children:h.jsx(I,{className:"flex items-center justify-center py-2",children:h.jsxs(oe,{type:"button",onClick:O,className:"flex items-center gap-1.5 px-4 py-1.5 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",children:[h.jsx(re,{name:"plus",className:"w-4 h-4"}),h.jsx(Q,{children:i})]})})}),(b||!j)&&T.length>0&&h.jsx(I,{className:"border-x border-b border-gray-200 dark:border-gray-700 rounded-b-lg h-1"})]})},Pp=()=>window.G7Core,BE=()=>Pp()?.locale?.supported?.()??["ko","en"],jE=()=>Pp()?.locale?.current?.()??"ko",WE={ko:"한국어",en:"English",ja:"日本語",zh:"中文"},GE=({children:t,className:e="",style:n,variant:r="default",defaultLocale:s,onLocaleChange:o,locales:i})=>{const a=v.useMemo(()=>i&&i.length>0?i:BE(),[i]),[l,c]=v.useState(s||jE());v.useEffect(()=>{if(a.length>0&&!a.includes(l)){const p=a[0];c(p),o?.(p)}},[a,l,o]);const d=v.useRef(l);d.current=l;const u=v.useCallback(()=>d.current,[]),f=v.useCallback(p=>{c(p),o?.(p)},[o]),g=v.useMemo(()=>({activeLocale:l,getActiveLocale:u,supportedLocales:a}),[l,u,a]),m=p=>{const y=p===l,b="px-4 py-2 text-sm font-medium transition-all";return r==="pills"?y?`${b} bg-blue-600 text-white rounded-lg`:`${b} text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg`:r==="underline"?y?`${b} text-blue-600 dark:text-blue-400 border-b-2 border-blue-600 dark:border-blue-400`:`${b} text-gray-600 dark:text-gray-400 border-b-2 border-transparent hover:border-gray-300 dark:hover:border-gray-500`:y?`${b} text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20 border-b-2 border-blue-600`:`${b} text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700 border-b-2 border-transparent`};return h.jsxs(I,{className:e,style:n,children:[h.jsx(Po,{className:"flex gap-1 border-b border-gray-200 dark:border-gray-700 mb-4",children:a.map(p=>h.jsx(oe,{type:"button",onClick:()=>f(p),className:m(p),children:h.jsx(Q,{children:WE[p]||p})},p))}),h.jsx($c.Provider,{value:g,children:t})]})},YE=({label:t,required:e=!1,error:n,helperText:r,className:s="",labelClassName:o="",children:i,horizontal:a=!1,labelWidth:l="w-1/3",id:c,editorAttrs:d})=>{const u=a?`flex items-start gap-4 ${s}`:`space-y-1 ${s}`,f=a?`${l} flex-shrink-0 pt-2`:"",g=a?"flex-1":"";return h.jsxs(I,{className:u,id:c,...d,children:[t&&h.jsx(I,{className:f,children:h.jsxs(sn,{className:`block text-sm font-medium text-gray-700 dark:text-gray-300 ${o}`,children:[t,e&&h.jsx("span",{className:"ml-1 text-red-500 dark:text-red-400",children:"*"})]})}),h.jsxs(I,{className:`${g} ${n?"form-field-error":""}`.trim(),children:[i,r&&!n&&h.jsx(I,{className:"mt-1 text-xs text-gray-500 dark:text-gray-400",children:r}),n&&h.jsx(I,{className:"mt-1 text-xs text-red-600 dark:text-red-400",children:n})]})]})};function ao(t){return t+.5|0}const ir=(t,e,n)=>Math.max(Math.min(t,n),e);function lo(t){return ir(ao(t*2.55),0,255)}function ar(t){return ir(ao(t*255),0,255)}function Gn(t){return ir(ao(t/2.55)/100,0,1)}function $p(t){return ir(ao(t*100),0,100)}const hn={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},zc=[..."0123456789ABCDEF"],XE=t=>zc[t&15],KE=t=>zc[(t&240)>>4]+zc[t&15],Qi=t=>(t&240)>>4===(t&15),qE=t=>Qi(t.r)&&Qi(t.g)&&Qi(t.b)&&Qi(t.a);function ZE(t){var e=t.length,n;return t[0]==="#"&&(e===4||e===5?n={r:255&hn[t[1]]*17,g:255&hn[t[2]]*17,b:255&hn[t[3]]*17,a:e===5?hn[t[4]]*17:255}:(e===7||e===9)&&(n={r:hn[t[1]]<<4|hn[t[2]],g:hn[t[3]]<<4|hn[t[4]],b:hn[t[5]]<<4|hn[t[6]],a:e===9?hn[t[7]]<<4|hn[t[8]]:255})),n}const QE=(t,e)=>t<255?e(t):"";function JE(t){var e=qE(t)?XE:KE;return t?"#"+e(t.r)+e(t.g)+e(t.b)+QE(t.a,e):void 0}const RE=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Fp(t,e,n){const r=e*Math.min(n,1-n),s=(o,i=(o+t/30)%12)=>n-r*Math.max(Math.min(i-3,9-i,1),-1);return[s(0),s(8),s(4)]}function eM(t,e,n){const r=(s,o=(s+t/60)%6)=>n-n*e*Math.max(Math.min(o,4-o,1),0);return[r(5),r(3),r(1)]}function tM(t,e,n){const r=Fp(t,1,.5);let s;for(e+n>1&&(s=1/(e+n),e*=s,n*=s),s=0;s<3;s++)r[s]*=1-e-n,r[s]+=e;return r}function nM(t,e,n,r,s){return t===s?(e-n)/r+(e.5?d/(2-o-i):d/(o+i),l=nM(n,r,s,d,o),l=l*60+.5),[l|0,c||0,a]}function Hc(t,e,n,r){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,n,r)).map(ar)}function Uc(t,e,n){return Hc(Fp,t,e,n)}function rM(t,e,n){return Hc(tM,t,e,n)}function sM(t,e,n){return Hc(eM,t,e,n)}function zp(t){return(t%360+360)%360}function oM(t){const e=RE.exec(t);let n=255,r;if(!e)return;e[5]!==r&&(n=e[6]?lo(+e[5]):ar(+e[5]));const s=zp(+e[2]),o=+e[3]/100,i=+e[4]/100;return e[1]==="hwb"?r=rM(s,o,i):e[1]==="hsv"?r=sM(s,o,i):r=Uc(s,o,i),{r:r[0],g:r[1],b:r[2],a:n}}function iM(t,e){var n=Vc(t);n[0]=zp(n[0]+e),n=Uc(n),t.r=n[0],t.g=n[1],t.b=n[2]}function aM(t){if(!t)return;const e=Vc(t),n=e[0],r=$p(e[1]),s=$p(e[2]);return t.a<255?`hsla(${n}, ${r}%, ${s}%, ${Gn(t.a)})`:`hsl(${n}, ${r}%, ${s}%)`}const Vp={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Hp={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};function lM(){const t={},e=Object.keys(Hp),n=Object.keys(Vp);let r,s,o,i,a;for(r=0;r>16&255,o>>8&255,o&255]}return t}let Ji;function cM(t){Ji||(Ji=lM(),Ji.transparent=[0,0,0,0]);const e=Ji[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:e.length===4?e[3]:255}}const dM=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;function uM(t){const e=dM.exec(t);let n=255,r,s,o;if(e){if(e[7]!==r){const i=+e[7];n=e[8]?lo(i):ir(i*255,0,255)}return r=+e[1],s=+e[3],o=+e[5],r=255&(e[2]?lo(r):ir(r,0,255)),s=255&(e[4]?lo(s):ir(s,0,255)),o=255&(e[6]?lo(o):ir(o,0,255)),{r,g:s,b:o,a:n}}}function fM(t){return t&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${Gn(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`)}const Bc=t=>t<=.0031308?t*12.92:Math.pow(t,1/2.4)*1.055-.055,ls=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function hM(t,e,n){const r=ls(Gn(t.r)),s=ls(Gn(t.g)),o=ls(Gn(t.b));return{r:ar(Bc(r+n*(ls(Gn(e.r))-r))),g:ar(Bc(s+n*(ls(Gn(e.g))-s))),b:ar(Bc(o+n*(ls(Gn(e.b))-o))),a:t.a+n*(e.a-t.a)}}function Ri(t,e,n){if(t){let r=Vc(t);r[e]=Math.max(0,Math.min(r[e]+r[e]*n,e===0?360:1)),r=Uc(r),t.r=r[0],t.g=r[1],t.b=r[2]}}function Up(t,e){return t&&Object.assign(e||{},t)}function Bp(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=ar(t[3]))):(e=Up(t,{r:0,g:0,b:0,a:1}),e.a=ar(e.a)),e}function gM(t){return t.charAt(0)==="r"?uM(t):oM(t)}class co{constructor(e){if(e instanceof co)return e;const n=typeof e;let r;n==="object"?r=Bp(e):n==="string"&&(r=ZE(e)||cM(e)||gM(e)),this._rgb=r,this._valid=!!r}get valid(){return this._valid}get rgb(){var e=Up(this._rgb);return e&&(e.a=Gn(e.a)),e}set rgb(e){this._rgb=Bp(e)}rgbString(){return this._valid?fM(this._rgb):void 0}hexString(){return this._valid?JE(this._rgb):void 0}hslString(){return this._valid?aM(this._rgb):void 0}mix(e,n){if(e){const r=this.rgb,s=e.rgb;let o;const i=n===o?.5:n,a=2*i-1,l=r.a-s.a,c=((a*l===-1?a:(a+l)/(1+a*l))+1)/2;o=1-c,r.r=255&c*r.r+o*s.r+.5,r.g=255&c*r.g+o*s.g+.5,r.b=255&c*r.b+o*s.b+.5,r.a=i*r.a+(1-i)*s.a,this.rgb=r}return this}interpolate(e,n){return e&&(this._rgb=hM(this._rgb,e._rgb,n)),this}clone(){return new co(this.rgb)}alpha(e){return this._rgb.a=ar(e),this}clearer(e){const n=this._rgb;return n.a*=1-e,this}greyscale(){const e=this._rgb,n=ao(e.r*.3+e.g*.59+e.b*.11);return e.r=e.g=e.b=n,this}opaquer(e){const n=this._rgb;return n.a*=1+e,this}negate(){const e=this._rgb;return e.r=255-e.r,e.g=255-e.g,e.b=255-e.b,this}lighten(e){return Ri(this._rgb,2,e),this}darken(e){return Ri(this._rgb,2,-e),this}saturate(e){return Ri(this._rgb,1,e),this}desaturate(e){return Ri(this._rgb,1,-e),this}rotate(e){return iM(this._rgb,e),this}}function Yn(){}const pM=(()=>{let t=0;return()=>t++})();function lt(t){return t==null}function Et(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return e.slice(0,7)==="[object"&&e.slice(-6)==="Array]"}function Qe(t){return t!==null&&Object.prototype.toString.call(t)==="[object Object]"}function gn(t){return(typeof t=="number"||t instanceof Number)&&isFinite(+t)}function Fn(t,e){return gn(t)?t:e}function Je(t,e){return typeof t>"u"?e:t}const mM=(t,e)=>typeof t=="string"&&t.endsWith("%")?parseFloat(t)/100:+t/e,jp=(t,e)=>typeof t=="string"&&t.endsWith("%")?parseFloat(t)/100*e:+t;function gt(t,e,n){if(t&&typeof t.call=="function")return t.apply(n,e)}function at(t,e,n,r){let s,o,i;if(Et(t))for(o=t.length,s=0;st,x:t=>t.x,y:t=>t.y};function vM(t){const e=t.split("."),n=[];let r="";for(const s of e)r+=s,r.endsWith("\\")?r=r.slice(0,-1)+".":(n.push(r),r="");return n}function xM(t){const e=vM(t);return n=>{for(const r of e){if(r==="")break;n=n&&n[r]}return n}}function Sr(t,e){return(Gp[e]||(Gp[e]=xM(e)))(t)}function jc(t){return t.charAt(0).toUpperCase()+t.slice(1)}const ho=t=>typeof t<"u",lr=t=>typeof t=="function",Yp=(t,e)=>{if(t.size!==e.size)return!1;for(const n of t)if(!e.has(n))return!1;return!0};function wM(t){return t.type==="mouseup"||t.type==="click"||t.type==="contextmenu"}const ct=Math.PI,St=2*ct,na=Number.POSITIVE_INFINITY,kM=ct/180,Mt=ct/2,_r=ct/4,Xp=ct*2/3,Kp=Math.log10,cr=Math.sign;function ra(t,e,n){return Math.abs(t-e)s-o).pop(),e}function SM(t){return typeof t=="symbol"||typeof t=="object"&&t!==null&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}function sa(t){return!SM(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function _M(t,e){const n=Math.round(t);return n-e<=t&&n+e>=t}function EM(t,e,n){let r,s,o;for(r=0,s=t.length;rl&&c=Math.min(e,n)-r&&t<=Math.max(e,n)+r}function Wc(t,e,n){n=n||(i=>t[i]1;)o=s+r>>1,n(o)?s=o:r=o;return{lo:s,hi:r}}const Gc=(t,e,n,r)=>Wc(t,n,r?s=>{const o=t[s][e];return ot[s][e]Wc(t,n,r=>t[r][e]>=n);function AM(t,e,n){let r=0,s=t.length;for(;rr&&t[s-1]>n;)s--;return r>0||s{const r="_onData"+jc(n),s=t[n];Object.defineProperty(t,n,{configurable:!0,enumerable:!1,value(...o){const i=s.apply(this,o);return t._chartjs.listeners.forEach(a=>{typeof a[r]=="function"&&a[r](...o)}),i}})})}function Rp(t,e){const n=t._chartjs;if(!n)return;const r=n.listeners,s=r.indexOf(e);s!==-1&&r.splice(s,1),!(r.length>0)&&(Jp.forEach(o=>{delete t[o]}),delete t._chartjs)}function e0(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const t0=(function(){return typeof window>"u"?function(t){return t()}:window.requestAnimationFrame})();function n0(t,e){let n=[],r=!1;return function(...s){n=s,r||(r=!0,t0.call(window,()=>{r=!1,t.apply(e,n)}))}}function IM(t,e){let n;return function(...r){return e?(clearTimeout(n),n=setTimeout(t,e,r)):t.apply(this,r),e}}const Yc=t=>t==="start"?"left":t==="end"?"right":"center",Ut=(t,e,n)=>t==="start"?e:t==="end"?n:(e+n)/2,LM=(t,e,n,r)=>t===(r?"left":"right")?n:t==="center"?(e+n)/2:e,ia=t=>t===0||t===1,r0=(t,e,n)=>-(Math.pow(2,10*(t-=1))*Math.sin((t-e)*St/n)),s0=(t,e,n)=>Math.pow(2,-10*t)*Math.sin((t-e)*St/n)+1,go={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>-Math.cos(t*Mt)+1,easeOutSine:t=>Math.sin(t*Mt),easeInOutSine:t=>-.5*(Math.cos(ct*t)-1),easeInExpo:t=>t===0?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>t===1?1:-Math.pow(2,-10*t)+1,easeInOutExpo:t=>ia(t)?t:t<.5?.5*Math.pow(2,10*(t*2-1)):.5*(-Math.pow(2,-10*(t*2-1))+2),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ia(t)?t:r0(t,.075,.3),easeOutElastic:t=>ia(t)?t:s0(t,.075,.3),easeInOutElastic(t){return ia(t)?t:t<.5?.5*r0(t*2,.1125,.45):.5+.5*s0(t*2-1,.1125,.45)},easeInBack(t){return t*t*((1.70158+1)*t-1.70158)},easeOutBack(t){return(t-=1)*t*((1.70158+1)*t+1.70158)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?.5*(t*t*(((e*=1.525)+1)*t-e)):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:t=>1-go.easeOutBounce(1-t),easeOutBounce(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:t=>t<.5?go.easeInBounce(t*2)*.5:go.easeOutBounce(t*2-1)*.5+.5};function o0(t){if(t&&typeof t=="object"){const e=t.toString();return e==="[object CanvasPattern]"||e==="[object CanvasGradient]"}return!1}function i0(t){return o0(t)?t:new co(t)}function Xc(t){return o0(t)?t:new co(t).saturate(.5).darken(.1).hexString()}const PM=["x","y","borderWidth","radius","tension"],$M=["color","borderColor","backgroundColor"];function FM(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:e=>e!=="onProgress"&&e!=="onComplete"&&e!=="fn"}),t.set("animations",{colors:{type:"color",properties:$M},numbers:{type:"number",properties:PM}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:e=>e|0}}}})}function zM(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})}const a0=new Map;function VM(t,e){e=e||{};const n=t+JSON.stringify(e);let r=a0.get(n);return r||(r=new Intl.NumberFormat(t,e),a0.set(n,r)),r}function Kc(t,e,n){return VM(e,n).format(t)}const HM={values(t){return Et(t)?t:""+t},numeric(t,e,n){if(t===0)return"0";const r=this.chart.options.locale;let s,o=t;if(n.length>1){const c=Math.max(Math.abs(n[0].value),Math.abs(n[n.length-1].value));(c<1e-4||c>1e15)&&(s="scientific"),o=UM(t,n)}const i=Kp(Math.abs(o)),a=isNaN(i)?1:Math.max(Math.min(-1*Math.floor(i),20),0),l={notation:s,minimumFractionDigits:a,maximumFractionDigits:a};return Object.assign(l,this.options.ticks.format),Kc(t,r,l)}};function UM(t,e){let n=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;return Math.abs(n)>=1&&t!==Math.floor(t)&&(n=t-Math.floor(t)),n}var l0={formatters:HM};function BM(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(e,n)=>n.lineWidth,tickColor:(e,n)=>n.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:l0.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:e=>!e.startsWith("before")&&!e.startsWith("after")&&e!=="callback"&&e!=="parser",_indexable:e=>e!=="borderDash"&&e!=="tickBorderDash"&&e!=="dash"}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:e=>e!=="backdropPadding"&&e!=="callback",_indexable:e=>e!=="backdropPadding"})}const Mr=Object.create(null),qc=Object.create(null);function po(t,e){if(!e)return t;const n=e.split(".");for(let r=0,s=n.length;rr.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(r,s)=>Xc(s.backgroundColor),this.hoverBorderColor=(r,s)=>Xc(s.borderColor),this.hoverColor=(r,s)=>Xc(s.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(e),this.apply(n)}set(e,n){return Zc(this,e,n)}get(e){return po(this,e)}describe(e,n){return Zc(qc,e,n)}override(e,n){return Zc(Mr,e,n)}route(e,n,r,s){const o=po(this,e),i=po(this,r),a="_"+n;Object.defineProperties(o,{[a]:{value:o[n],writable:!0},[n]:{enumerable:!0,get(){const l=this[a],c=i[s];return Qe(l)?Object.assign({},c,l):Je(l,c)},set(l){this[a]=l}}})}apply(e){e.forEach(n=>n(this))}}var xt=new jM({_scriptable:t=>!t.startsWith("on"),_indexable:t=>t!=="events",hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[FM,zM,BM]);function WM(t){return!t||lt(t.size)||lt(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function c0(t,e,n,r,s){let o=e[s];return o||(o=e[s]=t.measureText(s).width,n.push(s)),o>r&&(r=o),r}function Nr(t,e,n){const r=t.currentDevicePixelRatio,s=n!==0?Math.max(n/2,.5):0;return Math.round((e-s)*r)/r+s}function d0(t,e){!e&&!t||(e=e||t.getContext("2d"),e.save(),e.resetTransform(),e.clearRect(0,0,t.width,t.height),e.restore())}function u0(t,e,n,r){f0(t,e,n,r,null)}function f0(t,e,n,r,s){let o,i,a,l,c,d,u,f;const g=e.pointStyle,m=e.rotation,p=e.radius;let y=(m||0)*kM;if(g&&typeof g=="object"&&(o=g.toString(),o==="[object HTMLImageElement]"||o==="[object HTMLCanvasElement]")){t.save(),t.translate(n,r),t.rotate(y),t.drawImage(g,-g.width/2,-g.height/2,g.width,g.height),t.restore();return}if(!(isNaN(p)||p<=0)){switch(t.beginPath(),g){default:s?t.ellipse(n,r,s/2,p,0,0,St):t.arc(n,r,p,0,St),t.closePath();break;case"triangle":d=s?s/2:p,t.moveTo(n+Math.sin(y)*d,r-Math.cos(y)*p),y+=Xp,t.lineTo(n+Math.sin(y)*d,r-Math.cos(y)*p),y+=Xp,t.lineTo(n+Math.sin(y)*d,r-Math.cos(y)*p),t.closePath();break;case"rectRounded":c=p*.516,l=p-c,i=Math.cos(y+_r)*l,u=Math.cos(y+_r)*(s?s/2-c:l),a=Math.sin(y+_r)*l,f=Math.sin(y+_r)*(s?s/2-c:l),t.arc(n-u,r-a,c,y-ct,y-Mt),t.arc(n+f,r-i,c,y-Mt,y),t.arc(n+u,r+a,c,y,y+Mt),t.arc(n-f,r+i,c,y+Mt,y+ct),t.closePath();break;case"rect":if(!m){l=Math.SQRT1_2*p,d=s?s/2:l,t.rect(n-d,r-l,2*d,2*l);break}y+=_r;case"rectRot":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),t.moveTo(n-u,r-a),t.lineTo(n+f,r-i),t.lineTo(n+u,r+a),t.lineTo(n-f,r+i),t.closePath();break;case"crossRot":y+=_r;case"cross":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),t.moveTo(n-u,r-a),t.lineTo(n+u,r+a),t.moveTo(n+f,r-i),t.lineTo(n-f,r+i);break;case"star":u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),t.moveTo(n-u,r-a),t.lineTo(n+u,r+a),t.moveTo(n+f,r-i),t.lineTo(n-f,r+i),y+=_r,u=Math.cos(y)*(s?s/2:p),i=Math.cos(y)*p,a=Math.sin(y)*p,f=Math.sin(y)*(s?s/2:p),t.moveTo(n-u,r-a),t.lineTo(n+u,r+a),t.moveTo(n+f,r-i),t.lineTo(n-f,r+i);break;case"line":i=s?s/2:Math.cos(y)*p,a=Math.sin(y)*p,t.moveTo(n-i,r-a),t.lineTo(n+i,r+a);break;case"dash":t.moveTo(n,r),t.lineTo(n+Math.cos(y)*(s?s/2:p),r+Math.sin(y)*p);break;case!1:t.closePath();break}t.fill(),e.borderWidth>0&&t.stroke()}}function h0(t,e,n){return n=n||.5,!e||t&&t.x>e.left-n&&t.xe.top-n&&t.y0&&o.strokeColor!=="";let l,c;for(t.save(),t.font=s.string,GM(t,o),l=0;l+t||0;function Rc(t,e){const n={},r=Qe(e),s=r?Object.keys(e):e,o=Qe(t)?r?i=>Je(t[i],t[e[i]]):i=>t[i]:()=>t;for(const i of s)n[i]=QM(o(i));return n}function g0(t){return Rc(t,{top:"y",right:"x",bottom:"y",left:"x"})}function cs(t){return Rc(t,["topLeft","topRight","bottomLeft","bottomRight"])}function pn(t){const e=g0(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Bt(t,e){t=t||{},e=e||xt.font;let n=Je(t.size,e.size);typeof n=="string"&&(n=parseInt(n,10));let r=Je(t.style,e.style);r&&!(""+r).match(qM)&&(console.warn('Invalid font style specified: "'+r+'"'),r=void 0);const s={family:Je(t.family,e.family),lineHeight:ZM(Je(t.lineHeight,e.lineHeight),n),size:n,style:r,weight:Je(t.weight,e.weight),string:""};return s.string=WM(s),s}function la(t,e,n,r){let s,o,i;for(s=0,o=t.length;sn&&a===0?0:a+l;return{min:i(r,-Math.abs(o)),max:i(s,o)}}function ds(t,e){return Object.assign(Object.create(t),e)}function ed(t,e=[""],n,r,s=()=>t[0]){const o=n||t;typeof r>"u"&&(r=v0("_fallback",t));const i={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:r,_getTarget:s,override:a=>ed([a,...t],e,o,r)};return new Proxy(i,{deleteProperty(a,l){return delete a[l],delete a._keys,delete t[0][l],!0},get(a,l){return m0(a,l,()=>iN(l,e,t,a))},getOwnPropertyDescriptor(a,l){return Reflect.getOwnPropertyDescriptor(a._scopes[0],l)},getPrototypeOf(){return Reflect.getPrototypeOf(t[0])},has(a,l){return x0(a).includes(l)},ownKeys(a){return x0(a)},set(a,l,c){const d=a._storage||(a._storage=s());return a[l]=d[l]=c,delete a._keys,!0}})}function us(t,e,n,r){const s={_cacheable:!1,_proxy:t,_context:e,_subProxy:n,_stack:new Set,_descriptors:p0(t,r),setContext:o=>us(t,o,n,r),override:o=>us(t.override(o),e,n,r)};return new Proxy(s,{deleteProperty(o,i){return delete o[i],delete t[i],!0},get(o,i,a){return m0(o,i,()=>eN(o,i,a))},getOwnPropertyDescriptor(o,i){return o._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i)},getPrototypeOf(){return Reflect.getPrototypeOf(t)},has(o,i){return Reflect.has(t,i)},ownKeys(){return Reflect.ownKeys(t)},set(o,i,a){return t[i]=a,delete o[i],!0}})}function p0(t,e={scriptable:!0,indexable:!0}){const{_scriptable:n=e.scriptable,_indexable:r=e.indexable,_allKeys:s=e.allKeys}=t;return{allKeys:s,scriptable:n,indexable:r,isScriptable:lr(n)?n:()=>n,isIndexable:lr(r)?r:()=>r}}const RM=(t,e)=>t?t+jc(e):e,td=(t,e)=>Qe(e)&&t!=="adapters"&&(Object.getPrototypeOf(e)===null||e.constructor===Object);function m0(t,e,n){if(Object.prototype.hasOwnProperty.call(t,e)||e==="constructor")return t[e];const r=n();return t[e]=r,r}function eN(t,e,n){const{_proxy:r,_context:s,_subProxy:o,_descriptors:i}=t;let a=r[e];return lr(a)&&i.isScriptable(e)&&(a=tN(e,a,t,n)),Et(a)&&a.length&&(a=nN(e,a,t,i.isIndexable)),td(e,a)&&(a=us(a,s,o&&o[e],i)),a}function tN(t,e,n,r){const{_proxy:s,_context:o,_subProxy:i,_stack:a}=n;if(a.has(t))throw new Error("Recursion detected: "+Array.from(a).join("->")+"->"+t);a.add(t);let l=e(o,i||r);return a.delete(t),td(t,l)&&(l=nd(s._scopes,s,t,l)),l}function nN(t,e,n,r){const{_proxy:s,_context:o,_subProxy:i,_descriptors:a}=n;if(typeof o.index<"u"&&r(t))return e[o.index%e.length];if(Qe(e[0])){const l=e,c=s._scopes.filter(d=>d!==l);e=[];for(const d of l){const u=nd(c,s,t,d);e.push(us(u,o,i&&i[t],a))}}return e}function b0(t,e,n){return lr(t)?t(e,n):t}const rN=(t,e)=>t===!0?e:typeof t=="string"?Sr(e,t):void 0;function sN(t,e,n,r,s){for(const o of e){const i=rN(n,o);if(i){t.add(i);const a=b0(i._fallback,n,s);if(typeof a<"u"&&a!==n&&a!==r)return a}else if(i===!1&&typeof r<"u"&&n!==r)return null}return!1}function nd(t,e,n,r){const s=e._rootScopes,o=b0(e._fallback,n,r),i=[...t,...s],a=new Set;a.add(r);let l=y0(a,i,n,o||n,r);return l===null||typeof o<"u"&&o!==n&&(l=y0(a,i,o,l,r),l===null)?!1:ed(Array.from(a),[""],s,o,()=>oN(e,n,r))}function y0(t,e,n,r,s){for(;n;)n=sN(t,e,n,r,s);return n}function oN(t,e,n){const r=t._getTarget();e in r||(r[e]={});const s=r[e];return Et(s)&&Qe(n)?n:s||{}}function iN(t,e,n,r){let s;for(const o of e)if(s=v0(RM(o,t),n),typeof s<"u")return td(t,s)?nd(n,r,t,s):s}function v0(t,e){for(const n of e){if(!n)continue;const r=n[t];if(typeof r<"u")return r}}function x0(t){let e=t._keys;return e||(e=t._keys=aN(t._scopes)),e}function aN(t){const e=new Set;for(const n of t)for(const r of Object.keys(n).filter(s=>!s.startsWith("_")))e.add(r);return Array.from(e)}function rd(){return typeof window<"u"&&typeof document<"u"}function sd(t){let e=t.parentNode;return e&&e.toString()==="[object ShadowRoot]"&&(e=e.host),e}function ca(t,e,n){let r;return typeof t=="string"?(r=parseInt(t,10),t.indexOf("%")!==-1&&(r=r/100*e.parentNode[n])):r=t,r}const da=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function lN(t,e){return da(t).getPropertyValue(e)}const cN=["top","right","bottom","left"];function Or(t,e,n){const r={};n=n?"-"+n:"";for(let s=0;s<4;s++){const o=cN[s];r[o]=parseFloat(t[e+"-"+o+n])||0}return r.width=r.left+r.right,r.height=r.top+r.bottom,r}const dN=(t,e,n)=>(t>0||e>0)&&(!n||!n.shadowRoot);function uN(t,e){const n=t.touches,r=n&&n.length?n[0]:t,{offsetX:s,offsetY:o}=r;let i=!1,a,l;if(dN(s,o,t.target))a=s,l=o;else{const c=e.getBoundingClientRect();a=r.clientX-c.left,l=r.clientY-c.top,i=!0}return{x:a,y:l,box:i}}function Tr(t,e){if("native"in t)return t;const{canvas:n,currentDevicePixelRatio:r}=e,s=da(n),o=s.boxSizing==="border-box",i=Or(s,"padding"),a=Or(s,"border","width"),{x:l,y:c,box:d}=uN(t,n),u=i.left+(d&&a.left),f=i.top+(d&&a.top);let{width:g,height:m}=e;return o&&(g-=i.width+a.width,m-=i.height+a.height),{x:Math.round((l-u)/g*n.width/r),y:Math.round((c-f)/m*n.height/r)}}function fN(t,e,n){let r,s;if(e===void 0||n===void 0){const o=t&&sd(t);if(!o)e=t.clientWidth,n=t.clientHeight;else{const i=o.getBoundingClientRect(),a=da(o),l=Or(a,"border","width"),c=Or(a,"padding");e=i.width-c.width-l.width,n=i.height-c.height-l.height,r=ca(a.maxWidth,o,"clientWidth"),s=ca(a.maxHeight,o,"clientHeight")}}return{width:e,height:n,maxWidth:r||na,maxHeight:s||na}}const dr=t=>Math.round(t*10)/10;function hN(t,e,n,r){const s=da(t),o=Or(s,"margin"),i=ca(s.maxWidth,t,"clientWidth")||na,a=ca(s.maxHeight,t,"clientHeight")||na,l=fN(t,e,n);let{width:c,height:d}=l;if(s.boxSizing==="content-box"){const f=Or(s,"border","width"),g=Or(s,"padding");c-=g.width+f.width,d-=g.height+f.height}return c=Math.max(0,c-o.width),d=Math.max(0,r?c/r:d-o.height),c=dr(Math.min(c,i,l.maxWidth)),d=dr(Math.min(d,a,l.maxHeight)),c&&!d&&(d=dr(c/2)),(e!==void 0||n!==void 0)&&r&&l.height&&d>l.height&&(d=l.height,c=dr(Math.floor(d*r))),{width:c,height:d}}function w0(t,e,n){const r=e||1,s=dr(t.height*r),o=dr(t.width*r);t.height=dr(t.height),t.width=dr(t.width);const i=t.canvas;return i.style&&(n||!i.style.height&&!i.style.width)&&(i.style.height=`${t.height}px`,i.style.width=`${t.width}px`),t.currentDevicePixelRatio!==r||i.height!==s||i.width!==o?(t.currentDevicePixelRatio=r,i.height=s,i.width=o,t.ctx.setTransform(r,0,0,r,0,0),!0):!1}const gN=(function(){let t=!1;try{const e={get passive(){return t=!0,!1}};rd()&&(window.addEventListener("test",null,e),window.removeEventListener("test",null,e))}catch{}return t})();function k0(t,e){const n=lN(t,e),r=n&&n.match(/^(\d+)(\.\d+)?px$/);return r?+r[1]:void 0}const pN=function(t,e){return{x(n){return t+t+e-n},setWidth(n){e=n},textAlign(n){return n==="center"?n:n==="right"?"left":"right"},xPlus(n,r){return n-r},leftForLtr(n,r){return n-r}}},mN=function(){return{x(t){return t},setWidth(t){},textAlign(t){return t},xPlus(t,e){return t+e},leftForLtr(t,e){return t}}};function fs(t,e,n){return t?pN(e,n):mN()}function C0(t,e){let n,r;(e==="ltr"||e==="rtl")&&(n=t.canvas.style,r=[n.getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",e,"important"),t.prevTextDirection=r)}function S0(t,e){e!==void 0&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function ua(t,e,n){return t.options.clip?t[n]:e[n]}function bN(t,e){const{xScale:n,yScale:r}=t;return n&&r?{left:ua(n,e,"left"),right:ua(n,e,"right"),top:ua(r,e,"top"),bottom:ua(r,e,"bottom")}:e}function yN(t,e){const n=e._clip;if(n.disabled)return!1;const r=bN(e,t.chartArea);return{left:n.left===!1?0:r.left-(n.left===!0?0:n.left),right:n.right===!1?t.width:r.right+(n.right===!0?0:n.right),top:n.top===!1?0:r.top-(n.top===!0?0:n.top),bottom:n.bottom===!1?t.height:r.bottom+(n.bottom===!0?0:n.bottom)}}class vN{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(e,n,r,s){const o=n.listeners[s],i=n.duration;o.forEach(a=>a({chart:e,initial:n.initial,numSteps:i,currentStep:Math.min(r-n.start,i)}))}_refresh(){this._request||(this._running=!0,this._request=t0.call(window,()=>{this._update(),this._request=null,this._running&&this._refresh()}))}_update(e=Date.now()){let n=0;this._charts.forEach((r,s)=>{if(!r.running||!r.items.length)return;const o=r.items;let i=o.length-1,a=!1,l;for(;i>=0;--i)l=o[i],l._active?(l._total>r.duration&&(r.duration=l._total),l.tick(e),a=!0):(o[i]=o[o.length-1],o.pop());a&&(s.draw(),this._notify(s,r,e,"progress")),o.length||(r.running=!1,this._notify(s,r,e,"complete"),r.initial=!1),n+=o.length}),this._lastDate=e,n===0&&(this._running=!1)}_getAnims(e){const n=this._charts;let r=n.get(e);return r||(r={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},n.set(e,r)),r}listen(e,n,r){this._getAnims(e).listeners[n].push(r)}add(e,n){!n||!n.length||this._getAnims(e).items.push(...n)}has(e){return this._getAnims(e).items.length>0}start(e){const n=this._charts.get(e);n&&(n.running=!0,n.start=Date.now(),n.duration=n.items.reduce((r,s)=>Math.max(r,s._duration),0),this._refresh())}running(e){if(!this._running)return!1;const n=this._charts.get(e);return!(!n||!n.running||!n.items.length)}stop(e){const n=this._charts.get(e);if(!n||!n.items.length)return;const r=n.items;let s=r.length-1;for(;s>=0;--s)r[s].cancel();n.items=[],this._notify(e,n,Date.now(),"complete")}remove(e){return this._charts.delete(e)}}var Kn=new vN;const _0="transparent",xN={boolean(t,e,n){return n>.5?e:t},color(t,e,n){const r=i0(t||_0),s=r.valid&&i0(e||_0);return s&&s.valid?s.mix(r,n).hexString():e},number(t,e,n){return t+(e-t)*n}};class wN{constructor(e,n,r,s){const o=n[r];s=la([e.to,s,o,e.from]);const i=la([e.from,o,s]);this._active=!0,this._fn=e.fn||xN[e.type||typeof i],this._easing=go[e.easing]||go.linear,this._start=Math.floor(Date.now()+(e.delay||0)),this._duration=this._total=Math.floor(e.duration),this._loop=!!e.loop,this._target=n,this._prop=r,this._from=i,this._to=s,this._promises=void 0}active(){return this._active}update(e,n,r){if(this._active){this._notify(!1);const s=this._target[this._prop],o=r-this._start,i=this._duration-o;this._start=r,this._duration=Math.floor(Math.max(i,e.duration)),this._total+=o,this._loop=!!e.loop,this._to=la([e.to,n,s,e.from]),this._from=la([e.from,s,n])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(e){const n=e-this._start,r=this._duration,s=this._prop,o=this._from,i=this._loop,a=this._to;let l;if(this._active=o!==a&&(i||n1?2-l:l,l=this._easing(Math.min(1,Math.max(0,l))),this._target[s]=this._fn(o,a,l)}wait(){const e=this._promises||(this._promises=[]);return new Promise((n,r)=>{e.push({res:n,rej:r})})}_notify(e){const n=e?"res":"rej",r=this._promises||[];for(let s=0;s{const o=e[s];if(!Qe(o))return;const i={};for(const a of n)i[a]=o[a];(Et(o.properties)&&o.properties||[s]).forEach(a=>{(a===s||!r.has(a))&&r.set(a,i)})})}_animateOptions(e,n){const r=n.options,s=CN(e,r);if(!s)return[];const o=this._createAnimations(s,r);return r.$shared&&kN(e.options.$animations,r).then(()=>{e.options=r},()=>{}),o}_createAnimations(e,n){const r=this._properties,s=[],o=e.$animations||(e.$animations={}),i=Object.keys(n),a=Date.now();let l;for(l=i.length-1;l>=0;--l){const c=i[l];if(c.charAt(0)==="$")continue;if(c==="options"){s.push(...this._animateOptions(e,n));continue}const d=n[c];let u=o[c];const f=r.get(c);if(u)if(f&&u.active()){u.update(f,d,a);continue}else u.cancel();if(!f||!f.duration){e[c]=d;continue}o[c]=u=new wN(f,e,c,d),s.push(u)}return s}update(e,n){if(this._properties.size===0){Object.assign(e,n);return}const r=this._createAnimations(e,n);if(r.length)return Kn.add(this._chart,r),!0}}function kN(t,e){const n=[],r=Object.keys(e);for(let s=0;s0||!n&&o<0)return s.index}return null}function A0(t,e){const{chart:n,_cachedMeta:r}=t,s=n._stacks||(n._stacks={}),{iScale:o,vScale:i,index:a}=r,l=o.axis,c=i.axis,d=MN(o,i,r),u=e.length;let f;for(let g=0;gn[r].axis===e).shift()}function TN(t,e){return ds(t,{active:!1,dataset:void 0,datasetIndex:e,index:e,mode:"default",type:"dataset"})}function AN(t,e,n){return ds(t,{active:!1,dataIndex:e,parsed:void 0,raw:void 0,element:n,index:e,mode:"default",type:"data"})}function bo(t,e){const n=t.controller.index,r=t.vScale&&t.vScale.axis;if(r){e=e||t._parsed;for(const s of e){const o=s._stacks;if(!o||o[r]===void 0||o[r][n]===void 0)return;delete o[r][n],o[r]._visualValues!==void 0&&o[r]._visualValues[n]!==void 0&&delete o[r]._visualValues[n]}}}const ad=t=>t==="reset"||t==="none",D0=(t,e)=>e?t:Object.assign({},t),DN=(t,e,n)=>t&&!e.hidden&&e._stacked&&{keys:N0(n,!0),values:null};class hs{constructor(e,n){this.chart=e,this._ctx=e.ctx,this.index=n,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const e=this._cachedMeta;this.configure(),this.linkScales(),e._stacked=od(e.vScale,e),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(e){this.index!==e&&bo(this._cachedMeta),this.index=e}linkScales(){const e=this.chart,n=this._cachedMeta,r=this.getDataset(),s=(u,f,g,m)=>u==="x"?f:u==="r"?m:g,o=n.xAxisID=Je(r.xAxisID,id(e,"x")),i=n.yAxisID=Je(r.yAxisID,id(e,"y")),a=n.rAxisID=Je(r.rAxisID,id(e,"r")),l=n.indexAxis,c=n.iAxisID=s(l,o,i,a),d=n.vAxisID=s(l,i,o,a);n.xScale=this.getScaleForId(o),n.yScale=this.getScaleForId(i),n.rScale=this.getScaleForId(a),n.iScale=this.getScaleForId(c),n.vScale=this.getScaleForId(d)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(e){return this.chart.scales[e]}_getOtherScale(e){const n=this._cachedMeta;return e===n.iScale?n.vScale:n.iScale}reset(){this._update("reset")}_destroy(){const e=this._cachedMeta;this._data&&Rp(this._data,this),e._stacked&&bo(e)}_dataCheck(){const e=this.getDataset(),n=e.data||(e.data=[]),r=this._data;if(Qe(n)){const s=this._cachedMeta;this._data=EN(n,s)}else if(r!==n){if(r){Rp(r,this);const s=this._cachedMeta;bo(s),s._parsed=[]}n&&Object.isExtensible(n)&&DM(n,this),this._syncList=[],this._data=n}}addElements(){const e=this._cachedMeta;this._dataCheck(),this.datasetElementType&&(e.dataset=new this.datasetElementType)}buildOrUpdateElements(e){const n=this._cachedMeta,r=this.getDataset();let s=!1;this._dataCheck();const o=n._stacked;n._stacked=od(n.vScale,n),n.stack!==r.stack&&(s=!0,bo(n),n.stack=r.stack),this._resyncElements(e),(s||o!==n._stacked)&&(A0(this,n._parsed),n._stacked=od(n.vScale,n))}configure(){const e=this.chart.config,n=e.datasetScopeKeys(this._type),r=e.getOptionScopes(this.getDataset(),n,!0);this.options=e.createResolver(r,this.getContext()),this._parsing=this.options.parsing,this._cachedDataOpts={}}parse(e,n){const{_cachedMeta:r,_data:s}=this,{iScale:o,_stacked:i}=r,a=o.axis;let l=e===0&&n===s.length?!0:r._sorted,c=e>0&&r._parsed[e-1],d,u,f;if(this._parsing===!1)r._parsed=s,r._sorted=!0,f=s;else{Et(s[e])?f=this.parseArrayData(r,s,e,n):Qe(s[e])?f=this.parseObjectData(r,s,e,n):f=this.parsePrimitiveData(r,s,e,n);const g=()=>u[a]===null||c&&u[a]p||u=0;--f)if(!m()){this.updateRangeFromParsed(c,e,g,l);break}}return c}getAllParsedValues(e){const n=this._cachedMeta._parsed,r=[];let s,o,i;for(s=0,o=n.length;s=0&&ethis.getContext(r,s,n),p=c.resolveNamedOptions(f,g,m,u);return p.$shared&&(p.$shared=l,o[i]=Object.freeze(D0(p,l))),p}_resolveAnimations(e,n,r){const s=this.chart,o=this._cachedDataOpts,i=`animation-${n}`,a=o[i];if(a)return a;let l;if(s.options.animation!==!1){const d=this.chart.config,u=d.datasetAnimationScopeKeys(this._type,n),f=d.getOptionScopes(this.getDataset(),u);l=d.createResolver(f,this.getContext(e,r,n))}const c=new E0(s,l&&l.animations);return l&&l._cacheable&&(o[i]=Object.freeze(c)),c}getSharedOptions(e){if(e.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},e))}includeOptions(e,n){return!n||ad(e)||this.chart._animationsDisabled}_getSharedOptions(e,n){const r=this.resolveDataElementOptions(e,n),s=this._sharedOptions,o=this.getSharedOptions(r),i=this.includeOptions(n,o)||o!==s;return this.updateSharedOptions(o,n,r),{sharedOptions:o,includeOptions:i}}updateElement(e,n,r,s){ad(s)?Object.assign(e,r):this._resolveAnimations(n,s).update(e,r)}updateSharedOptions(e,n,r){e&&!ad(n)&&this._resolveAnimations(void 0,n).update(e,r)}_setStyle(e,n,r,s){e.active=s;const o=this.getStyle(n,s);this._resolveAnimations(n,r,s).update(e,{options:!s&&this.getSharedOptions(o)||o})}removeHoverStyle(e,n,r){this._setStyle(e,r,"active",!1)}setHoverStyle(e,n,r){this._setStyle(e,r,"active",!0)}_removeDatasetHoverStyle(){const e=this._cachedMeta.dataset;e&&this._setStyle(e,void 0,"active",!1)}_setDatasetHoverStyle(){const e=this._cachedMeta.dataset;e&&this._setStyle(e,void 0,"active",!0)}_resyncElements(e){const n=this._data,r=this._cachedMeta.data;for(const[a,l,c]of this._syncList)this[a](l,c);this._syncList=[];const s=r.length,o=n.length,i=Math.min(o,s);i&&this.parse(0,i),o>s?this._insertElements(s,o-s,e):o{for(c.length+=n,a=c.length-1;a>=i;a--)c[a]=c[a-n]};for(l(o),a=e;as-o))}return t._cache.$bar}function LN(t){const e=t.iScale,n=IN(e,t.type);let r=e._length,s,o,i,a;const l=()=>{i===32767||i===-32768||(ho(a)&&(r=Math.min(r,Math.abs(i-a)||r)),a=i)};for(s=0,o=n.length;s0?s[t-1]:null,a=tMath.abs(a)&&(l=a,c=i),e[n.axis]=c,e._custom={barStart:l,barEnd:c,start:s,end:o,min:i,max:a}}function I0(t,e,n,r){return Et(t)?FN(t,e,n,r):e[n.axis]=n.parse(t,r),e}function L0(t,e,n,r){const s=t.iScale,o=t.vScale,i=s.getLabels(),a=s===o,l=[];let c,d,u,f;for(c=n,d=n+r;c=n?1:-1)}function VN(t){let e,n,r,s,o;return t.horizontal?(e=t.base>t.x,n="left",r="right"):(e=t.based.controller.options.grouped),o=r.options.stacked,i=[],a=this._cachedMeta.controller.getParsed(n),l=a&&a[r.axis],c=d=>{const u=d._parsed.find(g=>g[r.axis]===l),f=u&&u[d.vScale.axis];if(lt(f)||isNaN(f))return!0};for(const d of s)if(!(n!==void 0&&c(d))&&((o===!1||i.indexOf(d.stack)===-1||o===void 0&&d.stack===void 0)&&i.push(d.stack),d.index===e))break;return i.length||i.push(void 0),i}_getStackCount(e){return this._getStacks(void 0,e).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const e=this.chart.scales,n=this.chart.options.indexAxis;return Object.keys(e).filter(r=>e[r].axis===n).shift()}_getAxis(){const e={},n=this.getFirstScaleIdForIndexAxis();for(const r of this.chart.data.datasets)e[Je(this.chart.options.indexAxis==="x"?r.xAxisID:r.yAxisID,n)]=!0;return Object.keys(e)}_getStackIndex(e,n,r){const s=this._getStacks(e,r),o=n!==void 0?s.indexOf(n):-1;return o===-1?s.length-1:o}_getRuler(){const e=this.options,n=this._cachedMeta,r=n.iScale,s=[];let o,i;for(o=0,i=n.data.length;ooa(x,a,l,!0)?1:Math.max(E,E*n,C,C*n),m=(x,E,C)=>oa(x,a,l,!0)?-1:Math.min(E,E*n,C,C*n),p=g(0,c,u),y=g(Mt,d,f),b=m(ct,c,u),_=m(ct+Mt,d,f);r=(p-b)/2,s=(y-_)/2,o=-(p+b)/2,i=-(y+_)/2}return{ratioX:r,ratioY:s,offsetX:o,offsetY:i}}class yo extends hs{constructor(e,n){super(e,n),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(e,n){const r=this.getDataset().data,s=this._cachedMeta;if(this._parsing===!1)s._parsed=r;else{let o=l=>+r[l];if(Qe(r[e])){const{key:l="value"}=this._parsing;o=c=>+Sr(r[c],l)}let i,a;for(i=e,a=e+n;i0&&!isNaN(e)?St*(Math.abs(e)/n):0}getLabelAndValue(e){const n=this._cachedMeta,r=this.chart,s=r.data.labels||[],o=Kc(n._parsed[e],r.options.locale);return{label:s[e]||"",value:o}}getMaxBorderWidth(e){let n=0;const r=this.chart;let s,o,i,a,l;if(!e){for(s=0,o=r.data.datasets.length;se!=="spacing",_indexable:e=>e!=="spacing"&&!e.startsWith("borderDash")&&!e.startsWith("hoverBorderDash")}),$e(yo,"overrides",{aspectRatio:1,plugins:{legend:{labels:{generateLabels(e){const n=e.data,{labels:{pointStyle:r,textAlign:s,color:o,useBorderRadius:i,borderRadius:a}}=e.legend.options;return n.labels.length&&n.datasets.length?n.labels.map((l,c)=>{const u=e.getDatasetMeta(0).controller.getStyle(c);return{text:l,fillStyle:u.backgroundColor,fontColor:o,hidden:!e.getDataVisibility(c),lineDash:u.borderDash,lineDashOffset:u.borderDashOffset,lineJoin:u.borderJoinStyle,lineWidth:u.borderWidth,strokeStyle:u.borderColor,textAlign:s,pointStyle:r,borderRadius:i&&(a||u.borderRadius),index:c}}):[]}},onClick(e,n,r){r.chart.toggleDataVisibility(n.index),r.chart.update()}}}});function Ar(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class cd{constructor(e){$e(this,"options");this.options=e||{}}static override(e){Object.assign(cd.prototype,e)}init(){}formats(){return Ar()}parse(){return Ar()}format(){return Ar()}add(){return Ar()}diff(){return Ar()}startOf(){return Ar()}endOf(){return Ar()}}var WN={_date:cd};function GN(t,e,n,r){const{controller:s,data:o,_sorted:i}=t,a=s._cachedMeta.iScale,l=t.dataset&&t.dataset.options?t.dataset.options.spanGaps:null;if(a&&e===a.axis&&e!=="r"&&i&&o.length){const c=a._reversePixels?TM:Gc;if(r){if(s._sharedOptions){const d=o[0],u=typeof d.getRange=="function"&&d.getRange(e);if(u){const f=c(o,e,n-u),g=c(o,e,n+u);return{lo:f.lo,hi:g.hi}}}}else{const d=c(o,e,n);if(l){const{vScale:u}=s._cachedMeta,{_parsed:f}=t,g=f.slice(0,d.lo+1).reverse().findIndex(p=>!lt(p[u.axis]));d.lo-=Math.max(0,g);const m=f.slice(d.hi).findIndex(p=>!lt(p[u.axis]));d.hi+=Math.max(0,m)}return d}}return{lo:0,hi:o.length-1}}function ha(t,e,n,r,s){const o=t.getSortedVisibleDatasetMetas(),i=n[e];for(let a=0,l=o.length;a{l[i]&&l[i](e[n],s)&&(o.push({element:l,datasetIndex:c,index:d}),a=a||l.inRange(e.x,e.y,s))}),r&&!a?[]:o}var qN={modes:{index(t,e,n,r){const s=Tr(e,t),o=n.axis||"x",i=n.includeInvisible||!1,a=n.intersect?dd(t,s,o,r,i):ud(t,s,o,!1,r,i),l=[];return a.length?(t.getSortedVisibleDatasetMetas().forEach(c=>{const d=a[0].index,u=c.data[d];u&&!u.skip&&l.push({element:u,datasetIndex:c.index,index:d})}),l):[]},dataset(t,e,n,r){const s=Tr(e,t),o=n.axis||"xy",i=n.includeInvisible||!1;let a=n.intersect?dd(t,s,o,r,i):ud(t,s,o,!1,r,i);if(a.length>0){const l=a[0].datasetIndex,c=t.getDatasetMeta(l).data;a=[];for(let d=0;dn.pos===e)}function V0(t,e){return t.filter(n=>z0.indexOf(n.pos)===-1&&n.box.axis===e)}function xo(t,e){return t.sort((n,r)=>{const s=e?r:n,o=e?n:r;return s.weight===o.weight?s.index-o.index:s.weight-o.weight})}function ZN(t){const e=[];let n,r,s,o,i,a;for(n=0,r=(t||[]).length;nc.box.fullSize),!0),r=xo(vo(e,"left"),!0),s=xo(vo(e,"right")),o=xo(vo(e,"top"),!0),i=xo(vo(e,"bottom")),a=V0(e,"x"),l=V0(e,"y");return{fullSize:n,leftAndTop:r.concat(o),rightAndBottom:s.concat(l).concat(i).concat(a),chartArea:vo(e,"chartArea"),vertical:r.concat(s).concat(l),horizontal:o.concat(i).concat(a)}}function H0(t,e,n,r){return Math.max(t[n],e[n])+Math.max(t[r],e[r])}function U0(t,e){t.top=Math.max(t.top,e.top),t.left=Math.max(t.left,e.left),t.bottom=Math.max(t.bottom,e.bottom),t.right=Math.max(t.right,e.right)}function eO(t,e,n,r){const{pos:s,box:o}=n,i=t.maxPadding;if(!Qe(s)){n.size&&(t[s]-=n.size);const u=r[n.stack]||{size:0,count:1};u.size=Math.max(u.size,n.horizontal?o.height:o.width),n.size=u.size/u.count,t[s]+=n.size}o.getPadding&&U0(i,o.getPadding());const a=Math.max(0,e.outerWidth-H0(i,t,"left","right")),l=Math.max(0,e.outerHeight-H0(i,t,"top","bottom")),c=a!==t.w,d=l!==t.h;return t.w=a,t.h=l,n.horizontal?{same:c,other:d}:{same:d,other:c}}function tO(t){const e=t.maxPadding;function n(r){const s=Math.max(e[r]-t[r],0);return t[r]+=s,s}t.y+=n("top"),t.x+=n("left"),n("right"),n("bottom")}function nO(t,e){const n=e.maxPadding;function r(s){const o={left:0,top:0,right:0,bottom:0};return s.forEach(i=>{o[i]=Math.max(e[i],n[i])}),o}return r(t?["left","right"]:["top","bottom"])}function wo(t,e,n,r){const s=[];let o,i,a,l,c,d;for(o=0,i=t.length,c=0;o{typeof p.beforeLayout=="function"&&p.beforeLayout()});const d=l.reduce((p,y)=>y.box.options&&y.box.options.display===!1?p:p+1,0)||1,u=Object.freeze({outerWidth:e,outerHeight:n,padding:s,availableWidth:o,availableHeight:i,vBoxMaxWidth:o/2/d,hBoxMaxHeight:i/2}),f=Object.assign({},s);U0(f,pn(r));const g=Object.assign({maxPadding:f,w:o,h:i,x:s.left,y:s.top},s),m=JN(l.concat(c),u);wo(a.fullSize,g,u,m),wo(l,g,u,m),wo(c,g,u,m)&&wo(l,g,u,m),tO(g),B0(a.leftAndTop,g,u,m),g.x+=g.w,g.y+=g.h,B0(a.rightAndBottom,g,u,m),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},at(a.chartArea,p=>{const y=p.box;Object.assign(y,t.chartArea),y.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})})}};class j0{acquireContext(e,n){}releaseContext(e){return!1}addEventListener(e,n,r){}removeEventListener(e,n,r){}getDevicePixelRatio(){return 1}getMaximumSize(e,n,r,s){return n=Math.max(0,n||e.width),r=r||e.height,{width:n,height:Math.max(0,s?Math.floor(n/s):r)}}isAttached(e){return!0}updateConfig(e){}}class rO extends j0{acquireContext(e){return e&&e.getContext&&e.getContext("2d")||null}updateConfig(e){e.options.animation=!1}}const pa="$chartjs",sO={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},W0=t=>t===null||t==="";function oO(t,e){const n=t.style,r=t.getAttribute("height"),s=t.getAttribute("width");if(t[pa]={initial:{height:r,width:s,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",n.boxSizing=n.boxSizing||"border-box",W0(s)){const o=k0(t,"width");o!==void 0&&(t.width=o)}if(W0(r))if(t.style.height==="")t.height=t.width/(e||2);else{const o=k0(t,"height");o!==void 0&&(t.height=o)}return t}const G0=gN?{passive:!0}:!1;function iO(t,e,n){t&&t.addEventListener(e,n,G0)}function aO(t,e,n){t&&t.canvas&&t.canvas.removeEventListener(e,n,G0)}function lO(t,e){const n=sO[t.type]||t.type,{x:r,y:s}=Tr(t,e);return{type:n,chart:e,native:t,x:r!==void 0?r:null,y:s!==void 0?s:null}}function ma(t,e){for(const n of t)if(n===e||n.contains(e))return!0}function cO(t,e,n){const r=t.canvas,s=new MutationObserver(o=>{let i=!1;for(const a of o)i=i||ma(a.addedNodes,r),i=i&&!ma(a.removedNodes,r);i&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}function dO(t,e,n){const r=t.canvas,s=new MutationObserver(o=>{let i=!1;for(const a of o)i=i||ma(a.removedNodes,r),i=i&&!ma(a.addedNodes,r);i&&n()});return s.observe(document,{childList:!0,subtree:!0}),s}const ko=new Map;let Y0=0;function X0(){const t=window.devicePixelRatio;t!==Y0&&(Y0=t,ko.forEach((e,n)=>{n.currentDevicePixelRatio!==t&&e()}))}function uO(t,e){ko.size||window.addEventListener("resize",X0),ko.set(t,e)}function fO(t){ko.delete(t),ko.size||window.removeEventListener("resize",X0)}function hO(t,e,n){const r=t.canvas,s=r&&sd(r);if(!s)return;const o=n0((a,l)=>{const c=s.clientWidth;n(a,l),c{const l=a[0],c=l.contentRect.width,d=l.contentRect.height;c===0&&d===0||o(c,d)});return i.observe(s),uO(t,o),i}function fd(t,e,n){n&&n.disconnect(),e==="resize"&&fO(t)}function gO(t,e,n){const r=t.canvas,s=n0(o=>{t.ctx!==null&&n(lO(o,t))},t);return iO(r,e,s),s}class pO extends j0{acquireContext(e,n){const r=e&&e.getContext&&e.getContext("2d");return r&&r.canvas===e?(oO(e,n),r):null}releaseContext(e){const n=e.canvas;if(!n[pa])return!1;const r=n[pa].initial;["height","width"].forEach(o=>{const i=r[o];lt(i)?n.removeAttribute(o):n.setAttribute(o,i)});const s=r.style||{};return Object.keys(s).forEach(o=>{n.style[o]=s[o]}),n.width=n.width,delete n[pa],!0}addEventListener(e,n,r){this.removeEventListener(e,n);const s=e.$proxies||(e.$proxies={}),i={attach:cO,detach:dO,resize:hO}[n]||gO;s[n]=i(e,n,r)}removeEventListener(e,n){const r=e.$proxies||(e.$proxies={}),s=r[n];if(!s)return;({attach:fd,detach:fd,resize:fd}[n]||aO)(e,n,s),r[n]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(e,n,r,s){return hN(e,n,r,s)}isAttached(e){const n=e&&sd(e);return!!(n&&n.isConnected)}}function mO(t){return!rd()||typeof OffscreenCanvas<"u"&&t instanceof OffscreenCanvas?rO:pO}let Dr=(Aa=class{constructor(){$e(this,"x");$e(this,"y");$e(this,"active",!1);$e(this,"options");$e(this,"$animations")}tooltipPosition(e){const{x:n,y:r}=this.getProps(["x","y"],e);return{x:n,y:r}}hasValue(){return sa(this.x)&&sa(this.y)}getProps(e,n){const r=this.$animations;if(!n||!r)return this;const s={};return e.forEach(o=>{s[o]=r[o]&&r[o].active()?r[o]._to:this[o]}),s}},$e(Aa,"defaults",{}),$e(Aa,"defaultRoutes"),Aa);function bO(t,e){const n=t.options.ticks,r=yO(t),s=Math.min(n.maxTicksLimit||r,r),o=n.major.enabled?xO(e):[],i=o.length,a=o[0],l=o[i-1],c=[];if(i>s)return wO(e,c,o,i/s),c;const d=vO(o,e,s);if(i>0){let u,f;const g=i>1?Math.round((l-a)/(i-1)):null;for(ba(e,c,d,lt(g)?0:a-g,a),u=0,f=i-1;us)return l}return Math.max(s,1)}function xO(t){const e=[];let n,r;for(n=0,r=t.length;nt==="left"?"right":t==="right"?"left":t,K0=(t,e,n)=>e==="top"||e==="left"?t[e]+n:t[e]-n,q0=(t,e)=>Math.min(e||t,t);function Z0(t,e){const n=[],r=t.length/e,s=t.length;let o=0;for(;oi+a)))return l}function _O(t,e){at(t,n=>{const r=n.gc,s=r.length/2;let o;if(s>e){for(o=0;or?r:n,r=s&&n>r?n:r,{min:Fn(n,Fn(r,n)),max:Fn(r,Fn(n,r))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const e=this.chart.data;return this.options.labels||(this.isHorizontal()?e.xLabels:e.yLabels)||e.labels||[]}getLabelItems(e=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(e))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){gt(this.options.beforeUpdate,[this])}update(e,n,r){const{beginAtZero:s,grace:o,ticks:i}=this.options,a=i.sampleSize;this.beforeUpdate(),this.maxWidth=e,this.maxHeight=n,this._margins=r=Object.assign({left:0,right:0,top:0,bottom:0},r),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+r.left+r.right:this.height+r.top+r.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=JM(this,o,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const l=a=o||r<=1||!this.isHorizontal()){this.labelRotation=s;return}const d=this._getLabelSizes(),u=d.widest.width,f=d.highest.height,g=en(this.chart.width-u,0,this.maxWidth);a=e.offset?this.maxWidth/r:g/(r-1),u+6>a&&(a=g/(r-(e.offset?.5:1)),l=this.maxHeight-Co(e.grid)-n.padding-Q0(e.title,this.chart.options.font),c=Math.sqrt(u*u+f*f),i=MM(Math.min(Math.asin(en((d.highest.height+6)/a,-1,1)),Math.asin(en(l/c,-1,1))-Math.asin(en(f/c,-1,1)))),i=Math.max(s,Math.min(o,i))),this.labelRotation=i}afterCalculateLabelRotation(){gt(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){gt(this.options.beforeFit,[this])}fit(){const e={width:0,height:0},{chart:n,options:{ticks:r,title:s,grid:o}}=this,i=this._isVisible(),a=this.isHorizontal();if(i){const l=Q0(s,n.options.font);if(a?(e.width=this.maxWidth,e.height=Co(o)+l):(e.height=this.maxHeight,e.width=Co(o)+l),r.display&&this.ticks.length){const{first:c,last:d,widest:u,highest:f}=this._getLabelSizes(),g=r.padding*2,m=Xn(this.labelRotation),p=Math.cos(m),y=Math.sin(m);if(a){const b=r.mirror?0:y*u.width+p*f.height;e.height=Math.min(this.maxHeight,e.height+b+g)}else{const b=r.mirror?0:p*u.width+y*f.height;e.width=Math.min(this.maxWidth,e.width+b+g)}this._calculatePadding(c,d,y,p)}}this._handleMargins(),a?(this.width=this._length=n.width-this._margins.left-this._margins.right,this.height=e.height):(this.width=e.width,this.height=this._length=n.height-this._margins.top-this._margins.bottom)}_calculatePadding(e,n,r,s){const{ticks:{align:o,padding:i},position:a}=this.options,l=this.labelRotation!==0,c=a!=="top"&&this.axis==="x";if(this.isHorizontal()){const d=this.getPixelForTick(0)-this.left,u=this.right-this.getPixelForTick(this.ticks.length-1);let f=0,g=0;l?c?(f=s*e.width,g=r*n.height):(f=r*e.height,g=s*n.width):o==="start"?g=n.width:o==="end"?f=e.width:o!=="inner"&&(f=e.width/2,g=n.width/2),this.paddingLeft=Math.max((f-d+i)*this.width/(this.width-d),0),this.paddingRight=Math.max((g-u+i)*this.width/(this.width-u),0)}else{let d=n.height/2,u=e.height/2;o==="start"?(d=0,u=e.height):o==="end"&&(d=n.height,u=0),this.paddingTop=d+i,this.paddingBottom=u+i}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){gt(this.options.afterFit,[this])}isHorizontal(){const{axis:e,position:n}=this.options;return n==="top"||n==="bottom"||e==="x"}isFullSize(){return this.options.fullSize}_convertTicksToLabels(e){this.beforeTickToLabelConversion(),this.generateTickLabels(e);let n,r;for(n=0,r=e.length;n({width:i[w]||0,height:a[w]||0});return{first:S(0),last:S(n-1),widest:S(k),highest:S(N),widths:i,heights:a}}getLabelForValue(e){return e}getPixelForValue(e,n){return NaN}getValueForPixel(e){}getPixelForTick(e){const n=this.ticks;return e<0||e>n.length-1?null:this.getPixelForValue(n[e].value)}getPixelForDecimal(e){this._reversePixels&&(e=1-e);const n=this._startPixel+e*this._length;return OM(this._alignToPixels?Nr(this.chart,n,0):n)}getDecimalForPixel(e){const n=(e-this._startPixel)/this._length;return this._reversePixels?1-n:n}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:e,max:n}=this;return e<0&&n<0?n:e>0&&n>0?e:0}getContext(e){const n=this.ticks||[];if(e>=0&&ea*s?a/r:l/s:l*s0}_computeGridLineItems(e){const n=this.axis,r=this.chart,s=this.options,{grid:o,position:i,border:a}=s,l=o.offset,c=this.isHorizontal(),u=this.ticks.length+(l?1:0),f=Co(o),g=[],m=a.setContext(this.getContext()),p=m.display?m.width:0,y=p/2,b=function(D){return Nr(r,D,p)};let _,x,E,C,k,N,S,w,M,T,P,L;if(i==="top")_=b(this.bottom),N=this.bottom-f,w=_-y,T=b(e.top)+y,L=e.bottom;else if(i==="bottom")_=b(this.top),T=e.top,L=b(e.bottom)-y,N=_+y,w=this.top+f;else if(i==="left")_=b(this.right),k=this.right-f,S=_-y,M=b(e.left)+y,P=e.right;else if(i==="right")_=b(this.left),M=e.left,P=b(e.right)-y,k=_+y,S=this.left+f;else if(n==="x"){if(i==="center")_=b((e.top+e.bottom)/2+.5);else if(Qe(i)){const D=Object.keys(i)[0],O=i[D];_=b(this.chart.scales[D].getPixelForValue(O))}T=e.top,L=e.bottom,N=_+y,w=N+f}else if(n==="y"){if(i==="center")_=b((e.left+e.right)/2);else if(Qe(i)){const D=Object.keys(i)[0],O=i[D];_=b(this.chart.scales[D].getPixelForValue(O))}k=_-y,S=k-f,M=e.left,P=e.right}const $=Je(s.ticks.maxTicksLimit,u),A=Math.max(1,Math.ceil(u/$));for(x=0;x0&&(K-=G/2);break}W={left:K,top:V,width:G+U.width,height:B+U.height,color:A.backdropColor}}y.push({label:E,font:w,textOffset:P,options:{rotation:p,color:O,strokeColor:z,strokeWidth:H,textAlign:j,textBaseline:L,translation:[C,k],backdrop:W}})}return y}_getXAxisLabelAlignment(){const{position:e,ticks:n}=this.options;if(-Xn(this.labelRotation))return e==="top"?"left":"right";let s="center";return n.align==="start"?s="left":n.align==="end"?s="right":n.align==="inner"&&(s="inner"),s}_getYAxisLabelAlignment(e){const{position:n,ticks:{crossAlign:r,mirror:s,padding:o}}=this.options,i=this._getLabelSizes(),a=e+o,l=i.widest.width;let c,d;return n==="left"?s?(d=this.right+o,r==="near"?c="left":r==="center"?(c="center",d+=l/2):(c="right",d+=l)):(d=this.right-a,r==="near"?c="right":r==="center"?(c="center",d-=l/2):(c="left",d=this.left)):n==="right"?s?(d=this.left+o,r==="near"?c="right":r==="center"?(c="center",d-=l/2):(c="left",d-=l)):(d=this.left+a,r==="near"?c="left":r==="center"?(c="center",d+=l/2):(c="right",d=this.right)):c="right",{textAlign:c,x:d}}_computeLabelArea(){if(this.options.ticks.mirror)return;const e=this.chart,n=this.options.position;if(n==="left"||n==="right")return{top:0,left:this.left,bottom:e.height,right:this.right};if(n==="top"||n==="bottom")return{top:this.top,left:0,bottom:this.bottom,right:e.width}}drawBackground(){const{ctx:e,options:{backgroundColor:n},left:r,top:s,width:o,height:i}=this;n&&(e.save(),e.fillStyle=n,e.fillRect(r,s,o,i),e.restore())}getLineWidthForValue(e){const n=this.options.grid;if(!this._isVisible()||!n.display)return 0;const s=this.ticks.findIndex(o=>o.value===e);return s>=0?n.setContext(this.getContext(s)).lineWidth:0}drawGrid(e){const n=this.options.grid,r=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(e));let o,i;const a=(l,c,d)=>{!d.width||!d.color||(r.save(),r.lineWidth=d.width,r.strokeStyle=d.color,r.setLineDash(d.borderDash||[]),r.lineDashOffset=d.borderDashOffset,r.beginPath(),r.moveTo(l.x,l.y),r.lineTo(c.x,c.y),r.stroke(),r.restore())};if(n.display)for(o=0,i=s.length;o{this.draw(o)}}]:[{z:r,draw:o=>{this.drawBackground(),this.drawGrid(o),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:n,draw:o=>{this.drawLabels(o)}}]}getMatchingVisibleMetas(e){const n=this.chart.getSortedVisibleDatasetMetas(),r=this.axis+"AxisID",s=[];let o,i;for(o=0,i=n.length;o{const r=n.split("."),s=r.pop(),o=[t].concat(r).join("."),i=e[n].split("."),a=i.pop(),l=i.join(".");xt.route(o,s,l,a)})}function DO(t){return"id"in t&&"defaults"in t}class IO{constructor(){this.controllers=new ya(hs,"datasets",!0),this.elements=new ya(Dr,"elements"),this.plugins=new ya(Object,"plugins"),this.scales=new ya(gs,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...e){this._each("register",e)}remove(...e){this._each("unregister",e)}addControllers(...e){this._each("register",e,this.controllers)}addElements(...e){this._each("register",e,this.elements)}addPlugins(...e){this._each("register",e,this.plugins)}addScales(...e){this._each("register",e,this.scales)}getController(e){return this._get(e,this.controllers,"controller")}getElement(e){return this._get(e,this.elements,"element")}getPlugin(e){return this._get(e,this.plugins,"plugin")}getScale(e){return this._get(e,this.scales,"scale")}removeControllers(...e){this._each("unregister",e,this.controllers)}removeElements(...e){this._each("unregister",e,this.elements)}removePlugins(...e){this._each("unregister",e,this.plugins)}removeScales(...e){this._each("unregister",e,this.scales)}_each(e,n,r){[...n].forEach(s=>{const o=r||this._getRegistryForType(s);r||o.isForType(s)||o===this.plugins&&s.id?this._exec(e,o,s):at(s,i=>{const a=r||this._getRegistryForType(i);this._exec(e,a,i)})})}_exec(e,n,r){const s=jc(e);gt(r["before"+s],[],r),n[e](r),gt(r["after"+s],[],r)}_getRegistryForType(e){for(let n=0;no.filter(a=>!i.some(l=>a.plugin.id===l.plugin.id));this._notify(s(n,r),e,"stop"),this._notify(s(r,n),e,"start")}}function PO(t){const e={},n=[],r=Object.keys(Vn.plugins.items);for(let o=0;o1&&J0(t[0].toLowerCase());if(r)return r}throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function R0(t,e,n){if(n[e+"AxisID"]===t)return{axis:e}}function BO(t,e){if(e.data&&e.data.datasets){const n=e.data.datasets.filter(r=>r.xAxisID===t||r.yAxisID===t);if(n.length)return R0(t,"x",n[0])||R0(t,"y",n[0])}return{}}function jO(t,e){const n=Mr[t.type]||{scales:{}},r=e.scales||{},s=hd(t.type,e),o=Object.create(null);return Object.keys(r).forEach(i=>{const a=r[i];if(!Qe(a))return console.error(`Invalid scale configuration for scale: ${i}`);if(a._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${i}`);const l=gd(i,a,BO(i,t),xt.scales[a.type]),c=HO(l,s),d=n.scales||{};o[i]=fo(Object.create(null),[{axis:l},a,d[l],d[c]])}),t.data.datasets.forEach(i=>{const a=i.type||t.type,l=i.indexAxis||hd(a,e),d=(Mr[a]||{}).scales||{};Object.keys(d).forEach(u=>{const f=VO(u,l),g=i[f+"AxisID"]||f;o[g]=o[g]||Object.create(null),fo(o[g],[{axis:f},r[g],d[u]])})}),Object.keys(o).forEach(i=>{const a=o[i];fo(a,[xt.scales[a.type],xt.scale])}),o}function em(t){const e=t.options||(t.options={});e.plugins=Je(e.plugins,{}),e.scales=jO(t,e)}function tm(t){return t=t||{},t.datasets=t.datasets||[],t.labels=t.labels||[],t}function WO(t){return t=t||{},t.data=tm(t.data),em(t),t}const nm=new Map,rm=new Set;function va(t,e){let n=nm.get(t);return n||(n=e(),nm.set(t,n),rm.add(n)),n}const So=(t,e,n)=>{const r=Sr(e,n);r!==void 0&&t.add(r)};class GO{constructor(e){this._config=WO(e),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(e){this._config.type=e}get data(){return this._config.data}set data(e){this._config.data=tm(e)}get options(){return this._config.options}set options(e){this._config.options=e}get plugins(){return this._config.plugins}update(){const e=this._config;this.clearCache(),em(e)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(e){return va(e,()=>[[`datasets.${e}`,""]])}datasetAnimationScopeKeys(e,n){return va(`${e}.transition.${n}`,()=>[[`datasets.${e}.transitions.${n}`,`transitions.${n}`],[`datasets.${e}`,""]])}datasetElementScopeKeys(e,n){return va(`${e}-${n}`,()=>[[`datasets.${e}.elements.${n}`,`datasets.${e}`,`elements.${n}`,""]])}pluginScopeKeys(e){const n=e.id,r=this.type;return va(`${r}-plugin-${n}`,()=>[[`plugins.${n}`,...e.additionalOptionScopes||[]]])}_cachedScopes(e,n){const r=this._scopeCache;let s=r.get(e);return(!s||n)&&(s=new Map,r.set(e,s)),s}getOptionScopes(e,n,r){const{options:s,type:o}=this,i=this._cachedScopes(e,r),a=i.get(n);if(a)return a;const l=new Set;n.forEach(d=>{e&&(l.add(e),d.forEach(u=>So(l,e,u))),d.forEach(u=>So(l,s,u)),d.forEach(u=>So(l,Mr[o]||{},u)),d.forEach(u=>So(l,xt,u)),d.forEach(u=>So(l,qc,u))});const c=Array.from(l);return c.length===0&&c.push(Object.create(null)),rm.has(n)&&i.set(n,c),c}chartOptionScopes(){const{options:e,type:n}=this;return[e,Mr[n]||{},xt.datasets[n]||{},{type:n},xt,qc]}resolveNamedOptions(e,n,r,s=[""]){const o={$shared:!0},{resolver:i,subPrefixes:a}=sm(this._resolverCache,e,s);let l=i;if(XO(i,n)){o.$shared=!1,r=lr(r)?r():r;const c=this.createResolver(e,r,a);l=us(i,r,c)}for(const c of n)o[c]=l[c];return o}createResolver(e,n,r=[""],s){const{resolver:o}=sm(this._resolverCache,e,r);return Qe(n)?us(o,n,void 0,s):o}}function sm(t,e,n){let r=t.get(e);r||(r=new Map,t.set(e,r));const s=n.join();let o=r.get(s);return o||(o={resolver:ed(e,n),subPrefixes:n.filter(a=>!a.toLowerCase().includes("hover"))},r.set(s,o)),o}const YO=t=>Qe(t)&&Object.getOwnPropertyNames(t).some(e=>lr(t[e]));function XO(t,e){const{isScriptable:n,isIndexable:r}=p0(t);for(const s of e){const o=n(s),i=r(s),a=(i||o)&&t[s];if(o&&(lr(a)||YO(a))||i&&Et(a))return!0}return!1}var KO="4.5.1";const qO=["top","bottom","left","right","chartArea"];function om(t,e){return t==="top"||t==="bottom"||qO.indexOf(t)===-1&&e==="x"}function im(t,e){return function(n,r){return n[t]===r[t]?n[e]-r[e]:n[t]-r[t]}}function am(t){const e=t.chart,n=e.options.animation;e.notifyPlugins("afterRender"),gt(n&&n.onComplete,[t],e)}function ZO(t){const e=t.chart,n=e.options.animation;gt(n&&n.onProgress,[t],e)}function lm(t){return rd()&&typeof t=="string"?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const xa={},cm=t=>{const e=lm(t);return Object.values(xa).filter(n=>n.canvas===e).pop()};function QO(t,e,n){const r=Object.keys(t);for(const s of r){const o=+s;if(o>=e){const i=t[s];delete t[s],(n>0||o>e)&&(t[o+n]=i)}}}function JO(t,e,n,r){return!n||t.type==="mouseout"?null:r?e:t}let _o=(fr=class{static register(...e){Vn.add(...e),dm()}static unregister(...e){Vn.remove(...e),dm()}constructor(e,n){const r=this.config=new GO(n),s=lm(e),o=cm(s);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const i=r.createResolver(r.chartOptionScopes(),this.getContext());this.platform=new(r.platform||mO(s)),this.platform.updateConfig(r);const a=this.platform.acquireContext(s,i.aspectRatio),l=a&&a.canvas,c=l&&l.height,d=l&&l.width;if(this.id=pM(),this.ctx=a,this.canvas=l,this.width=d,this.height=c,this._options=i,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new LO,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=IM(u=>this.update(u),i.resizeDelay||0),this._dataChanges=[],xa[this.id]=this,!a||!l){console.error("Failed to create chart: can't acquire context from the given item");return}Kn.listen(this,"complete",am),Kn.listen(this,"progress",ZO),this._initialize(),this.attached&&this.update()}get aspectRatio(){const{options:{aspectRatio:e,maintainAspectRatio:n},width:r,height:s,_aspectRatio:o}=this;return lt(e)?n&&o?o:s?r/s:null:e}get data(){return this.config.data}set data(e){this.config.data=e}get options(){return this._options}set options(e){this.config.options=e}get registry(){return Vn}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():w0(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return d0(this.canvas,this.ctx),this}stop(){return Kn.stop(this),this}resize(e,n){Kn.running(this)?this._resizeBeforeDraw={width:e,height:n}:this._resize(e,n)}_resize(e,n){const r=this.options,s=this.canvas,o=r.maintainAspectRatio&&this.aspectRatio,i=this.platform.getMaximumSize(s,e,n,o),a=r.devicePixelRatio||this.platform.getDevicePixelRatio(),l=this.width?"resize":"attach";this.width=i.width,this.height=i.height,this._aspectRatio=this.aspectRatio,w0(this,a,!0)&&(this.notifyPlugins("resize",{size:i}),gt(r.onResize,[this,i],this),this.attached&&this._doResize(l)&&this.render())}ensureScalesHaveIDs(){const n=this.options.scales||{};at(n,(r,s)=>{r.id=s})}buildOrUpdateScales(){const e=this.options,n=e.scales,r=this.scales,s=Object.keys(r).reduce((i,a)=>(i[a]=!1,i),{});let o=[];n&&(o=o.concat(Object.keys(n).map(i=>{const a=n[i],l=gd(i,a),c=l==="r",d=l==="x";return{options:a,dposition:c?"chartArea":d?"bottom":"left",dtype:c?"radialLinear":d?"category":"linear"}}))),at(o,i=>{const a=i.options,l=a.id,c=gd(l,a),d=Je(a.type,i.dtype);(a.position===void 0||om(a.position,c)!==om(i.dposition))&&(a.position=i.dposition),s[l]=!0;let u=null;if(l in r&&r[l].type===d)u=r[l];else{const f=Vn.getScale(d);u=new f({id:l,type:d,ctx:this.ctx,chart:this}),r[u.id]=u}u.init(a,e)}),at(s,(i,a)=>{i||delete r[a]}),at(r,i=>{mn.configure(this,i,i.options),mn.addBox(this,i)})}_updateMetasets(){const e=this._metasets,n=this.data.datasets.length,r=e.length;if(e.sort((s,o)=>s.index-o.index),r>n){for(let s=n;sn.length&&delete this._stacks,e.forEach((r,s)=>{n.filter(o=>o===r._dataset).length===0&&this._destroyDatasetMeta(s)})}buildOrUpdateControllers(){const e=[],n=this.data.datasets;let r,s;for(this._removeUnreferencedMetasets(),r=0,s=n.length;r{this.getDatasetMeta(n).controller.reset()},this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(e){const n=this.config;n.update();const r=this._options=n.createResolver(n.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!r.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),this.notifyPlugins("beforeUpdate",{mode:e,cancelable:!0})===!1)return;const o=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let i=0;for(let c=0,d=this.data.datasets.length;c{c.reset()}),this._updateDatasets(e),this.notifyPlugins("afterUpdate",{mode:e}),this._layers.sort(im("z","_idx"));const{_active:a,_lastEvent:l}=this;l?this._eventHandler(l,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){at(this.scales,e=>{mn.removeBox(this,e)}),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const e=this.options,n=new Set(Object.keys(this._listeners)),r=new Set(e.events);(!Yp(n,r)||!!this._responsiveListeners!==e.responsive)&&(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:e}=this,n=this._getUniformDataChanges()||[];for(const{method:r,start:s,count:o}of n){const i=r==="_removeElements"?-o:o;QO(e,s,i)}}_getUniformDataChanges(){const e=this._dataChanges;if(!e||!e.length)return;this._dataChanges=[];const n=this.data.datasets.length,r=o=>new Set(e.filter(i=>i[0]===o).map((i,a)=>a+","+i.splice(1).join(","))),s=r(0);for(let o=1;oo.split(",")).map(o=>({method:o[1],start:+o[2],count:+o[3]}))}_updateLayout(e){if(this.notifyPlugins("beforeLayout",{cancelable:!0})===!1)return;mn.update(this,this.width,this.height,e);const n=this.chartArea,r=n.width<=0||n.height<=0;this._layers=[],at(this.boxes,s=>{r&&s.position==="chartArea"||(s.configure&&s.configure(),this._layers.push(...s._layers()))},this),this._layers.forEach((s,o)=>{s._idx=o}),this.notifyPlugins("afterLayout")}_updateDatasets(e){if(this.notifyPlugins("beforeDatasetsUpdate",{mode:e,cancelable:!0})!==!1){for(let n=0,r=this.data.datasets.length;n=0;--n)this._drawDataset(e[n]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(e){const n=this.ctx,r={meta:e,index:e.index,cancelable:!0},s=yN(this,e);this.notifyPlugins("beforeDatasetDraw",r)!==!1&&(s&&Qc(n,s),e.controller.draw(),s&&Jc(n),r.cancelable=!1,this.notifyPlugins("afterDatasetDraw",r))}isPointInArea(e){return h0(e,this.chartArea,this._minPadding)}getElementsAtEventForMode(e,n,r,s){const o=qN.modes[n];return typeof o=="function"?o(this,e,r,s):[]}getDatasetMeta(e){const n=this.data.datasets[e],r=this._metasets;let s=r.filter(o=>o&&o._dataset===n).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:n&&n.order||0,index:e,_dataset:n,_parsed:[],_sorted:!1},r.push(s)),s}getContext(){return this.$context||(this.$context=ds(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(e){const n=this.data.datasets[e];if(!n)return!1;const r=this.getDatasetMeta(e);return typeof r.hidden=="boolean"?!r.hidden:!n.hidden}setDatasetVisibility(e,n){const r=this.getDatasetMeta(e);r.hidden=!n}toggleDataVisibility(e){this._hiddenIndices[e]=!this._hiddenIndices[e]}getDataVisibility(e){return!this._hiddenIndices[e]}_updateVisibility(e,n,r){const s=r?"show":"hide",o=this.getDatasetMeta(e),i=o.controller._resolveAnimations(void 0,s);ho(n)?(o.data[n].hidden=!r,this.update()):(this.setDatasetVisibility(e,r),i.update(o,{visible:r}),this.update(a=>a.datasetIndex===e?s:void 0))}hide(e,n){this._updateVisibility(e,n,!1)}show(e,n){this._updateVisibility(e,n,!0)}_destroyDatasetMeta(e){const n=this._metasets[e];n&&n.controller&&n.controller._destroy(),delete this._metasets[e]}_stop(){let e,n;for(this.stop(),Kn.remove(this),e=0,n=this.data.datasets.length;e{n.addEventListener(this,o,i),e[o]=i},s=(o,i,a)=>{o.offsetX=i,o.offsetY=a,this._eventHandler(o)};at(this.options.events,o=>r(o,s))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const e=this._responsiveListeners,n=this.platform,r=(l,c)=>{n.addEventListener(this,l,c),e[l]=c},s=(l,c)=>{e[l]&&(n.removeEventListener(this,l,c),delete e[l])},o=(l,c)=>{this.canvas&&this.resize(l,c)};let i;const a=()=>{s("attach",a),this.attached=!0,this.resize(),r("resize",o),r("detach",i)};i=()=>{this.attached=!1,s("resize",o),this._stop(),this._resize(0,0),r("attach",a)},n.isAttached(this.canvas)?a():i()}unbindEvents(){at(this._listeners,(e,n)=>{this.platform.removeEventListener(this,n,e)}),this._listeners={},at(this._responsiveListeners,(e,n)=>{this.platform.removeEventListener(this,n,e)}),this._responsiveListeners=void 0}updateHoverStyle(e,n,r){const s=r?"set":"remove";let o,i,a,l;for(n==="dataset"&&(o=this.getDatasetMeta(e[0].datasetIndex),o.controller["_"+s+"DatasetHoverStyle"]()),a=0,l=e.length;a{const a=this.getDatasetMeta(o);if(!a)throw new Error("No dataset found at index "+o);return{datasetIndex:o,element:a.data[i],index:i}});!ea(r,n)&&(this._active=r,this._lastEvent=null,this._updateHoverStyles(r,n))}notifyPlugins(e,n,r){return this._plugins.notify(this,e,n,r)}isPluginEnabled(e){return this._plugins._cache.filter(n=>n.plugin.id===e).length===1}_updateHoverStyles(e,n,r){const s=this.options.hover,o=(l,c)=>l.filter(d=>!c.some(u=>d.datasetIndex===u.datasetIndex&&d.index===u.index)),i=o(n,e),a=r?e:o(e,n);i.length&&this.updateHoverStyle(i,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(e,n){const r={event:e,replay:n,cancelable:!0,inChartArea:this.isPointInArea(e)},s=i=>(i.options.events||this.options.events).includes(e.native.type);if(this.notifyPlugins("beforeEvent",r,s)===!1)return;const o=this._handleEvent(e,n,r.inChartArea);return r.cancelable=!1,this.notifyPlugins("afterEvent",r,s),(o||r.changed)&&this.render(),this}_handleEvent(e,n,r){const{_active:s=[],options:o}=this,i=n,a=this._getActiveElements(e,s,r,i),l=wM(e),c=JO(e,this._lastEvent,r,l);r&&(this._lastEvent=null,gt(o.onHover,[e,a,this],this),l&>(o.onClick,[e,a,this],this));const d=!ea(a,s);return(d||n)&&(this._active=a,this._updateHoverStyles(a,s,n)),this._lastEvent=c,d}_getActiveElements(e,n,r,s){if(e.type==="mouseout")return[];if(!r)return n;const o=this.options.hover;return this.getElementsAtEventForMode(e,o.mode,o,s)}},$e(fr,"defaults",xt),$e(fr,"instances",xa),$e(fr,"overrides",Mr),$e(fr,"registry",Vn),$e(fr,"version",KO),$e(fr,"getChart",cm),fr);function dm(){return at(_o.instances,t=>t._plugins.invalidate())}function RO(t,e,n){const{startAngle:r,x:s,y:o,outerRadius:i,innerRadius:a,options:l}=e,{borderWidth:c,borderJoinStyle:d}=l,u=Math.min(c/i,zn(r-n));if(t.beginPath(),t.arc(s,o,i-c/2,r+u/2,n-u/2),a>0){const f=Math.min(c/a,zn(r-n));t.arc(s,o,a+c/2,n-f/2,r+f/2,!0)}else{const f=Math.min(c/2,i*zn(r-n));if(d==="round")t.arc(s,o,f,n-ct/2,r+ct/2,!0);else if(d==="bevel"){const g=2*f*f,m=-g*Math.cos(n+ct/2)+s,p=-g*Math.sin(n+ct/2)+o,y=g*Math.cos(r+ct/2)+s,b=g*Math.sin(r+ct/2)+o;t.lineTo(m,p),t.lineTo(y,b)}}t.closePath(),t.moveTo(0,0),t.rect(0,0,t.canvas.width,t.canvas.height),t.clip("evenodd")}function eT(t,e,n){const{startAngle:r,pixelMargin:s,x:o,y:i,outerRadius:a,innerRadius:l}=e;let c=s/a;t.beginPath(),t.arc(o,i,a,r-c,n+c),l>s?(c=s/l,t.arc(o,i,l,n+c,r-c,!0)):t.arc(o,i,s,n+Mt,r-Mt),t.closePath(),t.clip()}function tT(t){return Rc(t,["outerStart","outerEnd","innerStart","innerEnd"])}function nT(t,e,n,r){const s=tT(t.options.borderRadius),o=(n-e)/2,i=Math.min(o,r*e/2),a=l=>{const c=(n-Math.min(o,l))*r/2;return en(l,0,Math.min(o,c))};return{outerStart:a(s.outerStart),outerEnd:a(s.outerEnd),innerStart:en(s.innerStart,0,i),innerEnd:en(s.innerEnd,0,i)}}function ps(t,e,n,r){return{x:n+t*Math.cos(e),y:r+t*Math.sin(e)}}function wa(t,e,n,r,s,o){const{x:i,y:a,startAngle:l,pixelMargin:c,innerRadius:d}=e,u=Math.max(e.outerRadius+r+n-c,0),f=d>0?d+r+n+c:0;let g=0;const m=s-l;if(r){const A=d>0?d-r:0,D=u>0?u-r:0,O=(A+D)/2,z=O!==0?m*O/(O+r):m;g=(m-z)/2}const p=Math.max(.001,m*u-n/ct)/u,y=(m-p)/2,b=l+y+g,_=s-y-g,{outerStart:x,outerEnd:E,innerStart:C,innerEnd:k}=nT(e,f,u,_-b),N=u-x,S=u-E,w=b+x/N,M=_-E/S,T=f+C,P=f+k,L=b+C/T,$=_-k/P;if(t.beginPath(),o){const A=(w+M)/2;if(t.arc(i,a,u,w,A),t.arc(i,a,u,A,M),E>0){const H=ps(S,M,i,a);t.arc(H.x,H.y,E,M,_+Mt)}const D=ps(P,_,i,a);if(t.lineTo(D.x,D.y),k>0){const H=ps(P,$,i,a);t.arc(H.x,H.y,k,_+Mt,$+Math.PI)}const O=(_-k/f+(b+C/f))/2;if(t.arc(i,a,f,_-k/f,O,!0),t.arc(i,a,f,O,b+C/f,!0),C>0){const H=ps(T,L,i,a);t.arc(H.x,H.y,C,L+Math.PI,b-Mt)}const z=ps(N,b,i,a);if(t.lineTo(z.x,z.y),x>0){const H=ps(N,w,i,a);t.arc(H.x,H.y,x,b-Mt,w)}}else{t.moveTo(i,a);const A=Math.cos(w)*u+i,D=Math.sin(w)*u+a;t.lineTo(A,D);const O=Math.cos(M)*u+i,z=Math.sin(M)*u+a;t.lineTo(O,z)}t.closePath()}function rT(t,e,n,r,s){const{fullCircles:o,startAngle:i,circumference:a}=e;let l=e.endAngle;if(o){wa(t,e,n,r,l,s);for(let c=0;c=ct&&g===0&&d!=="miter"&&RO(t,e,p),o||(wa(t,e,n,r,p,s),t.stroke())}class Eo extends Dr{constructor(n){super();$e(this,"circumference");$e(this,"endAngle");$e(this,"fullCircles");$e(this,"innerRadius");$e(this,"outerRadius");$e(this,"pixelMargin");$e(this,"startAngle");this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,n&&Object.assign(this,n)}inRange(n,r,s){const o=this.getProps(["x","y"],s),{angle:i,distance:a}=Qp(o,{x:n,y:r}),{startAngle:l,endAngle:c,innerRadius:d,outerRadius:u,circumference:f}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],s),g=(this.options.spacing+this.options.borderWidth)/2,m=Je(f,c-l),p=oa(i,l,c)&&l!==c,y=m>=St||p,b=Er(a,d+g,u+g);return y&&b}getCenterPoint(n){const{x:r,y:s,startAngle:o,endAngle:i,innerRadius:a,outerRadius:l}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],n),{offset:c,spacing:d}=this.options,u=(o+i)/2,f=(a+l+d+c)/2;return{x:r+Math.cos(u)*f,y:s+Math.sin(u)*f}}tooltipPosition(n){return this.getCenterPoint(n)}draw(n){const{options:r,circumference:s}=this,o=(r.offset||0)/4,i=(r.spacing||0)/2,a=r.circular;if(this.pixelMargin=r.borderAlign==="inner"?.33:0,this.fullCircles=s>St?Math.floor(s/St):0,s===0||this.innerRadius<0||this.outerRadius<0)return;n.save();const l=(this.startAngle+this.endAngle)/2;n.translate(Math.cos(l)*o,Math.sin(l)*o);const c=1-Math.sin(Math.min(ct,s||0)),d=o*c;n.fillStyle=r.backgroundColor,n.strokeStyle=r.borderColor,rT(n,this,d,i,a),sT(n,this,d,i,a),n.restore()}}$e(Eo,"id","arc"),$e(Eo,"defaults",{borderAlign:"center",borderColor:"#fff",borderDash:[],borderDashOffset:0,borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0,circular:!0,selfJoin:!1}),$e(Eo,"defaultRoutes",{backgroundColor:"backgroundColor"}),$e(Eo,"descriptors",{_scriptable:!0,_indexable:n=>n!=="borderDash"});function um(t,e){const{x:n,y:r,base:s,width:o,height:i}=t.getProps(["x","y","base","width","height"],e);let a,l,c,d,u;return t.horizontal?(u=i/2,a=Math.min(n,s),l=Math.max(n,s),c=r-u,d=r+u):(u=o/2,a=n-u,l=n+u,c=Math.min(r,s),d=Math.max(r,s)),{left:a,top:c,right:l,bottom:d}}function ur(t,e,n,r){return t?0:en(e,n,r)}function oT(t,e,n){const r=t.options.borderWidth,s=t.borderSkipped,o=g0(r);return{t:ur(s.top,o.top,0,n),r:ur(s.right,o.right,0,e),b:ur(s.bottom,o.bottom,0,n),l:ur(s.left,o.left,0,e)}}function iT(t,e,n){const{enableBorderRadius:r}=t.getProps(["enableBorderRadius"]),s=t.options.borderRadius,o=cs(s),i=Math.min(e,n),a=t.borderSkipped,l=r||Qe(s);return{topLeft:ur(!l||a.top||a.left,o.topLeft,0,i),topRight:ur(!l||a.top||a.right,o.topRight,0,i),bottomLeft:ur(!l||a.bottom||a.left,o.bottomLeft,0,i),bottomRight:ur(!l||a.bottom||a.right,o.bottomRight,0,i)}}function aT(t){const e=um(t),n=e.right-e.left,r=e.bottom-e.top,s=oT(t,n/2,r/2),o=iT(t,n/2,r/2);return{outer:{x:e.left,y:e.top,w:n,h:r,radius:o},inner:{x:e.left+s.l,y:e.top+s.t,w:n-s.l-s.r,h:r-s.t-s.b,radius:{topLeft:Math.max(0,o.topLeft-Math.max(s.t,s.l)),topRight:Math.max(0,o.topRight-Math.max(s.t,s.r)),bottomLeft:Math.max(0,o.bottomLeft-Math.max(s.b,s.l)),bottomRight:Math.max(0,o.bottomRight-Math.max(s.b,s.r))}}}}function pd(t,e,n,r){const s=e===null,o=n===null,a=t&&!(s&&o)&&um(t,r);return a&&(s||Er(e,a.left,a.right))&&(o||Er(n,a.top,a.bottom))}function lT(t){return t.topLeft||t.topRight||t.bottomLeft||t.bottomRight}function cT(t,e){t.rect(e.x,e.y,e.w,e.h)}function md(t,e,n={}){const r=t.x!==n.x?-e:0,s=t.y!==n.y?-e:0,o=(t.x+t.w!==n.x+n.w?e:0)-r,i=(t.y+t.h!==n.y+n.h?e:0)-s;return{x:t.x+r,y:t.y+s,w:t.w+o,h:t.h+i,radius:t.radius}}class ka extends Dr{constructor(e){super(),this.options=void 0,this.horizontal=void 0,this.base=void 0,this.width=void 0,this.height=void 0,this.inflateAmount=void 0,e&&Object.assign(this,e)}draw(e){const{inflateAmount:n,options:{borderColor:r,backgroundColor:s}}=this,{inner:o,outer:i}=aT(this),a=lT(i.radius)?aa:cT;e.save(),(i.w!==o.w||i.h!==o.h)&&(e.beginPath(),a(e,md(i,n,o)),e.clip(),a(e,md(o,-n,i)),e.fillStyle=r,e.fill("evenodd")),e.beginPath(),a(e,md(o,n)),e.fillStyle=s,e.fill(),e.restore()}inRange(e,n,r){return pd(this,e,n,r)}inXRange(e,n){return pd(this,e,null,n)}inYRange(e,n){return pd(this,null,e,n)}getCenterPoint(e){const{x:n,y:r,base:s,horizontal:o}=this.getProps(["x","y","base","horizontal"],e);return{x:o?(n+s)/2:n,y:o?r:(r+s)/2}}getRange(e){return e==="x"?this.width/2:this.height/2}}$e(ka,"id","bar"),$e(ka,"defaults",{borderSkipped:"start",borderWidth:0,borderRadius:0,inflateAmount:"auto",pointStyle:void 0}),$e(ka,"defaultRoutes",{backgroundColor:"backgroundColor",borderColor:"borderColor"});const fm=(t,e)=>{let{boxHeight:n=e,boxWidth:r=e}=t;return t.usePointStyle&&(n=Math.min(n,e),r=t.pointStyleWidth||Math.min(r,e)),{boxWidth:r,boxHeight:n,itemHeight:Math.max(e,n)}},dT=(t,e)=>t!==null&&e!==null&&t.datasetIndex===e.datasetIndex&&t.index===e.index;class hm extends Dr{constructor(e){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=e.chart,this.options=e.options,this.ctx=e.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(e,n,r){this.maxWidth=e,this.maxHeight=n,this._margins=r,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const e=this.options.labels||{};let n=gt(e.generateLabels,[this.chart],this)||[];e.filter&&(n=n.filter(r=>e.filter(r,this.chart.data))),e.sort&&(n=n.sort((r,s)=>e.sort(r,s,this.chart.data))),this.options.reverse&&n.reverse(),this.legendItems=n}fit(){const{options:e,ctx:n}=this;if(!e.display){this.width=this.height=0;return}const r=e.labels,s=Bt(r.font),o=s.size,i=this._computeTitleHeight(),{boxWidth:a,itemHeight:l}=fm(r,o);let c,d;n.font=s.string,this.isHorizontal()?(c=this.maxWidth,d=this._fitRows(i,o,a,l)+10):(d=this.maxHeight,c=this._fitCols(i,s,a,l)+10),this.width=Math.min(c,e.maxWidth||this.maxWidth),this.height=Math.min(d,e.maxHeight||this.maxHeight)}_fitRows(e,n,r,s){const{ctx:o,maxWidth:i,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.lineWidths=[0],d=s+a;let u=e;o.textAlign="left",o.textBaseline="middle";let f=-1,g=-d;return this.legendItems.forEach((m,p)=>{const y=r+n/2+o.measureText(m.text).width;(p===0||c[c.length-1]+y+2*a>i)&&(u+=d,c[c.length-(p>0?0:1)]=0,g+=d,f++),l[p]={left:0,top:g,row:f,width:y,height:s},c[c.length-1]+=y+a}),u}_fitCols(e,n,r,s){const{ctx:o,maxHeight:i,options:{labels:{padding:a}}}=this,l=this.legendHitBoxes=[],c=this.columnSizes=[],d=i-e;let u=a,f=0,g=0,m=0,p=0;return this.legendItems.forEach((y,b)=>{const{itemWidth:_,itemHeight:x}=uT(r,n,o,y,s);b>0&&g+x+2*a>d&&(u+=f+a,c.push({width:f,height:g}),m+=f+a,p++,f=g=0),l[b]={left:m,top:g,col:p,width:_,height:x},f=Math.max(f,_),g+=x+a}),u+=f,c.push({width:f,height:g}),u}adjustHitBoxes(){if(!this.options.display)return;const e=this._computeTitleHeight(),{legendHitBoxes:n,options:{align:r,labels:{padding:s},rtl:o}}=this,i=fs(o,this.left,this.width);if(this.isHorizontal()){let a=0,l=Ut(r,this.left+s,this.right-this.lineWidths[a]);for(const c of n)a!==c.row&&(a=c.row,l=Ut(r,this.left+s,this.right-this.lineWidths[a])),c.top+=this.top+e+s,c.left=i.leftForLtr(i.x(l),c.width),l+=c.width+s}else{let a=0,l=Ut(r,this.top+e+s,this.bottom-this.columnSizes[a].height);for(const c of n)c.col!==a&&(a=c.col,l=Ut(r,this.top+e+s,this.bottom-this.columnSizes[a].height)),c.top=l,c.left+=this.left+s,c.left=i.leftForLtr(i.x(c.left),c.width),l+=c.height+s}}isHorizontal(){return this.options.position==="top"||this.options.position==="bottom"}draw(){if(this.options.display){const e=this.ctx;Qc(e,this),this._draw(),Jc(e)}}_draw(){const{options:e,columnSizes:n,lineWidths:r,ctx:s}=this,{align:o,labels:i}=e,a=xt.color,l=fs(e.rtl,this.left,this.width),c=Bt(i.font),{padding:d}=i,u=c.size,f=u/2;let g;this.drawTitle(),s.textAlign=l.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=c.string;const{boxWidth:m,boxHeight:p,itemHeight:y}=fm(i,u),b=function(k,N,S){if(isNaN(m)||m<=0||isNaN(p)||p<0)return;s.save();const w=Je(S.lineWidth,1);if(s.fillStyle=Je(S.fillStyle,a),s.lineCap=Je(S.lineCap,"butt"),s.lineDashOffset=Je(S.lineDashOffset,0),s.lineJoin=Je(S.lineJoin,"miter"),s.lineWidth=w,s.strokeStyle=Je(S.strokeStyle,a),s.setLineDash(Je(S.lineDash,[])),i.usePointStyle){const M={radius:p*Math.SQRT2/2,pointStyle:S.pointStyle,rotation:S.rotation,borderWidth:w},T=l.xPlus(k,m/2),P=N+f;f0(s,M,T,P,i.pointStyleWidth&&m)}else{const M=N+Math.max((u-p)/2,0),T=l.leftForLtr(k,m),P=cs(S.borderRadius);s.beginPath(),Object.values(P).some(L=>L!==0)?aa(s,{x:T,y:M,w:m,h:p,radius:P}):s.rect(T,M,m,p),s.fill(),w!==0&&s.stroke()}s.restore()},_=function(k,N,S){mo(s,S.text,k,N+y/2,c,{strikethrough:S.hidden,textAlign:l.textAlign(S.textAlign)})},x=this.isHorizontal(),E=this._computeTitleHeight();x?g={x:Ut(o,this.left+d,this.right-r[0]),y:this.top+d+E,line:0}:g={x:this.left+d,y:Ut(o,this.top+E+d,this.bottom-n[0].height),line:0},C0(this.ctx,e.textDirection);const C=y+d;this.legendItems.forEach((k,N)=>{s.strokeStyle=k.fontColor,s.fillStyle=k.fontColor;const S=s.measureText(k.text).width,w=l.textAlign(k.textAlign||(k.textAlign=i.textAlign)),M=m+f+S;let T=g.x,P=g.y;l.setWidth(this.width),x?N>0&&T+M+d>this.right&&(P=g.y+=C,g.line++,T=g.x=Ut(o,this.left+d,this.right-r[g.line])):N>0&&P+C>this.bottom&&(T=g.x=T+n[g.line].width+d,g.line++,P=g.y=Ut(o,this.top+E+d,this.bottom-n[g.line].height));const L=l.x(T);if(b(L,P,k),T=LM(w,T+m+f,x?T+M:this.right,e.rtl),_(l.x(T),P,k),x)g.x+=M+d;else if(typeof k.text!="string"){const $=c.lineHeight;g.y+=gm(k,$)+d}else g.y+=C}),S0(this.ctx,e.textDirection)}drawTitle(){const e=this.options,n=e.title,r=Bt(n.font),s=pn(n.padding);if(!n.display)return;const o=fs(e.rtl,this.left,this.width),i=this.ctx,a=n.position,l=r.size/2,c=s.top+l;let d,u=this.left,f=this.width;if(this.isHorizontal())f=Math.max(...this.lineWidths),d=this.top+c,u=Ut(e.align,u,this.right-f);else{const m=this.columnSizes.reduce((p,y)=>Math.max(p,y.height),0);d=c+Ut(e.align,this.top,this.bottom-m-e.labels.padding-this._computeTitleHeight())}const g=Ut(a,u,u+f);i.textAlign=o.textAlign(Yc(a)),i.textBaseline="middle",i.strokeStyle=n.color,i.fillStyle=n.color,i.font=r.string,mo(i,n.text,g,d,r)}_computeTitleHeight(){const e=this.options.title,n=Bt(e.font),r=pn(e.padding);return e.display?n.lineHeight+r.height:0}_getLegendItemAt(e,n){let r,s,o;if(Er(e,this.left,this.right)&&Er(n,this.top,this.bottom)){for(o=this.legendHitBoxes,r=0;ro.length>i.length?o:i)),e+n.size/2+r.measureText(s).width}function hT(t,e,n){let r=t;return typeof e.text!="string"&&(r=gm(e,n)),r}function gm(t,e){const n=t.text?t.text.length:0;return e*n}function gT(t,e){return!!((t==="mousemove"||t==="mouseout")&&(e.onHover||e.onLeave)||e.onClick&&(t==="click"||t==="mouseup"))}var pm={id:"legend",_element:hm,start(t,e,n){const r=t.legend=new hm({ctx:t.ctx,options:n,chart:t});mn.configure(t,r,n),mn.addBox(t,r)},stop(t){mn.removeBox(t,t.legend),delete t.legend},beforeUpdate(t,e,n){const r=t.legend;mn.configure(t,r,n),r.options=n},afterUpdate(t){const e=t.legend;e.buildLabels(),e.adjustHitBoxes()},afterEvent(t,e){e.replay||t.legend.handleEvent(e.event)},defaults:{display:!0,position:"top",align:"center",fullSize:!0,reverse:!1,weight:1e3,onClick(t,e,n){const r=e.datasetIndex,s=n.chart;s.isDatasetVisible(r)?(s.hide(r),e.hidden=!0):(s.show(r),e.hidden=!1)},onHover:null,onLeave:null,labels:{color:t=>t.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:n,pointStyle:r,textAlign:s,color:o,useBorderRadius:i,borderRadius:a}}=t.legend.options;return t._getSortedDatasetMetas().map(l=>{const c=l.controller.getStyle(n?0:void 0),d=pn(c.borderWidth);return{text:e[l.index].label,fillStyle:c.backgroundColor,fontColor:o,hidden:!l.visible,lineCap:c.borderCapStyle,lineDash:c.borderDash,lineDashOffset:c.borderDashOffset,lineJoin:c.borderJoinStyle,lineWidth:(d.width+d.height)/4,strokeStyle:c.borderColor,pointStyle:r||c.pointStyle,rotation:c.rotation,textAlign:s||c.textAlign,borderRadius:i&&(a||c.borderRadius),datasetIndex:l.index}},this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class mm extends Dr{constructor(e){super(),this.chart=e.chart,this.options=e.options,this.ctx=e.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(e,n){const r=this.options;if(this.left=0,this.top=0,!r.display){this.width=this.height=this.right=this.bottom=0;return}this.width=this.right=e,this.height=this.bottom=n;const s=Et(r.text)?r.text.length:1;this._padding=pn(r.padding);const o=s*Bt(r.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const e=this.options.position;return e==="top"||e==="bottom"}_drawArgs(e){const{top:n,left:r,bottom:s,right:o,options:i}=this,a=i.align;let l=0,c,d,u;return this.isHorizontal()?(d=Ut(a,r,o),u=n+e,c=o-r):(i.position==="left"?(d=r+e,u=Ut(a,s,n),l=ct*-.5):(d=o-e,u=Ut(a,n,s),l=ct*.5),c=s-n),{titleX:d,titleY:u,maxWidth:c,rotation:l}}draw(){const e=this.ctx,n=this.options;if(!n.display)return;const r=Bt(n.font),o=r.lineHeight/2+this._padding.top,{titleX:i,titleY:a,maxWidth:l,rotation:c}=this._drawArgs(o);mo(e,n.text,0,0,r,{color:n.color,maxWidth:l,rotation:c,textAlign:Yc(n.align),textBaseline:"middle",translation:[i,a]})}}function pT(t,e){const n=new mm({ctx:t.ctx,options:e,chart:t});mn.configure(t,n,e),mn.addBox(t,n),t.titleBlock=n}var mT={id:"title",_element:mm,start(t,e,n){pT(t,n)},stop(t){const e=t.titleBlock;mn.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,n){const r=t.titleBlock;mn.configure(t,r,n),r.options=n},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Mo={average(t){if(!t.length)return!1;let e,n,r=new Set,s=0,o=0;for(e=0,n=t.length;ea+l)/r.size,y:s/o}},nearest(t,e){if(!t.length)return!1;let n=e.x,r=e.y,s=Number.POSITIVE_INFINITY,o,i,a;for(o=0,i=t.length;o-1?t.split(` +`):t}function bT(t,e){const{element:n,datasetIndex:r,index:s}=e,o=t.getDatasetMeta(r).controller,{label:i,value:a}=o.getLabelAndValue(s);return{chart:t,label:i,parsed:o.getParsed(s),raw:t.data.datasets[r].data[s],formattedValue:a,dataset:o.getDataset(),dataIndex:s,datasetIndex:r,element:n}}function bm(t,e){const n=t.chart.ctx,{body:r,footer:s,title:o}=t,{boxWidth:i,boxHeight:a}=e,l=Bt(e.bodyFont),c=Bt(e.titleFont),d=Bt(e.footerFont),u=o.length,f=s.length,g=r.length,m=pn(e.padding);let p=m.height,y=0,b=r.reduce((E,C)=>E+C.before.length+C.lines.length+C.after.length,0);if(b+=t.beforeBody.length+t.afterBody.length,u&&(p+=u*c.lineHeight+(u-1)*e.titleSpacing+e.titleMarginBottom),b){const E=e.displayColors?Math.max(a,l.lineHeight):l.lineHeight;p+=g*E+(b-g)*l.lineHeight+(b-1)*e.bodySpacing}f&&(p+=e.footerMarginTop+f*d.lineHeight+(f-1)*e.footerSpacing);let _=0;const x=function(E){y=Math.max(y,n.measureText(E).width+_)};return n.save(),n.font=c.string,at(t.title,x),n.font=l.string,at(t.beforeBody.concat(t.afterBody),x),_=e.displayColors?i+2+e.boxPadding:0,at(r,E=>{at(E.before,x),at(E.lines,x),at(E.after,x)}),_=0,n.font=d.string,at(t.footer,x),n.restore(),y+=m.width,{width:y,height:p}}function yT(t,e){const{y:n,height:r}=e;return nt.height-r/2?"bottom":"center"}function vT(t,e,n,r){const{x:s,width:o}=r,i=n.caretSize+n.caretPadding;if(t==="left"&&s+o+i>e.width||t==="right"&&s-o-i<0)return!0}function xT(t,e,n,r){const{x:s,width:o}=n,{width:i,chartArea:{left:a,right:l}}=t;let c="center";return r==="center"?c=s<=(a+l)/2?"left":"right":s<=o/2?c="left":s>=i-o/2&&(c="right"),vT(c,t,e,n)&&(c="center"),c}function ym(t,e,n){const r=n.yAlign||e.yAlign||yT(t,n);return{xAlign:n.xAlign||e.xAlign||xT(t,e,n,r),yAlign:r}}function wT(t,e){let{x:n,width:r}=t;return e==="right"?n-=r:e==="center"&&(n-=r/2),n}function kT(t,e,n){let{y:r,height:s}=t;return e==="top"?r+=n:e==="bottom"?r-=s+n:r-=s/2,r}function vm(t,e,n,r){const{caretSize:s,caretPadding:o,cornerRadius:i}=t,{xAlign:a,yAlign:l}=n,c=s+o,{topLeft:d,topRight:u,bottomLeft:f,bottomRight:g}=cs(i);let m=wT(e,a);const p=kT(e,l,c);return l==="center"?a==="left"?m+=c:a==="right"&&(m-=c):a==="left"?m-=Math.max(d,f)+s:a==="right"&&(m+=Math.max(u,g)+s),{x:en(m,0,r.width-e.width),y:en(p,0,r.height-e.height)}}function Ca(t,e,n){const r=pn(n.padding);return e==="center"?t.x+t.width/2:e==="right"?t.x+t.width-r.right:t.x+r.left}function xm(t){return Hn([],qn(t))}function CT(t,e,n){return ds(t,{tooltip:e,tooltipItems:n,type:"tooltip"})}function wm(t,e){const n=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return n?t.override(n):t}const km={beforeTitle:Yn,title(t){if(t.length>0){const e=t[0],n=e.chart.data.labels,r=n?n.length:0;if(this&&this.options&&this.options.mode==="dataset")return e.dataset.label||"";if(e.label)return e.label;if(r>0&&e.dataIndex"u"?km[e].call(n,r):s}class bd extends Dr{constructor(e){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=e.chart,this.options=e.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(e){this.options=e,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const e=this._cachedAnimations;if(e)return e;const n=this.chart,r=this.options.setContext(this.getContext()),s=r.enabled&&n.options.animation&&r.animations,o=new E0(this.chart,s);return s._cacheable&&(this._cachedAnimations=Object.freeze(o)),o}getContext(){return this.$context||(this.$context=CT(this.chart.getContext(),this,this._tooltipItems))}getTitle(e,n){const{callbacks:r}=n,s=tn(r,"beforeTitle",this,e),o=tn(r,"title",this,e),i=tn(r,"afterTitle",this,e);let a=[];return a=Hn(a,qn(s)),a=Hn(a,qn(o)),a=Hn(a,qn(i)),a}getBeforeBody(e,n){return xm(tn(n.callbacks,"beforeBody",this,e))}getBody(e,n){const{callbacks:r}=n,s=[];return at(e,o=>{const i={before:[],lines:[],after:[]},a=wm(r,o);Hn(i.before,qn(tn(a,"beforeLabel",this,o))),Hn(i.lines,tn(a,"label",this,o)),Hn(i.after,qn(tn(a,"afterLabel",this,o))),s.push(i)}),s}getAfterBody(e,n){return xm(tn(n.callbacks,"afterBody",this,e))}getFooter(e,n){const{callbacks:r}=n,s=tn(r,"beforeFooter",this,e),o=tn(r,"footer",this,e),i=tn(r,"afterFooter",this,e);let a=[];return a=Hn(a,qn(s)),a=Hn(a,qn(o)),a=Hn(a,qn(i)),a}_createItems(e){const n=this._active,r=this.chart.data,s=[],o=[],i=[];let a=[],l,c;for(l=0,c=n.length;le.filter(d,u,f,r))),e.itemSort&&(a=a.sort((d,u)=>e.itemSort(d,u,r))),at(a,d=>{const u=wm(e.callbacks,d);s.push(tn(u,"labelColor",this,d)),o.push(tn(u,"labelPointStyle",this,d)),i.push(tn(u,"labelTextColor",this,d))}),this.labelColors=s,this.labelPointStyles=o,this.labelTextColors=i,this.dataPoints=a,a}update(e,n){const r=this.options.setContext(this.getContext()),s=this._active;let o,i=[];if(!s.length)this.opacity!==0&&(o={opacity:0});else{const a=Mo[r.position].call(this,s,this._eventPosition);i=this._createItems(r),this.title=this.getTitle(i,r),this.beforeBody=this.getBeforeBody(i,r),this.body=this.getBody(i,r),this.afterBody=this.getAfterBody(i,r),this.footer=this.getFooter(i,r);const l=this._size=bm(this,r),c=Object.assign({},a,l),d=ym(this.chart,r,c),u=vm(r,c,d,this.chart);this.xAlign=d.xAlign,this.yAlign=d.yAlign,o={opacity:1,x:u.x,y:u.y,width:l.width,height:l.height,caretX:a.x,caretY:a.y}}this._tooltipItems=i,this.$context=void 0,o&&this._resolveAnimations().update(this,o),e&&r.external&&r.external.call(this,{chart:this.chart,tooltip:this,replay:n})}drawCaret(e,n,r,s){const o=this.getCaretPosition(e,r,s);n.lineTo(o.x1,o.y1),n.lineTo(o.x2,o.y2),n.lineTo(o.x3,o.y3)}getCaretPosition(e,n,r){const{xAlign:s,yAlign:o}=this,{caretSize:i,cornerRadius:a}=r,{topLeft:l,topRight:c,bottomLeft:d,bottomRight:u}=cs(a),{x:f,y:g}=e,{width:m,height:p}=n;let y,b,_,x,E,C;return o==="center"?(E=g+p/2,s==="left"?(y=f,b=y-i,x=E+i,C=E-i):(y=f+m,b=y+i,x=E-i,C=E+i),_=y):(s==="left"?b=f+Math.max(l,d)+i:s==="right"?b=f+m-Math.max(c,u)-i:b=this.caretX,o==="top"?(x=g,E=x-i,y=b-i,_=b+i):(x=g+p,E=x+i,y=b+i,_=b-i),C=x),{x1:y,x2:b,x3:_,y1:x,y2:E,y3:C}}drawTitle(e,n,r){const s=this.title,o=s.length;let i,a,l;if(o){const c=fs(r.rtl,this.x,this.width);for(e.x=Ca(this,r.titleAlign,r),n.textAlign=c.textAlign(r.titleAlign),n.textBaseline="middle",i=Bt(r.titleFont),a=r.titleSpacing,n.fillStyle=r.titleColor,n.font=i.string,l=0;l_!==0)?(e.beginPath(),e.fillStyle=o.multiKeyBackground,aa(e,{x:p,y:m,w:c,h:l,radius:b}),e.fill(),e.stroke(),e.fillStyle=i.backgroundColor,e.beginPath(),aa(e,{x:y,y:m+1,w:c-2,h:l-2,radius:b}),e.fill()):(e.fillStyle=o.multiKeyBackground,e.fillRect(p,m,c,l),e.strokeRect(p,m,c,l),e.fillStyle=i.backgroundColor,e.fillRect(y,m+1,c-2,l-2))}e.fillStyle=this.labelTextColors[r]}drawBody(e,n,r){const{body:s}=this,{bodySpacing:o,bodyAlign:i,displayColors:a,boxHeight:l,boxWidth:c,boxPadding:d}=r,u=Bt(r.bodyFont);let f=u.lineHeight,g=0;const m=fs(r.rtl,this.x,this.width),p=function(S){n.fillText(S,m.x(e.x+g),e.y+f/2),e.y+=f+o},y=m.textAlign(i);let b,_,x,E,C,k,N;for(n.textAlign=i,n.textBaseline="middle",n.font=u.string,e.x=Ca(this,y,r),n.fillStyle=r.bodyColor,at(this.beforeBody,p),g=a&&y!=="right"?i==="center"?c/2+d:c+2+d:0,E=0,k=s.length;E0&&n.stroke()}_updateAnimationTarget(e){const n=this.chart,r=this.$animations,s=r&&r.x,o=r&&r.y;if(s||o){const i=Mo[e.position].call(this,this._active,this._eventPosition);if(!i)return;const a=this._size=bm(this,e),l=Object.assign({},i,this._size),c=ym(n,e,l),d=vm(e,l,c,n);(s._to!==d.x||o._to!==d.y)&&(this.xAlign=c.xAlign,this.yAlign=c.yAlign,this.width=a.width,this.height=a.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,d))}}_willRender(){return!!this.opacity}draw(e){const n=this.options.setContext(this.getContext());let r=this.opacity;if(!r)return;this._updateAnimationTarget(n);const s={width:this.width,height:this.height},o={x:this.x,y:this.y};r=Math.abs(r)<.001?0:r;const i=pn(n.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;n.enabled&&a&&(e.save(),e.globalAlpha=r,this.drawBackground(o,e,s,n),C0(e,n.textDirection),o.y+=i.top,this.drawTitle(o,e,n),this.drawBody(o,e,n),this.drawFooter(o,e,n),S0(e,n.textDirection),e.restore())}getActiveElements(){return this._active||[]}setActiveElements(e,n){const r=this._active,s=e.map(({datasetIndex:a,index:l})=>{const c=this.chart.getDatasetMeta(a);if(!c)throw new Error("Cannot find a dataset at index "+a);return{datasetIndex:a,element:c.data[l],index:l}}),o=!ea(r,s),i=this._positionChanged(s,n);(o||i)&&(this._active=s,this._eventPosition=n,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(e,n,r=!0){if(n&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,o=this._active||[],i=this._getActiveElements(e,o,n,r),a=this._positionChanged(i,e),l=n||!ea(i,o)||a;return l&&(this._active=i,(s.enabled||s.external)&&(this._eventPosition={x:e.x,y:e.y},this.update(!0,n))),l}_getActiveElements(e,n,r,s){const o=this.options;if(e.type==="mouseout")return[];if(!s)return n.filter(a=>this.chart.data.datasets[a.datasetIndex]&&this.chart.getDatasetMeta(a.datasetIndex).controller.getParsed(a.index)!==void 0);const i=this.chart.getElementsAtEventForMode(e,o.mode,o,r);return o.reverse&&i.reverse(),i}_positionChanged(e,n){const{caretX:r,caretY:s,options:o}=this,i=Mo[o.position].call(this,e,n);return i!==!1&&(r!==i.x||s!==i.y)}}$e(bd,"positioners",Mo);var Cm={id:"tooltip",_element:bd,positioners:Mo,afterInit(t,e,n){n&&(t.tooltip=new bd({chart:t,options:n}))},beforeUpdate(t,e,n){t.tooltip&&t.tooltip.initialize(n)},reset(t,e,n){t.tooltip&&t.tooltip.initialize(n)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const n={tooltip:e};if(t.notifyPlugins("beforeTooltipDraw",{...n,cancelable:!0})===!1)return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",n)}},afterEvent(t,e){if(t.tooltip){const n=e.replay;t.tooltip.handleEvent(e.event,n,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:km},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>t!=="filter"&&t!=="itemSort"&&t!=="external",_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};const ST=(t,e,n,r)=>(typeof e=="string"?(n=t.push(e)-1,r.unshift({index:n,label:e})):isNaN(e)&&(n=null),n);function _T(t,e,n,r){const s=t.indexOf(e);if(s===-1)return ST(t,e,n,r);const o=t.lastIndexOf(e);return s!==o?n:s}const ET=(t,e)=>t===null?null:en(Math.round(t),0,e);function Sm(t){const e=this.getLabels();return t>=0&&tn.length-1?null:this.getPixelForValue(n[e].value)}getValueForPixel(e){return Math.round(this._startValue+this.getDecimalForPixel(e)*this._valueRange)}getBasePixel(){return this.bottom}}$e(yd,"id","category"),$e(yd,"defaults",{ticks:{callback:Sm}});function MT(t,e){const n=[],{bounds:s,step:o,min:i,max:a,precision:l,count:c,maxTicks:d,maxDigits:u,includeBounds:f}=t,g=o||1,m=d-1,{min:p,max:y}=e,b=!lt(i),_=!lt(a),x=!lt(c),E=(y-p)/(u+1);let C=qp((y-p)/m/g)*g,k,N,S,w;if(C<1e-14&&!b&&!_)return[{value:p},{value:y}];w=Math.ceil(y/C)-Math.floor(p/C),w>m&&(C=qp(w*C/m/g)*g),lt(l)||(k=Math.pow(10,l),C=Math.ceil(C*k)/k),s==="ticks"?(N=Math.floor(p/C)*C,S=Math.ceil(y/C)*C):(N=p,S=y),b&&_&&o&&_M((a-i)/o,C/1e3)?(w=Math.round(Math.min((a-i)/C,d)),C=(a-i)/w,N=i,S=a):x?(N=b?i:N,S=_?a:S,w=c-1,C=(S-N)/w):(w=(S-N)/C,ra(w,Math.round(w),C/1e3)?w=Math.round(w):w=Math.ceil(w));const M=Math.max(Zp(C),Zp(N));k=Math.pow(10,lt(l)?M:l),N=Math.round(N*k)/k,S=Math.round(S*k)/k;let T=0;for(b&&(f&&N!==i?(n.push({value:i}),Na)break;n.push({value:P})}return _&&f&&S!==a?n.length&&ra(n[n.length-1].value,a,_m(a,E,t))?n[n.length-1].value=a:n.push({value:a}):(!_||S===a)&&n.push({value:S}),n}function _m(t,e,{horizontal:n,minRotation:r}){const s=Xn(r),o=(n?Math.sin(s):Math.cos(s))||.001,i=.75*e*(""+t).length;return Math.min(e/o,i)}class NT extends gs{constructor(e){super(e),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(e,n){return lt(e)||(typeof e=="number"||e instanceof Number)&&!isFinite(+e)?null:+e}handleTickRangeOptions(){const{beginAtZero:e}=this.options,{minDefined:n,maxDefined:r}=this.getUserBounds();let{min:s,max:o}=this;const i=l=>s=n?s:l,a=l=>o=r?o:l;if(e){const l=cr(s),c=cr(o);l<0&&c<0?a(0):l>0&&c>0&&i(0)}if(s===o){let l=o===0?1:Math.abs(o*.05);a(o+l),e||i(s-l)}this.min=s,this.max=o}getTickLimit(){const e=this.options.ticks;let{maxTicksLimit:n,stepSize:r}=e,s;return r?(s=Math.ceil(this.max/r)-Math.floor(this.min/r)+1,s>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${r} would result generating up to ${s} ticks. Limiting to 1000.`),s=1e3)):(s=this.computeTickLimit(),n=n||11),n&&(s=Math.min(n,s)),s}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const e=this.options,n=e.ticks;let r=this.getTickLimit();r=Math.max(2,r);const s={maxTicks:r,bounds:e.bounds,min:e.min,max:e.max,precision:n.precision,step:n.stepSize,count:n.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:n.minRotation||0,includeBounds:n.includeBounds!==!1},o=this._range||this,i=MT(s,o);return e.bounds==="ticks"&&EM(i,this,"value"),e.reverse?(i.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),i}configure(){const e=this.ticks;let n=this.min,r=this.max;if(super.configure(),this.options.offset&&e.length){const s=(r-n)/Math.max(e.length-1,1)/2;n-=s,r+=s}this._startValue=n,this._endValue=r,this._valueRange=r-n}getLabelForValue(e){return Kc(e,this.chart.options.locale,this.options.ticks.format)}}class vd extends NT{determineDataLimits(){const{min:e,max:n}=this.getMinMax(!0);this.min=gn(e)?e:0,this.max=gn(n)?n:1,this.handleTickRangeOptions()}computeTickLimit(){const e=this.isHorizontal(),n=e?this.width:this.height,r=Xn(this.options.ticks.minRotation),s=(e?Math.sin(r):Math.cos(r))||.001,o=this._resolveTickFontOptions(0);return Math.ceil(n/Math.min(40,o.lineHeight/s))}getPixelForValue(e){return e===null?NaN:this.getPixelForDecimal((e-this._startValue)/this._valueRange)}getValueForPixel(e){return this._startValue+this.getDecimalForPixel(e)*this._valueRange}}$e(vd,"id","linear"),$e(vd,"defaults",{ticks:{callback:l0.formatters.numeric}});const Sa={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},nn=Object.keys(Sa);function Em(t,e){return t-e}function Mm(t,e){if(lt(e))return null;const n=t._adapter,{parser:r,round:s,isoWeekday:o}=t._parseOpts;let i=e;return typeof r=="function"&&(i=r(i)),gn(i)||(i=typeof r=="string"?n.parse(i,r):n.parse(i)),i===null?null:(s&&(i=s==="week"&&(sa(o)||o===!0)?n.startOf(i,"isoWeek",o):n.startOf(i,s)),+i)}function Nm(t,e,n,r){const s=nn.length;for(let o=nn.indexOf(t);o=nn.indexOf(n);o--){const i=nn[o];if(Sa[i].common&&t._adapter.diff(s,r,i)>=e-1)return i}return nn[n?nn.indexOf(n):0]}function TT(t){for(let e=nn.indexOf(t)+1,n=nn.length;e=e?n[r]:n[s];t[o]=!0}}function AT(t,e,n,r){const s=t._adapter,o=+s.startOf(e[0].value,r),i=e[e.length-1].value;let a,l;for(a=o;a<=i;a=+s.add(a,1,r))l=n[a],l>=0&&(e[l].major=!0);return e}function Tm(t,e,n){const r=[],s={},o=e.length;let i,a;for(i=0;i+e.value))}initOffsets(e=[]){let n=0,r=0,s,o;this.options.offset&&e.length&&(s=this.getDecimalForValue(e[0]),e.length===1?n=1-s:n=(this.getDecimalForValue(e[1])-s)/2,o=this.getDecimalForValue(e[e.length-1]),e.length===1?r=o:r=(o-this.getDecimalForValue(e[e.length-2]))/2);const i=e.length<3?.5:.25;n=en(n,0,i),r=en(r,0,i),this._offsets={start:n,end:r,factor:1/(n+1+r)}}_generate(){const e=this._adapter,n=this.min,r=this.max,s=this.options,o=s.time,i=o.unit||Nm(o.minUnit,n,r,this._getLabelCapacity(n)),a=Je(s.ticks.stepSize,1),l=i==="week"?o.isoWeekday:!1,c=sa(l)||l===!0,d={};let u=n,f,g;if(c&&(u=+e.startOf(u,"isoWeek",l)),u=+e.startOf(u,c?"day":i),e.diff(r,n,i)>1e5*a)throw new Error(n+" and "+r+" are too far apart with stepSize of "+a+" "+i);const m=s.ticks.source==="data"&&this.getDataTimestamps();for(f=u,g=0;f+p)}getLabelForValue(e){const n=this._adapter,r=this.options.time;return r.tooltipFormat?n.format(e,r.tooltipFormat):n.format(e,r.displayFormats.datetime)}format(e,n){const s=this.options.time.displayFormats,o=this._unit,i=n||s[o];return this._adapter.format(e,i)}_tickFormatFunction(e,n,r,s){const o=this.options,i=o.ticks.callback;if(i)return gt(i,[e,n,r],this);const a=o.time.displayFormats,l=this._unit,c=this._majorUnit,d=l&&a[l],u=c&&a[c],f=r[n],g=c&&u&&f&&f.major;return this._adapter.format(e,s||(g?u:d))}generateTickLabels(e){let n,r,s;for(n=0,r=e.length;n0?a:1}getDataTimestamps(){let e=this._cache.data||[],n,r;if(e.length)return e;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(n=0,r=s.length;n=t[r].pos&&e<=t[s].pos&&({lo:r,hi:s}=Gc(t,"pos",e)),{pos:o,time:a}=t[r],{pos:i,time:l}=t[s]):(e>=t[r].time&&e<=t[s].time&&({lo:r,hi:s}=Gc(t,"time",e)),{time:o,pos:a}=t[r],{time:i,pos:l}=t[s]);const c=i-o;return c?a+(l-a)*(e-o)/c:a}class Am extends _a{constructor(e){super(e),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const e=this._getTimestampsForTable(),n=this._table=this.buildLookupTable(e);this._minPos=Ea(n,this.min),this._tableRange=Ea(n,this.max)-this._minPos,super.initOffsets(e)}buildLookupTable(e){const{min:n,max:r}=this,s=[],o=[];let i,a,l,c,d;for(i=0,a=e.length;i=n&&c<=r&&s.push(c);if(s.length<2)return[{time:n,pos:0},{time:r,pos:1}];for(i=0,a=s.length;is-o)}_getTimestampsForTable(){let e=this._cache.all||[];if(e.length)return e;const n=this.getDataTimestamps(),r=this.getLabelTimestamps();return n.length&&r.length?e=this.normalize(n.concat(r)):e=n.length?n:r,e=this._cache.all=e,e}getDecimalForValue(e){return(Ea(this._table,e)-this._minPos)/this._tableRange}getValueForPixel(e){const n=this._offsets,r=this.getDecimalForPixel(e)/n.factor-n.end;return Ea(this._table,r*this._tableRange+this._minPos,!0)}}$e(Am,"id","timeseries"),$e(Am,"defaults",_a.defaults);const Dm="label";function Im(t,e){typeof t=="function"?t(e):t&&(t.current=e)}function DT(t,e){const n=t.options;n&&e&&Object.assign(n,e)}function Lm(t,e){t.labels=e}function Pm(t,e,n=Dm){const r=[];t.datasets=e.map(s=>{const o=t.datasets.find(i=>i[n]===s[n]);return!o||!s.data||r.includes(o)?{...s}:(r.push(o),Object.assign(o,s),o)})}function IT(t,e=Dm){const n={labels:[],datasets:[]};return Lm(n,t.labels),Pm(n,t.datasets,e),n}function LT(t,e){const{height:n=150,width:r=300,redraw:s=!1,datasetIdKey:o,type:i,data:a,options:l,plugins:c=[],fallbackContent:d,updateMode:u,...f}=t,g=v.useRef(null),m=v.useRef(null),p=()=>{g.current&&(m.current=new _o(g.current,{type:i,data:IT(a,o),options:l&&{...l},plugins:c}),Im(e,m.current))},y=()=>{Im(e,null),m.current&&(m.current.destroy(),m.current=null)};return v.useEffect(()=>{!s&&m.current&&l&&DT(m.current,l)},[s,l]),v.useEffect(()=>{!s&&m.current&&Lm(m.current.config.data,a.labels)},[s,a.labels]),v.useEffect(()=>{!s&&m.current&&a.datasets&&Pm(m.current.config.data,a.datasets,o)},[s,a.datasets]),v.useEffect(()=>{m.current&&(s?(y(),setTimeout(p)):m.current.update(u))},[s,l,a.labels,a.datasets,u]),v.useEffect(()=>{m.current&&(y(),setTimeout(p))},[i]),v.useEffect(()=>(p(),()=>y()),[]),h.jsx("canvas",{ref:g,role:"img",height:n,width:r,...f,children:d})}const PT=v.forwardRef(LT);function $m(t,e){return _o.register(e),v.forwardRef((n,r)=>h.jsx(PT,{...n,ref:r,type:t}))}const $T=$m("bar",fa),FT=$m("doughnut",yo);_o.register(yd,vd,ka,mT,Cm,pm);const zT=({labels:t,datasets:e,height:n=200,showLegend:r=!1,showGrid:s=!1,showYGrid:o=!1,showYAxis:i=!1,className:a="",id:l,editorAttrs:c})=>{const d=v.useMemo(()=>({labels:t??[],datasets:(e??[]).map((m,p)=>({label:m.label,data:m.data,backgroundColor:m.backgroundColor||(p===0?"#7C3AED":"#DDD6FE"),borderRadius:m.borderRadius??4,barThickness:16,maxBarThickness:20,yAxisID:m.yAxisID??"y"}))}),[t,e]),u=v.useMemo(()=>(e??[]).some(m=>m.yAxisID==="y1"),[e]),f=v.useMemo(()=>({responsive:!0,maintainAspectRatio:!1,animation:{duration:750,easing:"easeOutQuart"},animations:{x:{duration:0},y:{from:m=>{if(m.type==="data")return m.chart.scales.y.getPixelForValue(0)}}},transitions:{active:{animation:{duration:200}},resize:{animation:{duration:0}}},plugins:{legend:{display:r,position:"top"},tooltip:{backgroundColor:"#1e293b",titleColor:"#fff",bodyColor:"#fff",padding:12,cornerRadius:8}},scales:{x:{grid:{display:s},border:{display:!1},ticks:{color:"#94a3b8",font:{size:11}}},y:{type:"linear",position:"left",grid:{display:o,color:"#e2e8f0"},border:{display:!1},ticks:{display:i,color:"#94a3b8",font:{size:11},padding:8},beginAtZero:!0,grace:"5%"},...u?{y1:{type:"linear",position:"right",grid:{display:!1},border:{display:!1},ticks:{display:i,color:"#94a3b8",font:{size:11},padding:8},beginAtZero:!0,grace:"5%"}}:{}}}),[r,s,o,i,u]),g=typeof n=="number"?n:/^\d+$/.test(String(n))?`${n}px`:n;return h.jsx("div",{className:`bar-chart-container ${a}`,style:{height:g},id:l,...c,children:h.jsx($T,{data:d,options:f})})},VT=v.memo(zT,(t,e)=>JSON.stringify(t.labels)===JSON.stringify(e.labels)&&JSON.stringify(t.datasets)===JSON.stringify(e.datasets)&&t.height===e.height&&t.showLegend===e.showLegend&&t.showGrid===e.showGrid&&t.showYGrid===e.showYGrid&&t.showYAxis===e.showYAxis&&t.className===e.className&&t.id===e.id&&t.editorAttrs===e.editorAttrs);_o.register(Eo,Cm,pm);const HT=({data:t,centerLabel:e,centerValue:n,size:r=200,cutout:s="70%",showLegend:o=!1,className:i="",id:a,editorAttrs:l})=>{const c=v.useMemo(()=>({labels:(t??[]).map(f=>f.name),datasets:[{data:(t??[]).map(f=>f.value),backgroundColor:(t??[]).map(f=>f.color),borderWidth:0,hoverOffset:4}]}),[t]),d=v.useMemo(()=>({responsive:!0,maintainAspectRatio:!1,cutout:s,plugins:{legend:{display:o},tooltip:{backgroundColor:"#1e293b",titleColor:"#fff",bodyColor:"#fff",padding:12,cornerRadius:8,callbacks:{label:f=>` $${f.parsed.toLocaleString()}`}}}}),[s,o]),u=typeof r=="number"?r:/^\d+$/.test(String(r))?`${r}px`:r;return h.jsx("div",{className:`donut-chart-wrapper ${i}`,id:a,...l,children:h.jsxs("div",{className:"relative",style:{width:u,height:u},children:[h.jsx(FT,{data:c,options:d}),(e||n)&&h.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center pointer-events-none",children:[e&&h.jsx("span",{className:"text-xs text-slate-500 dark:text-slate-400",children:e}),n&&h.jsx("span",{className:"text-lg font-bold text-slate-900 dark:text-white",children:n})]})]})})},UT=({id:t,className:e,style:n,children:r,editorAttrs:s})=>h.jsx("div",{id:t,className:e,style:n,...s,children:r}),BT=({children:t,cols:e=1,responsive:n,gap:r,rowGap:s,colGap:o,autoRows:i,autoCols:a,flow:l="row",className:c="",style:d,onClick:u,id:f,editorAttrs:g})=>{const m=["grid"];if(e&&m.push(`grid-cols-${e}`),n&&(n.sm&&m.push(`sm:grid-cols-${n.sm}`),n.md&&m.push(`md:grid-cols-${n.md}`),n.lg&&m.push(`lg:grid-cols-${n.lg}`),n.xl&&m.push(`xl:grid-cols-${n.xl}`),n["2xl"]&&m.push(`2xl:grid-cols-${n["2xl"]}`)),r!==void 0&&r>0&&m.push(`gap-${r}`),s!==void 0&&s>0&&m.push(`gap-y-${s}`),o!==void 0&&o>0&&m.push(`gap-x-${o}`),i){const y={auto:"auto-rows-auto",min:"auto-rows-min",max:"auto-rows-max",fr:"auto-rows-fr"};m.push(y[i])}if(a){const y={auto:"auto-cols-auto",min:"auto-cols-min",max:"auto-cols-max",fr:"auto-cols-fr"};m.push(y[a])}const p={row:"grid-flow-row",col:"grid-flow-col",dense:"grid-flow-dense","row-dense":"grid-flow-row-dense","col-dense":"grid-flow-col-dense"};return m.push(p[l]),c&&m.push(c),h.jsx(I,{className:m.join(" "),style:d,onClick:u,id:f,...g,children:t})},jT=({children:t,direction:e="row",justify:n="start",align:r="stretch",wrap:s="nowrap",gap:o=0,grow:i=!1,shrink:a=!0,className:l="",style:c,onClick:d,id:u,editorAttrs:f})=>{const g=["flex"],m={row:"flex-row","row-reverse":"flex-row-reverse",col:"flex-col","col-reverse":"flex-col-reverse"};g.push(m[e]);const p={wrap:"flex-wrap",nowrap:"flex-nowrap","wrap-reverse":"flex-wrap-reverse"};g.push(p[s]);const y={start:"justify-start",end:"justify-end",center:"justify-center",between:"justify-between",around:"justify-around",evenly:"justify-evenly"};g.push(y[n]);const b={start:"items-start",end:"items-end",center:"items-center",baseline:"items-baseline",stretch:"items-stretch"};return g.push(b[r]),o>0&&g.push(`gap-${o}`),i===!0?g.push("flex-grow"):typeof i=="number"&&i>0&&g.push(`flex-grow-${i}`),a===!1?g.push("shrink-0"):a===!0?g.push("flex-shrink"):typeof a=="number"&&a>0&&g.push(`flex-shrink-${a}`),l&&g.push(l),h.jsx(I,{className:g.join(" "),style:c,onClick:d,id:u,...f,children:t})},WT=({title:t,subtitle:e,children:n,padding:r="none",background:s="none",maxWidth:o,centered:i=!1,border:a=!1,shadow:l="none",rounded:c=!1,className:d="",style:u,onClick:f,id:g,editorAttrs:m})=>{const p=[],y={none:"",sm:"p-2",md:"p-4",lg:"p-6",xl:"p-8"};y[r]&&p.push(y[r]);const b={none:"",white:"bg-white dark:bg-gray-700",gray:"bg-gray-50 dark:bg-gray-800",primary:"bg-blue-50 dark:bg-blue-900/20",secondary:"bg-gray-50 dark:bg-gray-800"};if(s!=="none"&&p.push(b[s]),o){const x={sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-xl","2xl":"max-w-2xl","4xl":"max-w-4xl","6xl":"max-w-6xl","7xl":"max-w-7xl",full:"max-w-full"};p.push(x[o])}i&&p.push("mx-auto"),a&&p.push("border","border-gray-200","dark:border-gray-700");const _={none:"",sm:"shadow-sm",md:"shadow-md",lg:"shadow-lg",xl:"shadow-xl"};return l!=="none"&&p.push(_[l]),c&&p.push("rounded-lg"),d&&p.push(d),h.jsxs(Od,{className:p.join(" "),style:u,onClick:f,id:g,...m,children:[(t||e)&&h.jsxs("div",{className:"mb-4",children:[t&&h.jsx("h2",{className:"text-2xl font-bold text-gray-900 dark:text-white",children:t}),e&&h.jsx("p",{className:"mt-1 text-sm text-gray-600 dark:text-gray-400",children:e})]}),n]})},GT=({leftWidth:t="250px",rightWidth:e="300px",leftSlot:n,centerSlot:r,rightSlot:s,className:o="",style:i,id:a,editorAttrs:l})=>{const c=`flex flex-row w-full h-full ${o}`.trim(),d={width:t,flexShrink:0},u={flex:1,minWidth:0},f={width:e,flexShrink:0};return h.jsxs(I,{className:c,style:i,id:a,...l,children:[h.jsx(I,{className:"flex flex-col",style:d,children:n}),h.jsx(I,{className:"flex flex-col",style:u,children:r}),h.jsx(I,{className:"flex flex-col",style:f,children:s})]})},YT={language:"json",theme:"vs-dark",options:{automaticLayout:!0,minimap:{enabled:!0},scrollBeyondLastLine:!1,fontSize:14,tabSize:2,formatOnPaste:!0,formatOnType:!0,wordWrap:"on",folding:!0,lineNumbers:"on",renderWhitespace:"selection"}},Fm=[{uri:`${window.location.origin}/schemas/layout.json`,fileMatch:["*layout*.json"],schema:{type:"object",properties:{version:{type:"string"},endpoint:{type:"string"},components:{type:"array"}},required:["version","endpoint","components"]}}],XT={"vs/nls":{availableLanguages:{"*":"ko"}}};function KT(t){t.languages.json.jsonDefaults.setDiagnosticsOptions({validate:!0,schemas:Fm,enableSchemaRequest:!0,schemaValidation:"error",schemaRequest:"error"})}const qT={identifier:"sirsoft-admin_basic",vendor:"sirsoft",name:{ko:"Admin Basic",en:"Admin Basic"},version:"1.0.4",license:"MIT",description:{ko:"그누보드7 기본 관리자 템플릿",en:"Gnuboard7 default admin template"},type:"admin",locales:["ko","en"],author:{name:"sirsoft",email:"contact@sirsoft.com",url:"https://sirsoft.com"},release_date:"2026-05-15",g7_version:">=7.0.0",dependencies:{modules:{},plugins:{}},assets:{css:["assets/css/main.css","assets/css/ui-system.css"],js:["dist/components.js"],fonts:["assets/fonts/"],images:["assets/images/"]},components:{basic:["a","button","checkbox","div","form","h1","h2","h3","hr","icon","img","input","label","li","nav","p","section","select","span","svg","table","tbody","td","textarea","th","thead","tr","ul"],composite:["action-menu","admin-footer","admin-header","admin-sidebar","alert-dialog","breadcrumb","card","confirm-dialog","data-grid","dialog","dropdown","empty-state","filter-group","icon-button","loading-spinner","modal","page-header","pagination","product-card","search-bar","stat-card","status-badge","tab-navigation","template-card","toast"],layout:["container","flex","grid","section"]},preview:{thumbnail:"preview/thumbnail.png",screenshots:["preview/screenshot1.png","preview/screenshot2.png"]},error_config:{layouts:{401:"errors/401",403:"errors/403",404:"errors/404",500:"errors/500",503:"errors/503",maintenance:"errors/maintenance"}},github_url:"https://github.com/gnuboard/g7-template-sirsoft-admin_basic",github_changelog_url:"https://github.com/gnuboard/g7-template-sirsoft-admin_basic/blob/main/CHANGELOG.md",externals:[{id:"fontawesome",type:"style",url:"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css",preconnect:"https://cdnjs.cloudflare.com"},{id:"pretendard",type:"webfont",url:"https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css",preconnect:"https://cdn.jsdelivr.net",crossorigin:"anonymous"},{id:"flag-icons",type:"style",url:"https://cdn.jsdelivr.net/npm/flag-icons@7.2.3/css/flag-icons.min.css"}]},Zn=window.G7Core?.createLogger?.("Handler:SetTheme")??{log:(...t)=>console.log("[Handler:SetTheme]",...t),warn:(...t)=>console.warn("[Handler:SetTheme]",...t),error:(...t)=>console.error("[Handler:SetTheme]",...t)},xd=["auto","light","dark"],zm="g7_color_scheme",ZT=t=>t==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t,Ma=t=>{const e=ZT(t);document.documentElement.setAttribute("data-theme",e),e==="dark"?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")};function QT(){try{const t=localStorage.getItem(zm),e=t&&xd.includes(t)?t:"auto";Ma(e);try{window.G7Core?.state?.setGlobal?.({theme:e})}catch(n){Zn.warn("Failed to set global theme state:",n)}Zn.log("Initial theme applied:",e)}catch(t){Zn.warn("Failed to load initial theme:",t),Ma("auto")}}async function JT(t,e){const n=t?.target;if(n&&typeof n=="string"&&xd.includes(n)){Ma(n);try{window.G7Core?.state?.setGlobal?.({theme:n})}catch(r){Zn.warn("Failed to set global theme state:",r)}return}QT()}async function RT(t,e){let n;if(t?.target&&typeof t.target=="string"&&!t.target.includes("{{")&&(n=t.target),!n||typeof n!="string"){Zn.warn("Invalid theme:",n);return}if(!xd.includes(n)){Zn.warn("Unsupported theme:",n);return}const r=n;try{localStorage.setItem(zm,r)}catch(s){Zn.error("Failed to save theme to localStorage:",s);return}Ma(r);try{window.G7Core?.state?.setGlobal?.({theme:r})}catch(s){Zn.warn("Failed to set global theme state:",s)}Zn.log("Theme changed to:",r)}const No=window.G7Core?.createLogger?.("Handler:InitMenu")??{log:(...t)=>console.log("[Handler:InitMenu]",...t),warn:(...t)=>console.warn("[Handler:InitMenu]",...t),error:(...t)=>console.error("[Handler:InitMenu]",...t)};function Vm(t,e){for(const n of t){if(n.slug===e)return n;if(n.children&&n.children.length>0){const r=Vm(n.children,e);if(r)return r}}return null}function Hm(t){return new URLSearchParams(window.location.search).get(t)}function e5(t){return window.G7Core?.state?.getDataSource?.(t)}async function t5(t,e=30,n=100){for(let r=0;r0)return o;await new Promise(i=>setTimeout(i,n))}return null}async function n5(t,e){const n=window.G7Core,r=Hm("menu"),s=Hm("mode");if(!r){No.log("[initMenuFromUrl] No menu parameter in URL");return}if(!n?.state?.set){No.warn("[initMenuFromUrl] G7Core.state.set not available");return}const o=await t5("menus");if(!o){No.warn("[initMenuFromUrl] menus data not available after waiting");return}const i=Vm(o,r);if(!i){No.warn("[initMenuFromUrl] Menu not found with slug:",r);return}const a=s==="edit"?"edit":"view";n.state.set({selectedMenuId:i.id,selectedMenu:i,panelMode:a}),a==="edit"&&n.state.set({formData:{name:i.name,slug:i.slug,url:i.url,icon:i.icon,parent_id:i.parent_id,module_id:i.module_id,is_active:i.is_active}}),No.log("[initMenuFromUrl] Menu initialized from URL:",{slug:r,mode:a,menuId:i.id})}const qt=window.G7Core?.createLogger?.("Handler:FilterVisibility")??{log:(...t)=>console.log("[Handler:FilterVisibility]",...t),warn:(...t)=>console.warn("[Handler:FilterVisibility]",...t),error:(...t)=>console.error("[Handler:FilterVisibility]",...t)},Na="g7_filter_visibility_";async function r5(t,e){const{storageKey:n,defaultFilters:r=[],stateKey:s="visibleFilters"}=t?.params||{};if(!n){qt.warn("[initFilterVisibility] storageKey is required");return}const o=`${Na}${n}`;try{const i=localStorage.getItem(o);let a;if(i){const l=JSON.parse(i);Array.isArray(l)?(a=l,qt.log(`[initFilterVisibility] Loaded from localStorage: ${a.join(", ")}`)):(a=r,qt.warn("[initFilterVisibility] Invalid saved data format, using defaults"))}else a=r,qt.log(`[initFilterVisibility] No saved data, using defaults: ${r.join(", ")}`);e?.setState&&e.setState({[s]:a})}catch(i){qt.error("[initFilterVisibility] Failed to load filter visibility:",i),e?.setState&&e.setState({[s]:r})}}async function s5(t,e){const{storageKey:n,filters:r}=t?.params||{};if(!n){qt.warn("[saveFilterVisibility] storageKey is required");return}if(!Array.isArray(r)){qt.warn("[saveFilterVisibility] filters must be an array");return}const s=`${Na}${n}`;try{localStorage.setItem(s,JSON.stringify(r)),qt.log(`[saveFilterVisibility] Saved to localStorage: ${r.join(", ")}`)}catch(o){qt.error("[saveFilterVisibility] Failed to save filter visibility:",o)}}async function o5(t,e){const{storageKey:n,filterId:r,stateKey:s="visibleFilters"}=t?.params||{};if(!n||!r){qt.warn("[toggleFilterVisibility] storageKey and filterId are required");return}const o=e?.state?.[s]||[];let i;o.includes(r)?i=o.filter(l=>l!==r):i=[...o,r],e?.setState&&e.setState({[s]:i});const a=`${Na}${n}`;try{localStorage.setItem(a,JSON.stringify(i)),qt.log(`[toggleFilterVisibility] Toggled ${r}: ${i.join(", ")}`)}catch(l){qt.error("[toggleFilterVisibility] Failed to save:",l)}}async function i5(t,e){const{storageKey:n,defaultFilters:r=[],stateKey:s="visibleFilters"}=t?.params||{};if(!n){qt.warn("[resetFilterVisibility] storageKey is required");return}const o=`${Na}${n}`;try{localStorage.setItem(o,JSON.stringify(r)),e?.setState&&e.setState({[s]:r}),qt.log(`[resetFilterVisibility] Reset to defaults: ${r.join(", ")}`)}catch(i){qt.error("[resetFilterVisibility] Failed to reset:",i)}}const ms=window.G7Core?.createLogger?.("Handler:MultilingualTag")??{log:(...t)=>console.log("[Handler:MultilingualTag]",...t),warn:(...t)=>console.warn("[Handler:MultilingualTag]",...t),error:(...t)=>console.error("[Handler:MultilingualTag]",...t)};async function a5(t,e){const n=window.G7Core;if(!n){ms.error("G7Core not available");return}const r=n.state?.getGlobal?.()?.multilingualTagEdit;if(!r){ms.warn("No multilingualTagEdit state found"),n.modal?.close?.();return}const{fieldName:s,editingIndex:o,values:i,statePath:a,currentTags:l}=r;if(!Object.values(i||{}).some(g=>typeof g=="string"&&g.trim()!=="")){ms.warn("No values to save"),n.modal?.close?.(),wd(n);return}const d=Array.isArray(l)?l:[];let u;o==null?u=[...d,i]:u=d.map((g,m)=>m===o?i:g);const f=a||(s?.startsWith("form.")?s:`form.${s}`);n.state?.setParentLocal?.({[f]:u}),n.modal?.close?.(),wd(n)}async function l5(t,e){const n=window.G7Core;if(!n){ms.error("G7Core not available");return}n.modal?.close?.(),wd(n)}async function c5(t,e){const n=window.G7Core;if(!n){ms.error("G7Core not available");return}const r=t?.params?.locale;if(!r){ms.warn("No locale specified");return}const s=e?.event;let o="";s?.target?.value!==void 0?o=s.target.value:s?.value!==void 0&&(o=s.value);const a=(n.state?.getGlobal?.()||{}).multilingualTagEdit||{},l={...a.values||{},[r]:o};n.dispatch?.({handler:"setState",params:{target:"global",multilingualTagEdit:{...a,values:l}}})}function wd(t){t.dispatch?.({handler:"setState",params:{target:"global",multilingualTagEdit:null}})}function Um(t){const e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0"),s=String(t.getHours()).padStart(2,"0"),o=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0");return`${e}-${n}-${r}T${s}:${o}:${i}`}function d5(t){const e=new Date,n=new Date(e.getFullYear(),e.getMonth(),e.getDate(),23,59,59),r=Um(n),s=new Date(e.getFullYear(),e.getMonth(),e.getDate(),0,0,0);switch(t){case"today":break;case"week":s.setDate(s.getDate()-6);break;case"month":s.setMonth(s.getMonth()-1);break;case"3months":s.setMonth(s.getMonth()-3);break;case"6months":s.setMonth(s.getMonth()-6);break;case"1year":s.setFullYear(s.getFullYear()-1);break}return{startDate:Um(s),endDate:r}}function u5(t){const{preset:e}=t.params||{preset:"today"},{startDate:n,endDate:r}=d5(e);return{startDate:n,endDate:r,preset:e}}const Oo=window.G7Core?.createLogger?.("Handler:Sidebar")??{log:(...t)=>console.log("[Handler:Sidebar]",...t),warn:(...t)=>console.warn("[Handler:Sidebar]",...t),error:(...t)=>console.error("[Handler:Sidebar]",...t)},Bm="g7_admin_sidebar_collapsed";function jm(){try{return localStorage.getItem(Bm)==="1"}catch{return!1}}function f5(t){try{localStorage.setItem(Bm,t?"1":"0")}catch(e){Oo.error("Failed to save sidebar state to localStorage:",e)}try{window.G7Core?.state?.setGlobal?.({sidebarCollapsed:t})}catch(e){Oo.warn("Failed to set global sidebar state:",e)}}function Wm(){const t=jm();try{window.G7Core?.state?.setGlobal?.({sidebarCollapsed:t})}catch(e){Oo.warn("Failed to restore sidebar state:",e)}Oo.log("Initial sidebar collapsed state restored:",t)}async function h5(t,e){Wm()}async function g5(t,e){const r=!(()=>{try{return!!window.G7Core?.state?.getGlobal?.()?.sidebarCollapsed}catch{return jm()}})();f5(r),Oo.log("Sidebar collapsed toggled to:",r)}const kd=window.G7Core?.createLogger?.("Handler:DownloadAttachment")??{log:(...t)=>console.log("[Handler:DownloadAttachment]",...t),warn:(...t)=>console.warn("[Handler:DownloadAttachment]",...t),error:(...t)=>console.error("[Handler:DownloadAttachment]",...t)};async function p5(t,e){const n=window.G7Core,{url:r,filename:s}=t?.params||{};if(!r){kd.warn("다운로드 URL 이 없어 요청을 건너뜁니다.");return}if(!n?.api?.get){kd.error("G7Core.api.get 이 초기화되지 않아 다운로드를 실행할 수 없습니다.");return}try{const o=await n.api.get(r,{responseType:"blob"});if(o){const i=URL.createObjectURL(o),a=document.createElement("a");a.href=i,a.download=s||"",document.body.appendChild(a),a.click(),document.body.removeChild(a),URL.revokeObjectURL(i)}}catch(o){kd.error("첨부파일 다운로드 실패",o),n?.toast?.error?.(n?.t?.("common.download_failed")??"Download failed")}}const Oa=window.G7Core?.createLogger?.("Handler:DetectAssetUrlMode")??{log:(...t)=>console.log("[Handler:DetectAssetUrlMode]",...t),warn:(...t)=>console.warn("[Handler:DetectAssetUrlMode]",...t),error:(...t)=>console.error("[Handler:DetectAssetUrlMode]",...t)},m5="G7_ASSET_PROBE_OK";async function Gm(t){try{const e=await fetch(t,{cache:"no-store",credentials:"omit"});if(!e.ok)return!1;const n=e.headers.get("content-type")??"";return/javascript|ecmascript/i.test(n)?(await e.text()).includes(m5):!1}catch{return!1}}async function Ym(){const[t,e]=await Promise.all([Gm("/api/system/asset-probe.js"),Gm("/api/system/asset-probe")]);return t&&e?"extension":!t&&e?"extensionless":"unavailable"}function b5(){return window.G7Config?.settings?.general?.asset_url_mode??"extension"}async function y5(t){try{const e=await Ym();if(e==="unavailable")return;const n=b5();if(e===n)return;window.G7Core?.state?.setGlobal?.({assetUrlModeDrift:{detected:e,stored:n}})}catch(e){Oa.warn("자산 URL 방식 대조 실패 (대시보드 동작에는 영향 없음):",e)}}async function v5(t,e){const n=window.G7Core,r=e?.setState??n?.state?.setLocal,s=o=>{typeof r=="function"?r({asset_url_mode_detect_status:o}):Oa.warn("setState 를 찾지 못해 감지 상태를 폼에 반영하지 못했습니다")};try{s("detecting");const o=await Ym();if(o==="unavailable"){s("unavailable");return}typeof r=="function"?r({"general.asset_url_mode":o,asset_url_mode_detect_status:o}):Oa.warn("setState 를 찾지 못해 감지 결과를 폼에 반영하지 못했습니다")}catch(o){Oa.error("자산 URL 방식 감지 실패:",o),s("unavailable")}}const Ta={detectAssetUrlMode:v5,checkAssetUrlModeDrift:y5,setTheme:RT,initTheme:JT,scrollToSection:eu,initMenuFromUrl:n5,initFilterVisibility:r5,saveFilterVisibility:s5,toggleFilterVisibility:o5,resetFilterVisibility:i5,saveMultilingualTag:a5,cancelMultilingualTag:l5,updateMultilingualTagValue:c5,setDateRange:u5,toggleSidebar:g5,initSidebar:h5,downloadAttachment:p5},To=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic:IdentityLauncher")??{log:(...t)=>console.log("[Template:sirsoft-admin_basic:IdentityLauncher]",...t),warn:(...t)=>console.warn("[Template:sirsoft-admin_basic:IdentityLauncher]",...t),error:(...t)=>console.error("[Template:sirsoft-admin_basic:IdentityLauncher]",...t)};function x5(){const e=window.G7Core?.api?.getToken?.()??null;return e?{Authorization:`Bearer ${e}`}:{}}function w5(){try{const t=typeof window<"u"?window.localStorage?.getItem("g7_locale"):null;return t?{"Accept-Language":t}:{}}catch{return{}}}async function k5(t,e=null){const n={purpose:t.purpose};t.provider_id&&(n.provider_id=t.provider_id),e&&(n.target=e);const r=t.challenge_start_url||"/api/identity/challenges",s=await fetch(r,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json",Accept:"application/json",...w5(),...x5()},body:JSON.stringify(n)}),o=await s.json().catch(()=>null);if(!s.ok||!o?.success){const a=o?.message??`Challenge 시작 실패 (HTTP ${s.status})`;throw new Error(a)}const i=o.data??o;return{id:i.id,expires_at:i.expires_at,render_hint:i.render_hint??t.render_hint??"text_code",public_payload:i.public_payload,redirect_url:i.redirect_url,max_attempts:typeof i.max_attempts=="number"?i.max_attempts:void 0}}async function C5(t){const e=window.G7Core,n=e?.identity;if(!e?.dispatch||!e?.state||!n)return To.error("G7Core 또는 G7Core.identity 가 초기화되지 않아 launcher 를 실행할 수 없습니다."),{status:"failed",failureCode:"G7_NOT_READY"};if(t.render_hint==="external_redirect"||t.redirect_url)return n.redirectExternally(t);const r=t.target&&(t.target.email||t.target.phone)?t.target:null;let s;try{s=await k5(t,r)}catch(m){To.error("Challenge 시작 실패:",m);try{await e.dispatch({handler:"toast",params:{type:"error",message:m instanceof Error?m.message:"본인인증 시작에 실패했습니다."}})}catch{}return{status:"failed",failureCode:"CHALLENGE_START_FAILED",reason:m instanceof Error?m.message:String(m)}}if(s.redirect_url)return n.redirectExternally({...t,render_hint:"external_redirect",redirect_url:s.redirect_url});const o=s.expires_at?new Date(s.expires_at).getTime():0,i=o>0?Math.max(0,Math.floor((o-Date.now())/1e3)):0,a=typeof s.max_attempts=="number"?s.max_attempts:0;e.state.set({identityChallenge:{policy_key:t.policy_key,purpose:t.purpose,provider_id:t.provider_id??null,render_hint:s.render_hint,challenge_id:s.id,expires_at:s.expires_at,public_payload:s.public_payload??{},target:r,code:"",error:null,attempts:0,maxAttempts:a,remainingSeconds:i,resendCooldown:0}});let l=o,c=0;const d=window.setInterval(()=>{const m=l>0?Math.max(0,Math.floor((l-Date.now())/1e3)):0;c=Math.max(0,c-1),e.state.set({identityChallenge:{remainingSeconds:m,resendCooldown:c}})},1e3);let u=null;try{u=e.state.subscribe?.(m=>{const p=m?.identityChallenge?.expires_at;if(typeof p=="string"){const b=new Date(p).getTime();Number.isFinite(b)&&b!==l&&(l=b)}const y=m?.identityChallenge?.resendCooldown;typeof y=="number"&&y>c&&(c=y)})??null}catch{}const f=n.createDeferred();try{await e.dispatch({handler:"openModal",target:"identity-challenge-modal"})}catch(m){return To.error("openModal 실패 — 풀페이지로 폴백:",m),window.clearInterval(d),u?.(),n.redirectExternally({...t,render_hint:"external_redirect",redirect_url:`/admin/identity/challenge?challenge_id=${encodeURIComponent(s.id)}&return=${encodeURIComponent(window.location.href)}`})}const g=await f;return window.clearInterval(d),u?.(),g}function S5(){const t=window.G7Core?.identity;if(!t?.setLauncher){To.warn("G7Core.identity 가 아직 초기화되지 않아 launcher 등록을 건너뜁니다. 코어 부트스트랩 순서를 확인하세요.");return}t.setLauncher(C5),To.log("IDV launcher registered (sirsoft-admin_basic)")}function _5(t){const e=t.icons;return Array.isArray(e)?e.filter(n=>!!n&&typeof n=="object"&&typeof n.value=="string"):[]}function E5(t,e){const n=t.label;return typeof n=="string"?n.startsWith("$t:")?e(n.slice(3)):n:t.value}function M5(t,e,n){if(n==="")return!0;const r=n.toLowerCase();return t.value.toLowerCase().includes(r)||e.toLowerCase().includes(r)?!0:(t.keywords??[]).some(s=>s.toLowerCase().includes(r))}function N5({entry:t}){const e=t.preview;return e?.html?h.jsx("span",{className:"g7le-icon-preview",dangerouslySetInnerHTML:{__html:e.html},style:Sd}):e?.className?h.jsx("span",{className:`g7le-icon-preview ${e.className}`,style:Sd}):h.jsx("i",{className:`fas fa-${t.value}`,style:Sd,"aria-hidden":"true"})}function O5({control:t,value:e,onChange:n,t:r}){const s=_5(t),o=e==null?"":String(e),i=typeof t.iconColumns=="number"?t.iconColumns:6,a=t.iconSearchPlaceholder,l=typeof a=="string"&&a.startsWith("$t:")?r(a.slice(3)):r("layout_editor.icon_picker.search_placeholder"),[c,d]=v.useState("");if(s.length===0)return h.jsx("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:Xm,children:h.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-free","data-testid":"g7le-widget-icon-picker-free",value:o,placeholder:r("layout_editor.icon_picker.free_input_hint"),onChange:g=>n(g.target.value===""?void 0:g.target.value),style:Km})});const u=g=>E5(g,r),f=s.filter(g=>M5(g,u(g),c));return h.jsxs("div",{className:"g7le-widget g7le-widget--icon-picker","data-testid":"g7le-widget-icon-picker",style:Xm,children:[h.jsx("input",{type:"text",className:"g7le-widget g7le-widget--icon-search","data-testid":"g7le-widget-icon-picker-search",value:c,placeholder:l,onChange:g=>d(g.target.value),style:Km}),h.jsxs("div",{className:"g7le-icon-grid","data-testid":"g7le-widget-icon-picker-grid",style:{...T5,gridTemplateColumns:`repeat(${i}, minmax(0, 1fr))`},children:[h.jsx("button",{type:"button","data-testid":"g7le-icon-cell-none",title:r("layout_editor.icon_picker.none"),onClick:()=>n(void 0),"aria-pressed":o==="",style:o===""?qm:Cd,children:h.jsx("span",{style:A5,children:"—"})}),f.map(g=>{const m=g.value===o;return h.jsx("button",{type:"button","data-testid":`g7le-icon-cell-${g.value}`,title:u(g),"aria-pressed":m,onClick:()=>n(g.value),style:m?qm:Cd,children:h.jsx(N5,{entry:g})},g.value)})]}),f.length===0&&h.jsx("div",{"data-testid":"g7le-widget-icon-picker-empty",style:D5,children:r("layout_editor.icon_picker.no_results")})]})}const Xm={display:"flex",flexDirection:"column",gap:6,width:"100%"},Km={padding:"5px 8px",fontSize:12,border:"1px solid #cbd5e1",borderRadius:6,width:"100%"},T5={display:"grid",gap:4,maxHeight:220,overflowY:"auto",padding:2},Cd={display:"flex",alignItems:"center",justifyContent:"center",minHeight:34,padding:4,border:"1px solid #e2e8f0",borderRadius:6,background:"#fff",cursor:"pointer"},qm={...Cd,border:"2px solid #2563eb",background:"#eff6ff"},Sd={display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:16,lineHeight:1},A5={fontSize:10,color:"#475569",fontFamily:"monospace",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"100%"},D5={fontSize:11,color:"#94a3b8",fontStyle:"italic",padding:"4px 2px"},Zm=130,Mn=20;function I5(t){const e=t?.arrayProp;return typeof e=="string"&&e?e:"tabs"}function L5(t){let e=0;for(const n of t){const r=typeof n.id=="number"?n.id:Number(n.id);Number.isFinite(r)&&r>e&&(e=r)}return e+1}function P5({node:t,params:e,cellBoxes:n,t:r,onPatchNode:s}){const o=I5(e),i=v.useMemo(()=>{const g=(t.props??{})[o];return typeof g=="string"||!Array.isArray(g)?null:g},[t,o]),a=v.useMemo(()=>{const g=new Map,m=`props.${o}.`;for(const p of n??[]){if(!p.path.startsWith(m))continue;const y=Number(p.path.slice(m.length));Number.isInteger(y)&&g.set(y,{top:p.top,left:p.left,width:p.width,height:p.height})}return g},[n,o]),l=v.useCallback(g=>{const m=t.props??{};s({...t,props:{...m,[o]:g}})},[t,o,s]),c=v.useCallback(g=>{if(!i)return;const p={id:L5(i),label:r("editor.tabnav_inplace.new_tab")},y=i.slice();y.splice(g,0,p),l(y)},[i,l,r]),d=v.useCallback(g=>{if(!i)return;const m=i.slice();m.splice(g,1),l(m)},[i,l]),u=v.useCallback((g,m)=>{if(!i)return;const p=g+m;if(p<0||p>=i.length)return;const y=i.slice(),[b]=y.splice(g,1);y.splice(p,0,b),l(y)},[i,l]);if(!i||a.size===0)return null;const f={position:"absolute",width:Mn,height:Mn,display:"flex",alignItems:"center",justifyContent:"center",fontSize:12,lineHeight:1,border:"1px solid rgba(0,0,0,0.15)",borderRadius:4,background:"#ffffff",color:"#1f2937",cursor:"pointer",boxShadow:"0 1px 2px rgba(0,0,0,0.18)",pointerEvents:"auto",padding:0,zIndex:Zm};return h.jsx("div",{className:"g7le-tabnav-inplace","data-testid":"g7le-tabnav-inplace",style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:Zm},children:i.map((g,m)=>{const p=a.get(m);if(!p)return null;const y=p.left+p.width;return h.jsxs("div",{"data-testid":`g7le-tabnav-aff-${m}`,children:[h.jsx("button",{type:"button","data-testid":`g7le-tabnav-remove-${m}`,title:r("editor.tabnav_inplace.remove"),"aria-label":r("editor.tabnav_inplace.remove"),style:{...f,left:y-Mn/2,top:p.top-Mn/2,color:"#b91c1c"},onClick:b=>{b.preventDefault(),b.stopPropagation(),d(m)},children:"✕"}),m>0&&h.jsx("button",{type:"button","data-testid":`g7le-tabnav-move-left-${m}`,title:r("editor.tabnav_inplace.move_left"),"aria-label":r("editor.tabnav_inplace.move_left"),style:{...f,left:p.left,top:p.top+p.height-Mn/2},onClick:b=>{b.preventDefault(),b.stopPropagation(),u(m,-1)},children:"◀"}),m{b.preventDefault(),b.stopPropagation(),u(m,1)},children:"▶"}),m===0&&h.jsx("button",{type:"button","data-testid":"g7le-tabnav-add-0",title:r("editor.tabnav_inplace.add"),"aria-label":r("editor.tabnav_inplace.add"),style:{...f,left:p.left-Mn,top:p.top+p.height/2-Mn/2,color:"#15803d"},onClick:b=>{b.preventDefault(),b.stopPropagation(),c(0)},children:"+"}),h.jsx("button",{type:"button","data-testid":`g7le-tabnav-add-${m+1}`,title:r("editor.tabnav_inplace.add"),"aria-label":r("editor.tabnav_inplace.add"),style:{...f,left:y-Mn/2,top:p.top+p.height/2-Mn/2,color:"#15803d"},onClick:b=>{b.preventDefault(),b.stopPropagation(),c(m+1)},children:"+"})]},`tab-aff-${g.id}-${m}`)})})}const _d=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic:EditorWidgets")??{log:(...t)=>console.log("[Template:sirsoft-admin_basic:EditorWidgets]",...t),warn:(...t)=>console.warn("[Template:sirsoft-admin_basic:EditorWidgets]",...t)};function $5(){if(typeof window>"u")return;const t=window.G7Core?.layoutEditor;if(!t?.registerWidget){_d.warn("G7Core.layoutEditor 미노출 — icon-picker 위젯 등록 보류");return}t.registerWidget("icon-picker",O5),_d.log("편집기 위젯 'icon-picker' 등록됨(Font Awesome 검색 그리드)"),t.registerCanvasOverlay&&(t.registerCanvasOverlay("tabnav",P5),_d.log("편집기 캔버스 오버레이 'tabnav' 등록됨(TabNavigation 탭 인플레이스)"))}const Ed=window.G7Core?.createLogger?.("Template:sirsoft-admin_basic")??{log:(...t)=>console.log("[Template:sirsoft-admin_basic]",...t),warn:(...t)=>console.warn("[Template:sirsoft-admin_basic]",...t),error:(...t)=>console.error("[Template:sirsoft-admin_basic]",...t)};typeof window<"u"&&(window.G7TemplateHandlers=Ta);function Qm(){if(typeof window<"u"){let t=0;const e=50,n=()=>{const r=window.G7Core?.getActionDispatcher?.();r?(Object.entries(Ta).forEach(([s,o])=>{r.registerHandler(s,o)}),Ed.log(`${Object.keys(Ta).length} custom handler(s) registered:`,Object.keys(Ta)),S5(),Wm()):(t++,t<=e?(Ed.warn(`ActionDispatcher not found, retrying... (${t}/${e})`),setTimeout(n,100)):Ed.error("Failed to register handlers: ActionDispatcher not available after maximum retries"))};document.readyState==="complete"?n():window.addEventListener("load",n)}}return $5(),Qm(),R.A=Lr,R.Accordion=sc,R.ActionMenu=ys,R.AdminFooter=_y,R.AdminHeader=wy,R.AdminSidebar=xy,R.Alert=cu,R.AlertDialog=uy,R.Badge=Pd,R.BarChart=VT,R.Breadcrumb=ou,R.Button=oe,R.Card=pb,R.CardGrid=Kb,R.CategoryTree=jS,R.Checkbox=vn,R.ChipCheckbox=OE,R.Code=ub,R.CodeEditor=Uv,R.ColumnSelector=Kv,R.ConfirmDialog=ru,R.Container=UT,R.DataGrid=jb,R.Dialog=nu,R.Div=I,R.DonutChart=HT,R.Dropdown=Zb,R.DropdownButton=TE,R.DropdownMenuItem=AE,R.DynamicFieldList=UE,R.EmptyState=by,R.ExtensionBadge=nh,R.FileInput=Md,R.FileUploader=Ep,R.FilterGroup=ey,R.FilterVisibilitySelector=CE,R.Flex=jT,R.Form=Pa,R.FormField=YE,R.Fragment=hb,R.Grid=BT,R.H1=Da,R.H2=Jn,R.H3=Ir,R.H4=Nd,R.Hr=gb,R.HtmlContent=Za,R.HtmlEditor=vE,R.I=mt,R.Icon=re,R.IconButton=ly,R.IconName=ce,R.IconSelect=FS,R.ImageGallery=ji,R.Img=yn,R.Input=kt,R.Label=sn,R.LanguageSelector=Sy,R.LayoutEditorHeader=Ty,R.LayoutFileList=Ay,R.LayoutHistoryPanel=jv,R.LayoutWarnings=kE,R.Li=La,R.LoadingSpinner=vy,R.LoginForm=Wv,R.Modal=Bo,R.MultilingualInput=Ha,R.MultilingualLocaleContext=$c,R.MultilingualTabPanel=GE,R.MultilingualTagInput=Gk,R.Nav=Po,R.NotificationCenter=ky,R.Ol=sb,R.Optgroup=lb,R.Option=ab,R.P=yt,R.PageHeader=Ey,R.PageLoading=Jv,R.PageTransitionIndicator=Zv,R.Pagination=$o,R.PermissionTree=US,R.Pre=fb,R.ProductCard=Qb,R.RadioGroup=mE,R.RichSelect=ME,R.SearchBar=Rb,R.SearchableDropdown=Yk,R.Section=Od,R.SectionHeader=Ny,R.SectionLayout=WT,R.Select=Pr,R.SlotContainer=EE,R.SortableMenuItem=Gh,R.SortableMenuList=PS,R.Span=Q,R.StatCard=Jb,R.StatusBadge=Fo,R.Svg=Un,R.TabNavigation=ty,R.TabNavigationScroll=iy,R.Table=$a,R.TagInput=Bk,R.TagSelect=Xk,R.Tbody=za,R.Td=Zt,R.TemplateCard=Oy,R.Textarea=Qn,R.Tfoot=Va,R.Th=$r,R.Thead=Fa,R.ThemeToggle=Yv,R.ThreeColumnLayout=GT,R.Toast=py,R.Toggle=Ua,R.Tr=Rn,R.Ul=Ia,R.UnifiedIcon=db,R.UserProfile=Cy,R.VersionList=_u,R.configureJSONLanguageService=KT,R.defaultJSONSchemas=Fm,R.executeImageDownload=Ac,R.initTemplate=Qm,R.jsonEditorDefaults=YT,R.monacoInitOptions=XT,R.templateMetadata=qT,R.useCurrentMultilingualLocale=Ip,R.useImageGallery=fp,Object.defineProperty(R,Symbol.toStringTag,{value:"Module"}),R})({},ReactJSXRuntime,React,ReactDOM); +//# sourceMappingURL=components.iife.js.map diff --git a/templates/_bundled/sirsoft-admin_basic/lang/partial/en/extensions.json b/templates/_bundled/sirsoft-admin_basic/lang/partial/en/extensions.json index 6b9bd2c7..8fba330c 100644 --- a/templates/_bundled/sirsoft-admin_basic/lang/partial/en/extensions.json +++ b/templates/_bundled/sirsoft-admin_basic/lang/partial/en/extensions.json @@ -30,6 +30,9 @@ "compat_warning_title": "Core version compatibility warning", "compat_warning_message": "This {{type}} requires core {{required}} or higher. (current: {{installed}})", "compat_guide_link": "View core upgrade guide", - "force_label": "Ignore warning and force update (not recommended)" + "force_label": "Ignore warning and force update (not recommended)", + "rebuild_search_index_title": "Search index", + "rebuild_search_index_label": "Rebuild search indexes that hold no indexed content after the update", + "rebuild_search_index_desc": "Rebuilding locks or re-indexes the target. On a live site, leave this unchecked and run it during a maintenance window." } } diff --git a/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/extensions.json b/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/extensions.json index 29290011..258f9fef 100644 --- a/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/extensions.json +++ b/templates/_bundled/sirsoft-admin_basic/lang/partial/ko/extensions.json @@ -30,6 +30,9 @@ "compat_warning_title": "코어 버전 호환성 경고", "compat_warning_message": "이 {{type}} 은(는) 코어 {{required}} 이상이 필요합니다. (현재: {{installed}})", "compat_guide_link": "코어 업그레이드 가이드 보기", - "force_label": "경고를 무시하고 강제 업데이트 (권장하지 않음)" + "force_label": "경고를 무시하고 강제 업데이트 (권장하지 않음)", + "rebuild_search_index_title": "검색 인덱스", + "rebuild_search_index_label": "업데이트 후 색인이 누락된 검색 인덱스를 재생성", + "rebuild_search_index_desc": "재생성 중에는 대상 인덱스가 잠기거나 다시 색인됩니다. 운영 중인 사이트라면 체크하지 말고, 유지보수 시간에 별도로 수행하세요." } } diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/admin_module_list.json b/templates/_bundled/sirsoft-admin_basic/layouts/admin_module_list.json index 9526a871..af46dd55 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/admin_module_list.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/admin_module_list.json @@ -1013,7 +1013,8 @@ "hasModifiedModuleLayouts": false, "modifiedModuleLayoutsCount": 0, "modifiedModuleLayouts": [], - "modifiedModuleLayoutsCheckFailed": false + "modifiedModuleLayoutsCheckFailed": false, + "moduleRebuildSearchIndex": false } }, { diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/admin_plugin_list.json b/templates/_bundled/sirsoft-admin_basic/layouts/admin_plugin_list.json index f55b65e4..3ead2e1b 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/admin_plugin_list.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/admin_plugin_list.json @@ -1025,7 +1025,8 @@ "hasModifiedPluginLayouts": false, "modifiedPluginLayoutsCount": 0, "modifiedPluginLayouts": [], - "modifiedPluginLayoutsCheckFailed": false + "modifiedPluginLayoutsCheckFailed": false, + "pluginRebuildSearchIndex": false } }, { diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/admin_user_list.json b/templates/_bundled/sirsoft-admin_basic/layouts/admin_user_list.json index 6d8de677..d10884bf 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/admin_user_list.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/admin_user_list.json @@ -49,7 +49,7 @@ }, "params": { "page": "{{query.page}}", - "per_page": "{{query.per_page}}", + "per_page": "{{query.per_page || '10'}}", "filters[0][field]": "{{query['filters[0][field]'] || 'all'}}", "filters[0][value]": "{{query['filters[0][value]']}}", "filters[0][operator]": "{{query['filters[0][operator]']}}", @@ -1036,10 +1036,13 @@ "params": { "path": "/admin/users", "mergeQuery": true, + "replace": true, "query": { "sort_by": "{{$args[0]}}", - "sort_order": "{{$args[1]}}" - } + "sort_order": "{{$args[1]}}", + "page": 1 + }, + "transition_overlay_target": "users_data_grid__body" } }, { diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_module_list/_modal_update.json b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_module_list/_modal_update.json index 622c7a22..c428f493 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_module_list/_modal_update.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_module_list/_modal_update.json @@ -428,6 +428,75 @@ }, "text": "$t:admin.modules.modals.layout_strategy_title" }, + { + "id": "module_rebuild_search_index", + "comment": "검색 인덱스 재생성 — 인덱스 잠금·재색인 비용이 있어 기본은 미선택. 운영 중인 사이트는 유지보수 시간에 수행한다.", + "type": "basic", + "name": "Div", + "props": { + "className": "mt-4 p-3 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg" + }, + "children": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" + }, + "text": "$t:extensions.update_modal.rebuild_search_index_title" + }, + { + "type": "basic", + "name": "Label", + "props": { + "className": "flex items-start gap-2 cursor-pointer select-none" + }, + "children": [ + { + "type": "basic", + "name": "Input", + "props": { + "type": "checkbox", + "checked": "{{_global.moduleRebuildSearchIndex === true}}", + "className": "w-4 h-4 mt-0.5 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400" + }, + "actions": [ + { + "type": "change", + "handler": "setState", + "params": { + "target": "global", + "moduleRebuildSearchIndex": "{{!_global.moduleRebuildSearchIndex}}" + } + } + ] + }, + { + "type": "basic", + "name": "Div", + "children": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-sm text-gray-700 dark:text-gray-300" + }, + "text": "$t:extensions.update_modal.rebuild_search_index_label" + }, + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-xs text-gray-500 dark:text-gray-400 mt-0.5" + }, + "text": "$t:extensions.update_modal.rebuild_search_index_desc" + } + ] + } + ] + } + ] + }, { "type": "basic", "name": "Div", @@ -717,7 +786,8 @@ "method": "POST", "body": { "layout_strategy": "{{_global.moduleLayoutStrategy ?? 'overwrite'}}", - "force": "{{_global.moduleForceUpdate === true}}" + "force": "{{_global.moduleForceUpdate === true}}", + "rebuild_search_index": "{{_global.moduleRebuildSearchIndex === true}}" } }, "onSuccess": [ @@ -736,7 +806,8 @@ "moduleLayoutStrategy": "overwrite", "hasModifiedModuleLayouts": false, "modifiedModuleLayoutsCount": 0, - "modifiedModuleLayouts": [] + "modifiedModuleLayouts": [], + "moduleRebuildSearchIndex": false } }, { diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_plugin_list/_modal_update.json b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_plugin_list/_modal_update.json index 733d45ce..e6ce07b8 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_plugin_list/_modal_update.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_plugin_list/_modal_update.json @@ -428,6 +428,75 @@ }, "text": "$t:admin.plugins.modals.layout_strategy_title" }, + { + "id": "plugin_rebuild_search_index", + "comment": "검색 인덱스 재생성 — 인덱스 잠금·재색인 비용이 있어 기본은 미선택. 운영 중인 사이트는 유지보수 시간에 수행한다.", + "type": "basic", + "name": "Div", + "props": { + "className": "mt-4 p-3 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg" + }, + "children": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" + }, + "text": "$t:extensions.update_modal.rebuild_search_index_title" + }, + { + "type": "basic", + "name": "Label", + "props": { + "className": "flex items-start gap-2 cursor-pointer select-none" + }, + "children": [ + { + "type": "basic", + "name": "Input", + "props": { + "type": "checkbox", + "checked": "{{_global.pluginRebuildSearchIndex === true}}", + "className": "w-4 h-4 mt-0.5 rounded border-gray-300 dark:border-gray-600 text-blue-600 dark:text-blue-400 focus:ring-blue-500 dark:focus:ring-blue-400" + }, + "actions": [ + { + "type": "change", + "handler": "setState", + "params": { + "target": "global", + "pluginRebuildSearchIndex": "{{!_global.pluginRebuildSearchIndex}}" + } + } + ] + }, + { + "type": "basic", + "name": "Div", + "children": [ + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-sm text-gray-700 dark:text-gray-300" + }, + "text": "$t:extensions.update_modal.rebuild_search_index_label" + }, + { + "type": "basic", + "name": "Div", + "props": { + "className": "text-xs text-gray-500 dark:text-gray-400 mt-0.5" + }, + "text": "$t:extensions.update_modal.rebuild_search_index_desc" + } + ] + } + ] + } + ] + }, { "type": "basic", "name": "Div", @@ -717,7 +786,8 @@ "method": "POST", "body": { "layout_strategy": "{{_global.pluginLayoutStrategy ?? 'overwrite'}}", - "force": "{{_global.pluginForceUpdate === true}}" + "force": "{{_global.pluginForceUpdate === true}}", + "rebuild_search_index": "{{_global.pluginRebuildSearchIndex === true}}" } }, "onSuccess": [ @@ -736,7 +806,8 @@ "pluginLayoutStrategy": "overwrite", "hasModifiedPluginLayouts": false, "modifiedPluginLayoutsCount": 0, - "modifiedPluginLayouts": [] + "modifiedPluginLayouts": [], + "pluginRebuildSearchIndex": false } }, { diff --git a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_schedule_list/_tab_schedules.json b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_schedule_list/_tab_schedules.json index 3fc77c43..84ee4895 100644 --- a/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_schedule_list/_tab_schedules.json +++ b/templates/_bundled/sirsoft-admin_basic/layouts/partials/admin_schedule_list/_tab_schedules.json @@ -1331,7 +1331,7 @@ "props": { "name": "per_page", "className": "w-20", - "value": "{{query.per_page || '10'}}", + "value": "{{query.per_page ?? '20'}}", "options": [ { "label": "$t:admin.schedules.per_page.10", diff --git a/templates/_bundled/sirsoft-admin_basic/src/components/composite/DataGrid.tsx b/templates/_bundled/sirsoft-admin_basic/src/components/composite/DataGrid.tsx index e06068d8..302799a6 100644 --- a/templates/_bundled/sirsoft-admin_basic/src/components/composite/DataGrid.tsx +++ b/templates/_bundled/sirsoft-admin_basic/src/components/composite/DataGrid.tsx @@ -660,20 +660,21 @@ export const DataGrid: React.FC = ({ // 정렬 상태 - useControllableState 적용 // 외부 제어(externalSortField)가 있으면 Controlled, 없으면 Uncontrolled + // + // 정렬은 "컬럼 + 방향" 한 쌍이 하나의 의미 단위다. 두 상태의 onChange 콜백에서 각각 + // onSortChange 를 부르면, 각 콜백이 자기 렌더의 클로저에 잡힌 **반대쪽 이전 값**을 함께 + // 실어보내 잘못된 조합이 디스패치된다 (setSortField 는 이전 방향을, setSortDirection 은 + // 이전 컬럼을 보낸다). 헤더 클릭 한 번에 두 콜백이 연달아 불리므로 나중 것이 이겨 + // "이름 desc → 이메일 헤더 클릭" 이 sort_by=name&sort_order=asc 로 나가고, 헤더 표시만 + // 이메일↑ 이 되어 표시와 실제 정렬 기준이 어긋난다. + // 따라서 상태 setter 는 화살표 표시용 낙관적 갱신만 담당하고, 서버 디스패치는 + // handleSort 에서 새 값 한 쌍으로 단 한 번 수행한다. const [sortField, setSortField] = useControllableState - ? useControllableState(externalSortField, null, (value: string | null) => { - if (value !== null) { - onSortChange?.(value, sortDirection); - } - }) + ? useControllableState(externalSortField, null) : [externalSortField ?? null, () => {}]; const [sortDirection, setSortDirection] = useControllableState - ? useControllableState<'asc' | 'desc'>(externalSortDirection, 'asc', (value: 'asc' | 'desc') => { - if (sortField !== null) { - onSortChange?.(sortField, value); - } - }) + ? useControllableState<'asc' | 'desc'>(externalSortDirection, 'asc') : [externalSortDirection ?? 'asc', () => {}]; const [currentPage, setCurrentPage] = useState(1); @@ -831,9 +832,20 @@ export const DataGrid: React.FC = ({ // 값 비교 시 항상 앞으로 밀어 서버 ORDER BY 순서를 훼손한다. // onSortChange 가 없는 경우(uncontrolled)에만 컴포넌트가 단독 정렬 주체로서 // 전달받은 로컬 데이터를 클라이언트에서 정렬한다. + // 헤더 클릭 정렬을 제공할 수 있는가. + // + // 서버 페이지네이션 목록에서 `onSortChange` 가 배선돼 있지 않으면 컴포넌트는 서버에 + // 정렬을 요청할 방법이 없다. 그런데도 헤더를 누를 수 있게 두면 **현재 페이지 배열만** + // 정렬되고 화살표가 붙어, 전체 건수 기준으로 정렬된 것으로 오인된다 (#492 D-18). + // 정렬을 실제로 반영할 수 없는 화면에서는 정렬 가능한 척하지 않는다. + const headerSortEnabled = sortable && !(serverSidePagination && !onSortChange); + const sortedData = useMemo(() => { if (!sortField || !sortable || !data) return data || []; if (onSortChange) return data; + // 서버 페이지네이션이면 data 는 "전체 중 이번 페이지" 다. 여기서 정렬하면 그 페이지 + // 안에서만 순서가 바뀌는데 헤더 화살표는 전체 정렬처럼 보인다 (#492 D-18). + if (serverSidePagination) return data; return [...data].sort((a, b) => { const aValue = a[sortField]; @@ -844,7 +856,7 @@ export const DataGrid: React.FC = ({ const comparison = aValue > bValue ? 1 : -1; return sortDirection === 'asc' ? comparison : -comparison; }); - }, [data, sortField, sortDirection, sortable, onSortChange]); + }, [data, sortField, sortDirection, sortable, onSortChange, serverSidePagination]); // 페이지네이션 처리 const paginatedData = useMemo(() => { @@ -872,21 +884,19 @@ export const DataGrid: React.FC = ({ // 정렬 토글 (Phase 2-2: useControllableState로 단순화) const handleSort = (field: string) => { - if (!sortable) return; + if (!headerSortEnabled) return; const column = columns.find((col) => col.field === field); if (column?.sortable === false) return; const newDirection: 'asc' | 'desc' = sortField === field && sortDirection === 'asc' ? 'desc' : 'asc'; - // useControllableState 사용 시: setter가 onChange 콜백 자동 호출 - // 폴백 시: 직접 onSortChange 호출 - if (useControllableState) { - setSortField(field); - setSortDirection(newDirection); - } else if (onSortChange) { - onSortChange(field, newDirection); - } + // 낙관적 갱신 — 헤더 화살표가 즉시 새 컬럼/방향을 가리키게 한다 + setSortField(field); + setSortDirection(newDirection); + + // 서버 디스패치는 새 값 한 쌍으로 정확히 한 번 (컬럼/방향이 섞이지 않는다) + onSortChange?.(field, newDirection); }; // 전체 선택 처리 @@ -1336,12 +1346,12 @@ export const DataGrid: React.FC = ({ handleSort(column.field)} + onClick={headerSortEnabled ? () => handleSort(column.field) : undefined} > {column.header} {sortField === column.field && ( diff --git a/templates/_bundled/sirsoft-admin_basic/src/components/composite/__tests__/DataGrid.test.tsx b/templates/_bundled/sirsoft-admin_basic/src/components/composite/__tests__/DataGrid.test.tsx index 37566ad1..a2c1e0f7 100644 --- a/templates/_bundled/sirsoft-admin_basic/src/components/composite/__tests__/DataGrid.test.tsx +++ b/templates/_bundled/sirsoft-admin_basic/src/components/composite/__tests__/DataGrid.test.tsx @@ -1,8 +1,58 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { DataGrid, DataGridColumn } from '../DataGrid'; +/** + * 런타임의 `G7Core.useControllableState` 와 같은 계약을 갖는 테스트용 구현. + * + * 코어 훅(resources/js/core/hooks/useControllableState.ts)을 직접 임포트하면 코어와 + * 템플릿이 각자의 node_modules/react 를 갖고 있어 React 사본이 둘이 되고 "Invalid hook + * call" 이 난다. vitest 설정에서 react 를 전역 alias 로 단일화하면 이 파일은 통과하지만 + * 다른 레이아웃 테스트 300여 건이 깨진다(실측 확인). + * + * 이 테스트가 지키려는 것은 훅 내부 동작이 아니라 **DataGrid 가 정렬을 디스패치하는 + * 방식** 이므로, 훅은 테스트의 React 로 같은 계약(낙관적 내부 상태 + onChange 콜백)만 + * 재현하면 충분하다. + */ +function useControllableState( + controlledValue: T | undefined, + defaultValue: T, + onChange?: (value: T) => void +): [T, (value: T | ((prev: T) => T)) => void] { + const [internal, setInternal] = useState( + controlledValue !== undefined ? controlledValue : defaultValue + ); + const prevControlled = useRef(controlledValue); + + useEffect(() => { + if (controlledValue !== undefined && controlledValue !== prevControlled.current) { + setInternal(controlledValue); + } + prevControlled.current = controlledValue; + }, [controlledValue]); + + const setValue = useCallback( + (next: T | ((prev: T) => T)) => { + const resolved = typeof next === 'function' ? (next as (prev: T) => T)(internal) : next; + if (resolved === internal) return; + setInternal(resolved); + onChange?.(resolved); + }, + [internal, onChange] + ); + + return [internal, setValue]; +} + +/** shallowArrayEqual 의 테스트용 동등 구현 */ +function shallowArrayEqual(a: T[], b: T[]): boolean { + if (a === b) return true; + if (a.length !== b.length) return false; + return a.every((item, index) => item === b[index]); +} + // window.G7Core.useResponsive mock const mockUseResponsive = vi.fn(() => ({ width: 1024, @@ -298,6 +348,115 @@ describe('DataGrid', () => { expect(container.firstChild).toHaveClass('custom-grid'); }); + describe('서버 페이지네이션 목록의 헤더 정렬 (#492 D-18)', () => { + // 서버 페이지네이션이면 data 는 "전체 중 이번 페이지" 다. onSortChange 가 없으면 + // 컴포넌트는 서버에 정렬을 요청할 수단이 없는데, 그 상태로 헤더를 누를 수 있게 두면 + // 이 페이지 안에서만 순서가 바뀌고 화살표가 붙어 전체 정렬로 오인된다. + // 클릭이 실제로 정렬 상태를 바꾸는지 보려면 런타임과 같은 controllable-state 훅이 + // 있어야 한다. 훅을 주입하지 않으면 클릭해도 상태가 안 바뀌어, 결함이 있어도 + // "행 순서 그대로 / 화살표 없음" 이 성립해 테스트가 공허하게 통과한다. + describe('G7Core.useControllableState 주입 시 (런타임 경로)', () => { + beforeEach(() => { + (window as any).G7Core = { + ...(window as any).G7Core, + useControllableState, + shallowArrayEqual, + }; + }); + + it('onSortChange 가 없으면 헤더 클릭이 현재 페이지를 정렬하지 않는다', async () => { + const user = userEvent.setup(); + + render( + + ); + + const before = screen.getAllByRole('row').slice(1).map((r) => r.textContent); + + await user.click(screen.getByText('이름')); + + const after = screen.getAllByRole('row').slice(1).map((r) => r.textContent); + + expect(after).toEqual(before); + }); + + it('onSortChange 가 없으면 헤더에 정렬 화살표가 붙지 않는다', async () => { + const user = userEvent.setup(); + + render( + + ); + + await user.click(screen.getByText('이름')); + + expect(screen.getByText('이름').parentElement).not.toContainHTML('↑'); + expect(screen.getByText('이름').parentElement).not.toContainHTML('↓'); + }); + }); + + it('onSortChange 가 없으면 헤더에 클릭 가능 커서를 표시하지 않는다', () => { + render( + + ); + + expect(screen.getByText('이름').className).not.toContain('cursor-pointer'); + }); + + it('onSortChange 가 배선돼 있으면 서버 정렬을 요청한다 (정상 경로는 유지)', async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + + render( + + ); + + expect(screen.getByText('이름').className).toContain('cursor-pointer'); + + await user.click(screen.getByText('이름')); + + expect(onSortChange).toHaveBeenCalledWith('name', 'asc'); + }); + + // 가드가 과하게 걸려 클라이언트 목록의 정렬까지 죽이면 안 된다. + // 서버 페이지네이션이 아니면 data 가 전체 집합이므로 컴포넌트 단독 정렬이 옳다. + it('서버 페이지네이션이 아니면 헤더 정렬 어포던스를 유지한다 (클라이언트 목록)', () => { + render(); + + expect(screen.getByText('이름').className).toContain('cursor-pointer'); + // sortable: false 인 컬럼은 원래대로 정렬 대상이 아니다 + expect(screen.getByText('역할').className).not.toContain('cursor-pointer'); + }); + }); + describe('외부 정렬 제어 (sortField, sortDirection, onSortChange)', () => { it('외부에서 sortField와 sortDirection을 제어할 수 있음', () => { render( @@ -416,6 +575,82 @@ describe('DataGrid', () => { expect(screen.getByText('역할')).toBeInTheDocument(); }); + // 회귀 가드 (#492 브라우저 실측): 런타임에는 G7Core.useControllableState 가 존재하므로 + // handleSort 가 폴백(onSortChange 직접 호출) 대신 controllable-state 경로를 탄다. + // 이 훅을 주입하지 않은 테스트는 폴백만 검증하게 되어 런타임 결함을 통과시킨다. + // (실측: 이름 desc 상태에서 이메일 헤더 클릭 → 헤더는 `이메일↑`, 요청은 sort_by=name&sort_order=asc) + describe('G7Core.useControllableState 주입 시 (런타임 경로)', () => { + beforeEach(() => { + (window as any).G7Core = { + ...(window as any).G7Core, + useControllableState, + shallowArrayEqual, + }; + }); + + it('다른 컬럼 헤더 클릭 시 새 컬럼 + asc 로 단 한 번만 디스패치된다', async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + + render( + + ); + + await user.click(screen.getByText('이메일')); + + // 이전 컬럼(name)이 섞인 디스패치가 있어서는 안 된다 + expect(onSortChange).toHaveBeenCalledTimes(1); + expect(onSortChange).toHaveBeenCalledWith('email', 'asc'); + }); + + it('동일 컬럼 재클릭 시 같은 컬럼 + 반대 방향으로 단 한 번만 디스패치된다', async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + + render( + + ); + + await user.click(screen.getByText('이름')); + + expect(onSortChange).toHaveBeenCalledTimes(1); + expect(onSortChange).toHaveBeenCalledWith('name', 'desc'); + }); + + it('정렬되지 않은 상태에서 헤더 클릭 시 해당 컬럼 asc 로 디스패치된다', async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + + render( + + ); + + await user.click(screen.getByText('이메일')); + + expect(onSortChange).toHaveBeenCalledTimes(1); + expect(onSortChange).toHaveBeenCalledWith('email', 'asc'); + }); + }); + it('sortable이 false인 컬럼은 onSortChange가 호출되지 않음', async () => { const user = userEvent.setup(); const onSortChange = vi.fn(); diff --git a/templates/_bundled/sirsoft-admin_basic/src/styles/main.css b/templates/_bundled/sirsoft-admin_basic/src/styles/main.css index ad864cac..683a72fb 100644 --- a/templates/_bundled/sirsoft-admin_basic/src/styles/main.css +++ b/templates/_bundled/sirsoft-admin_basic/src/styles/main.css @@ -19,6 +19,12 @@ @source inline("bg-blue-600 bg-gray-100 bg-gray-50 bg-gray-800 bg-gray-900 bg-red-600 bg-white block border border-0 border-blue-600 border-dashed border-dotted border-gray-300 border-gray-500 border-gray-700 border-gray-900 border-green-600 border-red-600 border-solid border-white dark:bg-amber-500 dark:bg-amber-900 dark:bg-amber-900/20 dark:bg-amber-900/30 dark:bg-amber-900/40 dark:bg-blue-400 dark:bg-blue-500 dark:bg-blue-600 dark:bg-blue-800 dark:bg-blue-900 dark:bg-blue-900/20 dark:bg-blue-900/30 dark:bg-blue-900/40 dark:bg-blue-900/50 dark:bg-emerald-400 dark:bg-gray-100 dark:bg-gray-50 dark:bg-gray-600 dark:bg-gray-700 dark:bg-gray-700/30 dark:bg-gray-700/50 dark:bg-gray-800 dark:bg-gray-800/50 dark:bg-gray-900 dark:bg-gray-900/50 dark:bg-gray-950 dark:bg-green-900 dark:bg-green-900/20 dark:bg-green-900/30 dark:bg-indigo-500 dark:bg-indigo-900 dark:bg-indigo-900/30 dark:bg-orange-900 dark:bg-purple-800/50 dark:bg-purple-900 dark:bg-purple-900/20 dark:bg-purple-900/30 dark:bg-purple-900/50 dark:bg-red-500 dark:bg-red-600 dark:bg-red-700 dark:bg-red-900 dark:bg-red-900/20 dark:bg-red-900/30 dark:bg-rose-400 dark:bg-slate-300 dark:bg-slate-700 dark:bg-slate-800 dark:bg-white dark:bg-yellow-700 dark:bg-yellow-900 dark:bg-yellow-900/30 dark:bg-yellow-900/40 dark:block dark:border dark:border-0 dark:border-amber-400 dark:border-amber-600 dark:border-amber-700 dark:border-amber-800 dark:border-blue-600 dark:border-blue-700 dark:border-blue-800 dark:border-dashed dark:border-dotted dark:border-gray-300 dark:border-gray-500 dark:border-gray-600 dark:border-gray-700 dark:border-gray-900 dark:border-green-600 dark:border-green-700 dark:border-green-800 dark:border-indigo-400 dark:border-orange-600 dark:border-purple-600 dark:border-purple-700 dark:border-red-500 dark:border-red-600 dark:border-red-700 dark:border-red-800 dark:border-solid dark:border-white dark:border-yellow-600 dark:disabled:hover:bg-indigo-500 dark:disabled:text-gray-600 dark:divide-purple-800 dark:flex dark:flex-1 dark:flex-auto dark:flex-col dark:flex-none dark:flex-row dark:flex-wrap dark:focus:ring-amber-400 dark:focus:ring-blue-400 dark:focus:ring-indigo-400 dark:focus:ring-offset-gray-900 dark:focus:ring-slate-500 dark:font-bold dark:font-medium dark:font-normal dark:from-blue-900 dark:hover:bg-amber-600 dark:hover:bg-blue-600 dark:hover:bg-gray-100 dark:hover:bg-gray-200 dark:hover:bg-gray-500 dark:hover:bg-gray-600 dark:hover:bg-gray-700 dark:hover:bg-gray-700/30 dark:hover:bg-indigo-600 dark:hover:bg-indigo-900/30 dark:hover:bg-orange-900/30 dark:hover:bg-purple-900/30 dark:hover:bg-red-600 dark:hover:bg-red-800 dark:hover:bg-red-900/20 dark:hover:bg-red-900/30 dark:hover:bg-slate-200 dark:hover:bg-slate-600 dark:hover:bg-yellow-800 dark:hover:text-blue-300 dark:hover:text-blue-400 dark:hover:text-gray-200 dark:hover:text-gray-300 dark:hover:text-purple-200 dark:hover:text-purple-300 dark:hover:text-red-400 dark:hover:text-slate-800 dark:hover:text-white dark:italic dark:items-center dark:items-end dark:items-start dark:items-stretch dark:justify-around dark:justify-between dark:justify-center dark:justify-end dark:justify-start dark:opacity-0 dark:opacity-100 dark:opacity-25 dark:opacity-50 dark:opacity-75 dark:overflow-auto dark:overflow-hidden dark:overflow-visible dark:overflow-x-auto dark:overflow-y-auto dark:placeholder-gray-500 dark:prose-invert dark:rounded dark:rounded-full dark:rounded-lg dark:rounded-none dark:rounded-sm dark:rounded-xl dark:self-center dark:self-end dark:self-start dark:self-stretch dark:shadow dark:shadow-2xl dark:shadow-lg dark:shadow-none dark:shadow-purple-900/50 dark:shadow-sm dark:shadow-xl dark:text-2xl dark:text-amber-200 dark:text-amber-300 dark:text-amber-400 dark:text-base dark:text-blue-100 dark:text-blue-200 dark:text-blue-300 dark:text-blue-400 dark:text-blue-600 dark:text-center dark:text-gray-100 dark:text-gray-200 dark:text-gray-300 dark:text-gray-400 dark:text-gray-500 dark:text-gray-600 dark:text-gray-700 dark:text-gray-900 dark:text-green-100 dark:text-green-200 dark:text-green-300 dark:text-green-400 dark:text-green-600 dark:text-indigo-200 dark:text-indigo-300 dark:text-indigo-400 dark:text-justify dark:text-left dark:text-lg dark:text-orange-200 dark:text-orange-400 dark:text-orange-500 dark:text-purple-100 dark:text-purple-200 dark:text-purple-300 dark:text-purple-400 dark:text-red-200 dark:text-red-300 dark:text-red-400 dark:text-red-500 dark:text-red-600 dark:text-right dark:text-slate-300 dark:text-slate-400 dark:text-sm dark:text-white dark:text-xl dark:text-xs dark:text-yellow-100 dark:text-yellow-200 dark:text-yellow-300 dark:text-yellow-400 dark:text-yellow-500 dark:to-indigo-900 dark:underline dark:whitespace-normal dark:whitespace-nowrap dark:whitespace-pre-wrap disabled:bg-blue-400 disabled:bg-gray-100 disabled:bg-gray-400 disabled:cursor-not-allowed disabled:hover:bg-indigo-600 disabled:hover:bg-transparent disabled:no-underline disabled:opacity-50 disabled:text-gray-400 flex flex-1 flex-auto flex-col flex-none flex-row flex-wrap focus:border-blue-500 focus:border-indigo-500 focus:border-red-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-blue-500 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-red-500 focus:ring-slate-400 font-bold font-medium font-normal hover:-translate-y-0.5 hover:bg-amber-50 hover:bg-amber-700 hover:bg-blue-700 hover:bg-gray-100 hover:bg-gray-200 hover:bg-gray-300 hover:bg-gray-50 hover:bg-gray-800 hover:bg-gray-900 hover:bg-green-700 hover:bg-indigo-50 hover:bg-indigo-700 hover:bg-orange-50 hover:bg-orange-700 hover:bg-purple-50 hover:bg-purple-700 hover:bg-red-100 hover:bg-red-50 hover:bg-red-700 hover:bg-slate-50 hover:bg-slate-700 hover:bg-yellow-700 hover:no-underline hover:text-blue-500 hover:text-blue-600 hover:text-gray-600 hover:text-gray-700 hover:text-gray-900 hover:text-purple-800 hover:text-purple-900 hover:text-red-500 hover:text-white hover:underline italic items-center items-end items-start items-stretch justify-around justify-between justify-center justify-end justify-start lg:grid-cols-2 lg:grid-cols-3 lg:mb-8 lg:p-8 lg:px-8 max-h-[400px] max-h-[500px] max-h-[70vh] max-w-[200px] md: md:col-span-2 md:flex-1 md:flex-row md:gap-2 md:gap-3 md:grid-cols-2 md:grid-cols-3 md:grid-cols-4 md:h-20 md:items-center md:justify-between md:mb-6 md:p-6 md:rounded-2xl md:space-y-0 md:text-xl md:text-xs md:w-20 md:w-40 md:w-64 md:w-72 md:w-80 md:w-auto min-h-[200px] min-h-[60vh] min-w-[200px] min-w-[80px] opacity-0 opacity-100 opacity-25 opacity-50 opacity-75 overflow-auto overflow-hidden overflow-visible overflow-x-auto overflow-y-auto rounded rounded-full rounded-lg rounded-none rounded-sm rounded-xl self-center self-end self-start self-stretch shadow shadow-2xl shadow-lg shadow-none shadow-sm shadow-xl sm:flex-row sm:gap-6 sm:grid-cols-2 sm:inline sm:items-start sm:justify-between sm:mt-6 sm:p-6 sm:px-6 sm:text-2xl sm:text-3xl sm:w-40 text-2xl text-[10px] text-[11px] text-base text-blue-600 text-center text-gray-500 text-gray-700 text-gray-900 text-green-600 text-justify text-left text-lg text-red-600 text-right text-sm text-white text-xl text-xs underline whitespace-normal whitespace-nowrap whitespace-pre-wrap"); /* SAFELIST-END */ +/** + * Safelist (자동 생성) +@source inline("bg-blue-600 bg-gray-100 bg-gray-50 bg-gray-800 bg-gray-900 bg-red-600 bg-white block border border-0 border-blue-600 border-dashed border-dotted border-gray-300 border-gray-500 border-gray-700 border-gray-900 border-green-600 border-red-600 border-solid border-white dark:bg-amber-500 dark:bg-amber-900 dark:bg-amber-900/20 dark:bg-amber-900/30 dark:bg-amber-900/40 dark:bg-blue-500 dark:bg-blue-600 dark:bg-blue-800 dark:bg-blue-900 dark:bg-blue-900/20 dark:bg-blue-900/30 dark:bg-blue-900/40 dark:bg-blue-900/50 dark:bg-blue-900text-info-soft dark:bg-gray-100 dark:bg-gray-50 dark:bg-gray-600 dark:bg-gray-700 dark:bg-gray-700/30 dark:bg-gray-700/50 dark:bg-gray-800 dark:bg-gray-800/50 dark:bg-gray-800text-warning-soft dark:bg-gray-900 dark:bg-gray-900/50 dark:bg-gray-950 dark:bg-green-900 dark:bg-green-900/20 dark:bg-green-900/30 dark:bg-indigo-500 dark:bg-indigo-900 dark:bg-indigo-900/30 dark:bg-orange-900 dark:bg-orange-900/20 dark:bg-purple-800/50 dark:bg-purple-900 dark:bg-purple-900/20 dark:bg-purple-900/30 dark:bg-purple-900/50 dark:bg-red-600 dark:bg-red-700 dark:bg-red-900 dark:bg-red-900/20 dark:bg-red-900/30 dark:bg-slate-700 dark:bg-slate-800 dark:bg-white dark:bg-yellow-900 dark:bg-yellow-900/20 dark:bg-yellow-900/30 dark:bg-yellow-900/40 dark:bg-yellow-900text-warning-soft dark:block dark:border dark:border-0 dark:border-amber-400 dark:border-amber-600 dark:border-amber-700 dark:border-amber-800 dark:border-blue-600 dark:border-blue-700 dark:border-blue-800 dark:border-dashed dark:border-dotted dark:border-gray-300 dark:border-gray-500 dark:border-gray-600 dark:border-gray-700 dark:border-gray-900 dark:border-green-600 dark:border-green-700 dark:border-green-800 dark:border-indigo-400 dark:border-orange-600 dark:border-orange-800 dark:border-purple-600 dark:border-purple-700 dark:border-red-500 dark:border-red-600 dark:border-red-700 dark:border-red-800 dark:border-solid dark:border-white dark:border-yellow-600 dark:border-yellow-800 dark:disabled:bg-gray-800 dark:disabled:hover:bg-indigo-500 dark:disabled:text-gray-500 dark:disabled:text-gray-600 dark:divide-gray-700 dark:divide-purple-800 dark:divide-slate-700 dark:flex dark:flex-1 dark:flex-auto dark:flex-col dark:flex-none dark:flex-row dark:flex-wrap dark:focus:ring-amber-400 dark:focus:ring-blue-400 dark:focus:ring-indigo-400 dark:focus:ring-offset-gray-900 dark:focus:ring-slate-500 dark:font-bold dark:font-medium dark:font-normal dark:from-blue-900 dark:hover:bg-amber-600 dark:hover:bg-blue-600 dark:hover:bg-gray-100 dark:hover:bg-gray-200 dark:hover:bg-gray-500 dark:hover:bg-gray-600 dark:hover:bg-gray-700 dark:hover:bg-gray-700/30 dark:hover:bg-gray-800 dark:hover:bg-indigo-600 dark:hover:bg-indigo-900/30 dark:hover:bg-orange-900/30 dark:hover:bg-purple-900/30 dark:hover:bg-red-800 dark:hover:bg-red-900/20 dark:hover:bg-red-900/30 dark:hover:bg-slate-200 dark:hover:bg-slate-700 dark:hover:text-blue-300 dark:hover:text-blue-400 dark:hover:text-gray-200 dark:hover:text-gray-300 dark:hover:text-purple-200 dark:hover:text-purple-300 dark:hover:text-red-400 dark:hover:text-slate-300 dark:hover:text-slate-800 dark:hover:text-white dark:italic dark:items-center dark:items-end dark:items-start dark:items-stretch dark:justify-around dark:justify-between dark:justify-center dark:justify-end dark:justify-start dark:opacity-0 dark:opacity-100 dark:opacity-25 dark:opacity-50 dark:opacity-75 dark:overflow-auto dark:overflow-hidden dark:overflow-visible dark:overflow-x-auto dark:overflow-y-auto dark:placeholder-gray-500 dark:prose-invert dark:rounded dark:rounded-full dark:rounded-lg dark:rounded-none dark:rounded-sm dark:rounded-xl dark:self-center dark:self-end dark:self-start dark:self-stretch dark:shadow dark:shadow-2xl dark:shadow-lg dark:shadow-none dark:shadow-purple-900/50 dark:shadow-sm dark:shadow-xl dark:text-2xl dark:text-amber-200 dark:text-amber-300 dark:text-amber-400 dark:text-base dark:text-blue-100 dark:text-blue-200 dark:text-blue-300 dark:text-blue-400 dark:text-blue-600 dark:text-center dark:text-emerald-400 dark:text-gray-100 dark:text-gray-200 dark:text-gray-300 dark:text-gray-400 dark:text-gray-500 dark:text-gray-600 dark:text-gray-700 dark:text-gray-900 dark:text-green-100 dark:text-green-200 dark:text-green-300 dark:text-green-400 dark:text-green-600 dark:text-indigo-200 dark:text-indigo-300 dark:text-indigo-400 dark:text-justify dark:text-left dark:text-lg dark:text-orange-200 dark:text-orange-400 dark:text-orange-500 dark:text-purple-100 dark:text-purple-200 dark:text-purple-300 dark:text-purple-400 dark:text-red-200 dark:text-red-300 dark:text-red-400 dark:text-red-500 dark:text-red-600 dark:text-right dark:text-slate-200 dark:text-slate-300 dark:text-slate-400 dark:text-sm dark:text-white dark:text-xl dark:text-xs dark:text-yellow-100 dark:text-yellow-200 dark:text-yellow-300 dark:text-yellow-400 dark:text-yellow-500 dark:to-indigo-900 dark:underline dark:whitespace-normal dark:whitespace-nowrap dark:whitespace-pre-wrap disabled:bg-blue-400 disabled:bg-gray-100 disabled:bg-gray-400 disabled:cursor-not-allowed disabled:hover:bg-indigo-600 disabled:hover:bg-transparent disabled:no-underline disabled:opacity-50 disabled:text-gray-400 flex flex-1 flex-auto flex-col flex-none flex-row flex-wrap focus:border-blue-500 focus:border-indigo-500 focus:border-red-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-blue-500 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-red-500 focus:ring-slate-400 font-bold font-medium font-normal hover:-translate-y-0.5 hover:bg-amber-50 hover:bg-amber-700 hover:bg-blue-700 hover:bg-gray-100 hover:bg-gray-200 hover:bg-gray-300 hover:bg-gray-50 hover:bg-gray-700 hover:bg-gray-800 hover:bg-gray-900 hover:bg-green-700 hover:bg-indigo-50 hover:bg-indigo-700 hover:bg-orange-50 hover:bg-orange-700 hover:bg-purple-50 hover:bg-purple-700 hover:bg-red-100 hover:bg-red-50 hover:bg-red-700 hover:bg-slate-100 hover:bg-slate-700 hover:bg-yellow-700 hover:text-blue-500 hover:text-blue-600 hover:text-gray-600 hover:text-gray-700 hover:text-gray-900 hover:text-purple-800 hover:text-purple-900 hover:text-red-500 hover:text-slate-700 hover:text-slate-900 hover:text-white hover:underline italic items-center items-end items-start items-stretch justify-around justify-between justify-center justify-end justify-start lg:col-span-4 lg:gap-5 lg:grid-cols-2 lg:grid-cols-3 lg:grid-cols-4 lg:mb-8 lg:p-8 lg:px-8 max-h-[400px] max-h-[500px] max-h-[70vh] max-w-[200px] md: md:block md:col-span-2 md:flex-1 md:flex-row md:gap-0 md:gap-2 md:gap-3 md:gap-4 md:gap-8 md:grid-cols-2 md:grid-cols-3 md:grid-cols-4 md:h-20 md:h-36 md:items-center md:justify-between md:mb-2 md:mb-6 md:p-5 md:p-6 md:rounded-2xl md:space-y-0 md:text-2xl md:text-[2rem] md:text-xl md:text-xs md:w-20 md:w-40 md:w-48 md:w-64 md:w-72 md:w-80 md:w-auto min-h-[200px] min-h-[60vh] min-w-[200px] min-w-[80px] opacity-0 opacity-100 opacity-25 opacity-50 opacity-75 overflow-auto overflow-hidden overflow-visible overflow-x-auto overflow-y-auto rounded rounded-full rounded-lg rounded-none rounded-sm rounded-xl self-center self-end self-start self-stretch shadow shadow-2xl shadow-lg shadow-none shadow-sm shadow-xl sm:flex-row sm:gap-6 sm:grid-cols-2 sm:inline sm:items-start sm:justify-between sm:mt-6 sm:p-6 sm:px-6 sm:text-2xl sm:text-3xl sm:w-40 text-2xl text-[10px] text-[11px] text-base text-blue-600 text-center text-gray-500 text-gray-700 text-gray-900 text-green-600 text-justify text-left text-lg text-red-600 text-right text-sm text-white text-xl text-xs underline whitespace-normal whitespace-nowrap whitespace-pre-wrap"); + * 빌드 시 자동 실행: npm run build → prebuild → main.css 업데이트 + */ + /* ======================================== * BASE STYLES - 기본 글자 크기 설정 * ======================================== */ diff --git a/templates/_bundled/sirsoft-basic/CHANGELOG.md b/templates/_bundled/sirsoft-basic/CHANGELOG.md index e62aea59..fd902730 100644 --- a/templates/_bundled/sirsoft-basic/CHANGELOG.md +++ b/templates/_bundled/sirsoft-basic/CHANGELOG.md @@ -24,8 +24,11 @@ - 상품 목록에서 상품을 열고 상세의 탭(상세정보·리뷰·문의)을 누르면 보고 있던 페이지·정렬·검색어·분류가 주소에서 사라져, 이어서 '상품 목록'을 눌러도 1페이지로 되돌아가던 문제를 수정했습니다. 이제 탭을 오가도 목록 상태가 그대로 유지됩니다. (#75 @jiwonpapa 님께서 제보해주셨습니다.) #### 장바구니·오류 안내 +- 마이페이지 알림 화면의 "읽지 않은 알림 N건" 이 실제 개수와 무관하게 항상 0건으로 표시되던 문제를 수정했습니다. 헤더 알림 아이콘의 개수와 동일하게 표시됩니다. +- 상품 상세의 쿠폰 「+N」을 눌러 열리는 목록에 이 상품에 쓸 수 없는 쿠폰까지 섞여 나오던 문제를 수정했습니다. 이제 「+N」이 가리키는 개수와 목록의 개수가 일치합니다. - 상품 상세에서 옵션을 선택해 장바구니에 담거나 바로 구매할 때, 표시 언어(로케일)나 관리자의 옵션 이름 수정과 무관하게 선택한 옵션이 정확히 담기도록 개선했습니다. 특정 상황에서 "해당 옵션 조합을 찾을 수 없습니다" 오류로 담기가 실패하던 문제가 해소됩니다. +- 주문서의 "배송지 관리"에서 해외 배송지의 주소가 표시되지 않고 빈 괄호만 보이던 문제를 수정했습니다. 국내 주소 형식으로만 표시하고 있어 해외 주소가 비어 보였으며, 이제 마이페이지 배송지 목록과 동일하게 국가에 맞는 형식으로 표시됩니다. - 저장이나 삭제가 실패했을 때 아무 안내 없이 조용히 넘어가던 화면들을 수정했습니다. 이제 실패하면 그 이유가 화면에 표시되므로, 반영되지 않은 변경을 성공한 것으로 오해하지 않습니다. - 오류 안내가 뜨기는 하지만 내용이 비어 있던 문제를 수정했습니다. 서버가 알려 준 실패 사유가 그대로 표시됩니다. - 주문서의 무통장입금·가상계좌 입금 기한 안내가 관리자가 설정한 미입금 자동취소 기한과 다르게 표시되던 문제를 수정했습니다. 설정과 무관하게 각각 7일·3일로 고정 안내되어, 자동취소 기한을 3일로 둔 사이트에서 무통장입금 구매자는 "7일 이내"라는 안내를 받고도 3일 뒤 주문이 취소됐습니다. 이제 두 결제수단 모두 설정한 기한을 그대로 안내합니다. (#81 @jiwonpapa 님께서 제보해주셨습니다.) diff --git a/templates/_bundled/sirsoft-basic/__tests__/layouts/mypage-notifications-unread-binding.test.ts b/templates/_bundled/sirsoft-basic/__tests__/layouts/mypage-notifications-unread-binding.test.ts new file mode 100644 index 00000000..30d24c69 --- /dev/null +++ b/templates/_bundled/sirsoft-basic/__tests__/layouts/mypage-notifications-unread-binding.test.ts @@ -0,0 +1,83 @@ +/** + * 마이페이지 알림 화면의 미읽음 건수 바인딩 회귀 (#492 6차 브라우저 실측) + * + * "읽지 않은 알림 N건" 라벨이 목록 데이터소스(`userNotifications`)를 참조하고 있어 + * 항상 0 으로 표시됐다. 목록 응답에는 `unread_count` 가 없고, 그 값은 별도 데이터소스 + * `notification_unread_count`(`/api/user/notifications/unread-count`) 가 가져온다. + * + * 같은 화면의 헤더 벨은 올바른 데이터소스를 써서 정확한 값을 보여주고 있었으므로, + * 두 표시가 어긋나는 것이 결함의 신호였다(실측: 벨 7 · API 7 · 페이지 라벨 0). + * + * 브라우저로 잡으려면 미읽음이 1건 이상인 계정이 필요해 환경 의존이 크다. + * 바인딩 경로 자체는 레이아웃 선언이므로 여기서 정적으로 고정한다. + */ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const templateRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); + +/** + * 레이아웃 JSON 을 읽어 파싱합니다. + * + * @param relativePath 템플릿 루트 기준 상대 경로 + * @returns 파싱된 레이아웃 객체 + */ +function readLayout(relativePath: string): unknown { + return JSON.parse(readFileSync(resolve(templateRoot, relativePath), 'utf-8')); +} + +/** + * 객체 트리에서 주어진 정규식과 일치하는 문자열을 모두 수집합니다. + * + * @param node 순회할 노드 + * @param pattern 찾을 패턴 + * @param out 누적 배열 + * @returns 수집된 문자열 배열 + */ +function collectStrings(node: unknown, pattern: RegExp, out: string[] = []): string[] { + if (typeof node === 'string') { + if (pattern.test(node)) out.push(node); + + return out; + } + if (Array.isArray(node)) { + for (const item of node) collectStrings(item, pattern, out); + + return out; + } + if (node && typeof node === 'object') { + for (const value of Object.values(node)) collectStrings(value, pattern, out); + } + + return out; +} + +describe('마이페이지 알림 — 미읽음 건수 바인딩', () => { + const listPartial = readLayout('layouts/partials/mypage/notifications/_list.json'); + const baseLayout = readLayout('layouts/_user_base.json'); + + it('미읽음 건수 라벨은 전용 데이터소스를 참조한다', () => { + const bindings = collectStrings(listPartial, /unread_count/); + + expect(bindings.length, 'unread_count 바인딩을 찾지 못했습니다').toBeGreaterThan(0); + + for (const binding of bindings) { + expect( + binding, + '미읽음 건수를 목록 데이터소스에서 읽고 있습니다 — 목록 응답에는 unread_count 가 없어 항상 0 이 됩니다' + ).not.toMatch(/userNotifications[?.]*\.?data\??\.unread_count/); + expect(binding).toContain('notification_unread_count'); + } + }); + + it('참조하는 데이터소스가 베이스 레이아웃에 실제로 선언되어 있다', () => { + const sources = (baseLayout as { data_sources?: Array<{ id?: string; endpoint?: string }> }) + .data_sources ?? []; + const target = sources.find((source) => source.id === 'notification_unread_count'); + + expect(target, 'notification_unread_count 데이터소스 선언이 없습니다').toBeDefined(); + expect(target?.endpoint).toContain('/api/user/notifications/unread-count'); + }); +}); diff --git a/templates/_bundled/sirsoft-basic/layouts/partials/mypage/notifications/_list.json b/templates/_bundled/sirsoft-basic/layouts/partials/mypage/notifications/_list.json index 42e2cda9..527022c6 100644 --- a/templates/_bundled/sirsoft-basic/layouts/partials/mypage/notifications/_list.json +++ b/templates/_bundled/sirsoft-basic/layouts/partials/mypage/notifications/_list.json @@ -42,7 +42,7 @@ "type": "basic", "name": "Span", "props": { "className": "text-sm text-red-500 dark:text-red-400 font-medium" }, - "text": "$t:mypage.notifications.unread_count|count={{userNotifications.data?.unread_count ?? 0}}" + "text": "$t:mypage.notifications.unread_count|count={{notification_unread_count.data?.unread_count ?? 0}}" }, { "comment": "안 읽은 알림만 필터 토글", diff --git a/templates/_bundled/sirsoft-basic/layouts/partials/shop/_modal_address_manage.json b/templates/_bundled/sirsoft-basic/layouts/partials/shop/_modal_address_manage.json index 8814fffd..9571b29c 100644 --- a/templates/_bundled/sirsoft-basic/layouts/partials/shop/_modal_address_manage.json +++ b/templates/_bundled/sirsoft-basic/layouts/partials/shop/_modal_address_manage.json @@ -198,9 +198,10 @@ ] }, { - "comment": "주소", + "comment": "주소 (국내: 우편번호 + 주소 + 상세주소)", "type": "basic", "name": "Div", + "if": "{{(addr.country_code ?? 'KR') === 'KR'}}", "props": { "className": "text-sm text-gray-600 dark:text-gray-400" }, @@ -219,6 +220,22 @@ "text": "{{addr.address}} {{addr.address_detail ?? ''}}" } ] + }, + { + "comment": "주소 (해외: 서버가 조립한 full_address — 국내 전용 형식으로 찍으면 '()' 만 보인다. 마이페이지 배송지 목록과 동일 규칙.)", + "type": "basic", + "name": "Div", + "if": "{{(addr.country_code ?? 'KR') !== 'KR'}}", + "props": { + "className": "text-sm text-gray-600 dark:text-gray-400 break-words" + }, + "children": [ + { + "type": "basic", + "name": "Span", + "text": "{{addr.full_address ?? ''}}" + } + ] } ] }, diff --git a/templates/_bundled/sirsoft-basic/layouts/partials/shop/detail/_info_summary.json b/templates/_bundled/sirsoft-basic/layouts/partials/shop/detail/_info_summary.json index f075e51c..b899ea31 100644 --- a/templates/_bundled/sirsoft-basic/layouts/partials/shop/detail/_info_summary.json +++ b/templates/_bundled/sirsoft-basic/layouts/partials/shop/detail/_info_summary.json @@ -173,45 +173,18 @@ "if": "{{_global.currentUser?.uuid}}", "then": { "handler": "sequence", + "comment": "「+N」 배지는 이 상품에 적용되는 쿠폰 수(productDownloadableCoupons)로 계산되므로, 모달도 같은 목록을 보여준다. 전체 다운로드 쿠폰을 다시 조회하면 이 상품에 적용되지 않는 쿠폰이 섞여 배지 수와 모달 건수가 어긋난다.", "actions": [ { "handler": "setState", "params": { "target": "local", - "downloadableCouponsLoading": true + "downloadableCoupons": "{{productDownloadableCoupons.data}}", + "downloadableCouponsLoading": false, + "downloadingMore": false } }, - { - "handler": "apiCall", - "target": "/api/modules/sirsoft-ecommerce/user/coupons/downloadable?per_page=8&page=1", - "auth_mode": "required", - "params": { "method": "GET" }, - "onSuccess": [ - { - "handler": "setState", - "params": { - "target": "local", - "downloadableCoupons": "{{response.data}}", - "downloadableCouponsLoading": false, - "downloadingMore": false - } - }, - { "handler": "openModal", "target": "coupon_download_modal" } - ], - "onError": [ - { - "handler": "setState", - "params": { "target": "local", "downloadableCouponsLoading": false } - }, - { - "handler": "toast", - "params": { - "type": "error", - "message": "$t:shop.coupon_download.fetch_failed" - } - } - ] - } + { "handler": "openModal", "target": "coupon_download_modal" } ] } }, diff --git a/templates/_bundled/sirsoft-basic/layouts/shop/reorder.json b/templates/_bundled/sirsoft-basic/layouts/shop/reorder.json index d916a765..15feffac 100644 --- a/templates/_bundled/sirsoft-basic/layouts/shop/reorder.json +++ b/templates/_bundled/sirsoft-basic/layouts/shop/reorder.json @@ -79,7 +79,7 @@ "name": "Icon", "props": { "name": "loader-2", - "className": "w-10 h-10 animate-spin text-gray-500 dark:text-gray-400" + "className": "text-4xl animate-spin text-gray-500 dark:text-gray-400" } }, { @@ -113,7 +113,7 @@ "name": "Icon", "props": { "name": "alert-triangle", - "className": "w-6 h-6 text-amber-500" + "className": "text-2xl text-amber-500" } }, { @@ -139,7 +139,7 @@ "className": "space-y-2 mb-6 border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-gray-50 dark:bg-gray-900" }, "iteration": { - "items": "{{_local.skipped ?? []}}", + "source": "{{_local.skipped ?? []}}", "item_var": "item", "index_var": "idx" }, @@ -239,7 +239,7 @@ "name": "Icon", "props": { "name": "x-circle", - "className": "w-6 h-6 text-red-500" + "className": "text-2xl text-red-500" } }, { diff --git a/tests/Feature/Api/Admin/ScheduleControllerTest.php b/tests/Feature/Api/Admin/ScheduleControllerTest.php index 0b954c8b..6bcb7f23 100644 --- a/tests/Feature/Api/Admin/ScheduleControllerTest.php +++ b/tests/Feature/Api/Admin/ScheduleControllerTest.php @@ -5,6 +5,7 @@ namespace Tests\Feature\Api\Admin; use App\Models\Permission; use App\Models\Role; use App\Models\Schedule; +use App\Models\ScheduleHistory; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; @@ -115,6 +116,51 @@ class ScheduleControllerTest extends TestCase ], $attributes)); } + // ──────────────────────────────────────────────────────── + // 실행 이력 목록 — 관계 로드 (지연 조인 계약) + // ──────────────────────────────────────────────────────── + + /** + * 실행 이력 목록이 실행자(triggered_by)를 함께 내려주는지 검증합니다. + * + * 지연 조인 트레이트는 inner 뿐 아니라 outer 에서도 `setEagerLoads([])` 로 eager load 를 + * 지운다. 따라서 저장소가 관계를 `$query->with()` 로 붙이고 `relations:` 인자를 넘기지 + * 않으면 관계가 조용히 사라지고, Resource 의 `whenLoaded('triggeredBy')` 가 항상 비어 + * `triggered_by` 키 자체가 응답에서 없어진다. 예외도 쿼리 오류도 나지 않으므로 이 단언이 + * 없으면 화면에서 실행자 칸이 빈 것으로만 드러난다. + */ + public function test_history_list_includes_triggered_by_relation(): void + { + $schedule = $this->createSchedule(); + + ScheduleHistory::create([ + 'schedule_id' => $schedule->id, + 'started_at' => now()->subMinute(), + 'ended_at' => now(), + 'duration' => 1, + 'status' => 'success', + 'exit_code' => 0, + 'trigger_type' => 'manual', + 'triggered_by' => $this->admin->id, + ]); + + $response = $this->authRequest() + ->getJson("/api/admin/schedules/{$schedule->id}/history"); + + $response->assertStatus(200); + + $rows = $response->json('data.data') ?? $response->json('data'); + $this->assertNotEmpty($rows, '실행 이력이 최소 1건 조회되어야 한다'); + + $this->assertArrayHasKey( + 'triggered_by', + $rows[0], + 'triggered_by 키가 없다 — 저장소가 relations: 로 관계를 넘기지 않아 eager load 가 지워졌을 수 있다' + ); + $this->assertSame($this->admin->name, $rows[0]['triggered_by']['name']); + $this->assertSame($this->admin->uuid, $rows[0]['triggered_by']['uuid']); + } + // ──────────────────────────────────────────────────────── // Bulk Update Status (BulkUpdateScheduleStatusRequest) // ──────────────────────────────────────────────────────── diff --git a/tests/Feature/Extension/CheckUpdatesCommandReportingTest.php b/tests/Feature/Extension/CheckUpdatesCommandReportingTest.php new file mode 100644 index 00000000..b477f9d4 --- /dev/null +++ b/tests/Feature/Extension/CheckUpdatesCommandReportingTest.php @@ -0,0 +1,61 @@ +getFileName()); + + $this->assertMatchesRegularExpression( + "/'update_available' => (true|\\\$result\['update_available'\]),\s*\n\s*'identifier'|'identifier' => \\\$identifier,\s*\n\s*'update_available'/", + $source, + class_basename($manager).'::details 항목에 update_available 이 없으면 커맨드가 상태를 판별할 수 없습니다' + ); + } + } + + /** + * 커맨드가 요약 건수를 "확인한 전체" 가 아닌 값으로 오기재하지 않는지 검증합니다. + * + * @return void + */ + public function test_커맨드가_업데이트_가능_건수를_details_로_센다(): void + { + foreach ([ + 'app/Console/Commands/Module/CheckModuleUpdatesCommand.php', + 'app/Console/Commands/Plugin/CheckPluginUpdatesCommand.php', + 'app/Console/Commands/Template/CheckTemplateUpdatesCommand.php', + ] as $path) { + $source = file_get_contents(base_path($path)); + + $this->assertStringNotContainsString( + "\$detail['update_available'] ?? false", + $source, + basename($path).': 없는 키를 기본 false 로 읽으면 업데이트 가능 항목이 "최신" 으로 표시됩니다' + ); + } + } +} diff --git a/tests/Feature/Identity/IdentityLogDateRangeFilterTest.php b/tests/Feature/Identity/IdentityLogDateRangeFilterTest.php new file mode 100644 index 00000000..d53c6a7d --- /dev/null +++ b/tests/Feature/Identity/IdentityLogDateRangeFilterTest.php @@ -0,0 +1,131 @@ +` 라 'YYYY-MM-DD' 만 오는데 + * datetime 비교에 그대로 넣어 '00:00:00' 으로 해석됐다. 종료일 당일이 통째로 빠진다. + * + * 기준은 활동 로그(`ActivityLogRepository`)와 같다 — 입력은 **사이트 타임존 기준**으로 읽고, + * 시각이 없는 종료일은 그날 끝까지 포함한다. + * + * 부하 설계: 경계 시각 9건을 **INSERT 1회**로 심고 한 메서드 안에서 네 경계를 모두 단언한다. + * 케이스마다 픽스처를 다시 만들면 같은 행을 4번 심게 되고, 행마다 create→forceFill→refresh 로 + * 3쿼리를 쓰면 그 3배가 다시 곱해진다. + */ +class IdentityLogDateRangeFilterTest extends TestCase +{ + use RefreshDatabase; + + /** + * 경계 판정에 쓰는 사이트 타임존 기준 시각 목록. + * + * 한 벌로 네 가지 경계(종료일 종일 확장 / 시작일 당일 포함 / 같은 날 범위 / 시각 명시)를 + * 모두 판정할 수 있도록 전날 끝 · 당일 처음/중간/끝 · 다음날 처음을 함께 둔다. + */ + private const BOUNDARY_TIMES = [ + '2026-07-28 23:59:59', + '2026-07-29 00:00:00', + '2026-07-29 09:00:00', + '2026-07-29 11:00:00', + '2026-07-29 12:00:00', + '2026-07-29 13:45:00', + '2026-07-29 23:59:59', + '2026-07-30 00:00:00', + '2026-07-30 00:00:01', + ]; + + /** + * 경계 시각 이력을 INSERT 1회로 심습니다. + * + * `created_at` 은 fillable 이 아니라 모델 create() 로 지정되지 않는다. 모델을 거치면 + * 저장 후 덮어쓰기(UPDATE)와 재조회(SELECT)가 행마다 붙으므로 쿼리 빌더로 직접 넣는다. + */ + private function seedBoundaryLogs(): void + { + $timezone = TimezoneHelper::getSiteTimezone(); + + $rows = array_map(function (string $siteLocal) use ($timezone) { + $utc = Carbon::parse($siteLocal, $timezone)->utc(); + + return [ + 'id' => (string) Str::uuid(), + 'provider_id' => 'g7:core.mail', + 'purpose' => 'sensitive_action', + 'channel' => 'email', + 'target_hash' => hash('sha256', $siteLocal), + 'status' => 'verified', + 'attempts' => 1, + 'max_attempts' => 5, + 'created_at' => $utc, + 'updated_at' => $utc, + ]; + }, self::BOUNDARY_TIMES); + + DB::table((new IdentityVerificationLog)->getTable())->insert($rows); + } + + /** + * 리포지토리 검색 결과 총건수를 반환합니다. + * + * @param array $filters 필터 배열 + * @return int 총건수 + */ + private function totalFor(array $filters): int + { + return app(IdentityVerificationLogRepository::class)->search($filters, 50)->total(); + } + + /** + * 기간 필터의 네 경계가 사이트 타임존 기준으로 해석되어야 한다. + */ + public function test_기간필터_경계가_사이트_타임존_기준으로_해석된다(): void + { + $this->seedBoundaryLogs(); + + // 종료일만 지정 — 시각이 없으므로 그날 23:59:59 까지 포함한다 + // (07-28 1건 + 07-29 6건 = 7건, 07-30 2건 제외) + $this->assertSame( + 7, + $this->totalFor(['date_to' => '2026-07-29']), + '종료일 당일이 통째로 빠지면 종일 확장이 적용되지 않은 것입니다.' + ); + + // 시작일만 지정 — 그날 00:00:00 부터 포함한다 (07-29 6건 + 07-30 2건 = 8건) + $this->assertSame( + 8, + $this->totalFor(['date_from' => '2026-07-29']), + '전날 23:59:59 가 섞이면 타임존 변환이 빠진 것입니다.' + ); + + // 같은 날 범위 — 그날 하루 전체 (07-29 6건) + $this->assertSame( + 6, + $this->totalFor(['date_from' => '2026-07-29', 'date_to' => '2026-07-29']), + '같은 날짜 범위는 그날 00:00:00~23:59:59 전체여야 합니다.' + ); + + // 시각까지 지정한 종료값 — 종일 확장이 그 시각을 덮어쓰면 안 된다 + // (07-28 23:59:59 + 07-29 00:00:00 + 07-29 09:00:00 = 3건) + $this->assertSame( + 3, + $this->totalFor(['date_to' => '2026-07-29 10:00:00']), + '시각이 포함된 종료값은 그 시각까지만 조회해야 합니다.' + ); + } +} diff --git a/tests/Feature/LanguagePack/ModuleLangPathAlignmentTest.php b/tests/Feature/LanguagePack/ModuleLangPathAlignmentTest.php new file mode 100644 index 00000000..33a3348a --- /dev/null +++ b/tests/Feature/LanguagePack/ModuleLangPathAlignmentTest.php @@ -0,0 +1,92 @@ +getVirtualBuiltInPacks(); + + $modulePacks = $packs->filter( + fn (LanguagePack $p) => $p->scope === LanguagePackScope::Module->value + && $p->source_type === LanguagePackSourceType::BuiltIn->value + ); + + $this->assertNotEmpty($modulePacks, '번들 모듈의 내장 언어팩이 하나 이상 합성되어야 합니다.'); + + foreach ($modulePacks as $pack) { + $this->assertMatchesRegularExpression( + '#^modules/_bundled/[^/]+/src/lang/(ko|en)$#', + (string) $pack->source_url, + "모듈 내장 언어팩 경로는 런타임이 로드하는 src/lang 이어야 합니다: {$pack->target_identifier} ({$pack->locale}) → {$pack->source_url}" + ); + } + } + + /** + * 번들 모듈이 런타임 미로드 경로에 백엔드 다국어 파일을 두지 않았는지 검증합니다. + * + * `resources/lang/{locale}/*.php` 는 프런트엔드용 `resources/lang/{locale}.json` · + * `resources/lang/partial/{locale}/` 와 달리 아무도 읽지 않는다. 거기 문구를 고치면 + * 고쳐지지 않고, 그 사실이 드러나지 않는다. + */ + #[Test] + public function bundled_modules_have_no_backend_lang_outside_runtime_path(): void + { + $root = base_path('modules/_bundled'); + + if (! File::isDirectory($root)) { + $this->markTestSkipped('번들 모듈 디렉토리가 없습니다.'); + } + + $stray = []; + + foreach (File::directories($root) as $moduleDir) { + foreach (['ko', 'en'] as $locale) { + $wrong = $moduleDir.'/resources/lang/'.$locale; + + if (! File::isDirectory($wrong)) { + continue; + } + + $php = array_map( + fn ($f) => basename($moduleDir).'/resources/lang/'.$locale.'/'.basename($f), + File::glob($wrong.'/*.php') ?: [] + ); + + $stray = array_merge($stray, $php); + } + } + + $this->assertSame( + [], + $stray, + "런타임이 읽지 않는 경로의 백엔드 다국어 파일이 있습니다(모듈은 src/lang 만 로드):\n ".implode("\n ", $stray) + ); + } +} diff --git a/tests/Feature/Menu/MenuListSortAndSearchTest.php b/tests/Feature/Menu/MenuListSortAndSearchTest.php new file mode 100644 index 00000000..400eff40 --- /dev/null +++ b/tests/Feature/Menu/MenuListSortAndSearchTest.php @@ -0,0 +1,183 @@ +ko`)로 검색해야 한다. + * + * **HTTP 엔드포인트로 검증한다.** Service 를 직접 부르면 컨트롤러의 분기를 타지 않아 + * D-22 가 남아 있어도 통과한다(실제로 처음 작성했을 때 그렇게 통과했다). + * + * 부하 설계: 관리자 1명 + 메뉴 3건을 INSERT 로 심고 한 메서드에서 두 결함을 모두 단언한다. + */ +class MenuListSortAndSearchTest extends TestCase +{ + use RefreshDatabase; + + private User $adminUser; + + private int $roleId; + + protected function setUp(): void + { + parent::setUp(); + + // permission 미들웨어는 권한 행이 실제로 존재해야 통과한다(is_super 만으로는 403). + // 이 테스트가 쓰는 읽기 권한 1건만 심어 픽스처를 최소로 유지한다. + $role = Role::create([ + 'identifier' => 'menu-reader', + 'name' => ['ko' => '메뉴 조회자', 'en' => 'Menu Reader'], + 'description' => ['ko' => '메뉴 조회 전용', 'en' => 'Menu read only'], + 'is_active' => true, + ]); + + $permission = Permission::create([ + 'identifier' => 'core.menus.read', + 'name' => ['ko' => '메뉴 조회', 'en' => 'Read Menus'], + 'description' => ['ko' => '메뉴를 조회할 수 있습니다.', 'en' => 'Can read menus.'], + 'extension_type' => 'core', + 'extension_identifier' => 'core', + 'type' => 'admin', + ]); + + $role->permissions()->attach($permission->id); + + $this->roleId = $role->id; + + $this->adminUser = User::factory()->create(['is_super' => true]); + $this->adminUser->roles()->attach($role->id); + } + + /** + * 정렬·검색 판정용 메뉴를 INSERT 1회로 심습니다. + * + * `order` 순서와 이름 순서를 **일부러 어긋나게** 둔다 — 두 순서가 같으면 정렬이 + * 무시돼도 기본 정렬(order)과 결과가 같아 결함을 통과시킨다. + */ + private function seedMenus(): void + { + $rows = [ + ['ko' => '대시보드', 'en' => 'Dashboard', 'slug' => 'reg-dashboard', 'order' => 1], + ['ko' => '사용자 관리', 'en' => 'Users', 'slug' => 'reg-users', 'order' => 2], + ['ko' => '게시판 관리', 'en' => 'Boards', 'slug' => 'reg-boards', 'order' => 3], + ]; + + DB::table((new Menu)->getTable())->insert(array_map(fn ($r) => [ + 'name' => json_encode(['ko' => $r['ko'], 'en' => $r['en']]), + 'slug' => $r['slug'], + 'url' => '/'.$r['slug'], + 'order' => $r['order'], + 'is_active' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ], $rows)); + + // 메뉴는 역할에 read 로 연결돼야 목록에 노출된다(`Menu::scopeAccessibleBy`). + // 연결 행도 INSERT 1회로 심는다. + $menuIds = Menu::whereIn('slug', array_column($rows, 'slug'))->pluck('id'); + + DB::table('role_menus')->insert($menuIds->map(fn ($id) => [ + 'menu_id' => $id, + 'role_id' => $this->roleId, + 'permission_type' => MenuPermissionType::Read->value, + 'created_at' => now(), + 'updated_at' => now(), + ])->all()); + } + + /** + * 메뉴 목록 API 를 호출하고 이 테스트가 심은 메뉴의 slug 순서를 반환합니다. + * + * @param string $query 쿼리스트링 (앞의 ? 제외) + * @return array slug 순서 + */ + private function slugsFrom(string $query): array + { + $response = $this->actingAs($this->adminUser, 'sanctum') + ->getJson('/api/admin/menus'.($query === '' ? '' : '?'.$query)); + + $response->assertOk(); + + $slugs = []; + $collect = function ($node) use (&$collect, &$slugs) { + if (! is_array($node)) { + return; + } + + if (isset($node['slug']) && is_string($node['slug']) && str_starts_with($node['slug'], 'reg-')) { + $slugs[] = $node['slug']; + } + + foreach ($node as $child) { + $collect($child); + } + }; + + $collect($response->json()); + + return $slugs; + } + + /** + * 필터 없이도 정렬이 적용되고, 한글 검색이 다국어 JSON 컬럼에서 매칭되어야 한다. + */ + public function test_필터_없이도_정렬이_적용되고_한글검색이_매칭된다(): void + { + $this->seedMenus(); + + // (D-22) 필터를 하나도 걸지 않은 상태에서 이름 오름차순. + // 기대 순서는 이름순(게시판<대시보드<사용자 / BoardsassertSame( + ['reg-boards', 'reg-dashboard', 'reg-users'], + $this->slugsFrom('sort_by=name&sort_order=asc'), + '필터 없이 정렬만 요청하면 정렬이 무시됩니다 — 기본 order 순서로 되돌아간 것입니다.' + ); + + // 방향을 뒤집으면 순서도 뒤집혀야 한다 (한쪽만 보면 우연한 일치를 못 걸러낸다) + $this->assertSame( + ['reg-users', 'reg-dashboard', 'reg-boards'], + $this->slugsFrom('sort_by=name&sort_order=desc'), + '정렬 방향이 반영되지 않았습니다.' + ); + + // (D-23) 한글 검색어가 JSON 이스케이프 저장값에서도 매칭되어야 한다 + $this->assertSame( + ['reg-dashboard'], + $this->slugsFrom('filters[0][field]=name&filters[0][value]=대시보드&filters[0][operator]=like'), + '한글 검색이 0건이면 JSON 컬럼 전체에 LIKE 를 건 것입니다 — 로케일 경로로 검색해야 합니다.' + ); + + // 'all' 필드 검색도 같은 경로를 타야 한다 + $this->assertSame( + ['reg-users'], + $this->slugsFrom('filters[0][field]=all&filters[0][value]=사용자&filters[0][operator]=like'), + "'all' 검색에서도 다국어 컬럼이 로케일 경로로 풀려야 합니다." + ); + + // 다른 로케일 값으로도 검색되어야 한다 (표시 로케일에만 묶이면 안 된다) + $this->assertSame( + ['reg-boards'], + $this->slugsFrom('filters[0][field]=name&filters[0][value]=Boards&filters[0][operator]=like'), + '지원 로케일 전체를 대상으로 검색해야 합니다.' + ); + } +} diff --git a/tests/Feature/Search/FulltextIndexMaintainerTest.php b/tests/Feature/Search/FulltextIndexMaintainerTest.php new file mode 100644 index 00000000..5d819c2a --- /dev/null +++ b/tests/Feature/Search/FulltextIndexMaintainerTest.php @@ -0,0 +1,206 @@ +markTestSkipped('FULLTEXT 미지원 드라이버 — 이 검사는 MySQL/MariaDB 에서만 의미가 있습니다.'); + } + + $this->dropProbeTable(); + } + + /** + * 테스트 정리 — 임시 테이블 제거. + * + * @return void + */ + protected function tearDown(): void + { + $this->dropProbeTable(); + + parent::tearDown(); + } + + /** + * 스키마에 새로 만든 FULLTEXT 인덱스를 발견하고 정상으로 판정하는지 검증합니다. + * + * 대상 목록을 하드코딩하지 않으므로, 확장이 나중에 추가하는 인덱스도 자동 포함됩니다. + * + * @return void + */ + public function test_새로_만든_인덱스를_발견하고_정상으로_판정한다(): void + { + $this->createProbeTable(); + + $health = $this->findProbeHealth(); + + $this->assertNotNull($health, '스키마에 만든 FULLTEXT 인덱스를 발견하지 못했습니다'); + $this->assertSame(SearchIndexStatus::Healthy, $health->status); + $this->assertGreaterThan(0, $health->details['probed']); + $this->assertSame($health->details['probed'], $health->details['found']); + $this->assertSame(['body'], $health->details['columns']); + } + + /** + * 재생성이 컬럼 구성과 파서를 보존하는지 검증합니다. + * + * 재생성이 정의를 바꾸면 검색 동작이 조용히 달라집니다. + * + * @return void + */ + public function test_재생성이_컬럼과_파서를_보존한다(): void + { + $this->createProbeTable(); + + $before = $this->findProbeHealth(); + $this->assertNotNull($before); + + app(FulltextIndexMaintainer::class)->rebuild($before); + + $after = $this->findProbeHealth(); + + $this->assertNotNull($after, '재생성 후 인덱스가 사라졌습니다'); + $this->assertSame($before->details['columns'], $after->details['columns']); + $this->assertSame($before->details['parser'], $after->details['parser']); + $this->assertSame(SearchIndexStatus::Healthy, $after->status); + } + + /** + * 색인할 내용이 없는 테이블은 stale 이 아니라 판정 불가로 보고하는지 검증합니다. + * + * 빈 테이블을 "색인 누락" 으로 보면 신규 설치본이 매번 불필요한 재생성을 하게 됩니다. + * + * @return void + */ + public function test_행이_없으면_stale_이_아니라_판정_불가다(): void + { + $this->createProbeTable(withRows: false); + + $health = $this->findProbeHealth(); + + $this->assertNotNull($health); + $this->assertSame(SearchIndexStatus::Skipped, $health->status); + $this->assertFalse($health->needsRebuild(), '빈 테이블이 재생성 대상이 되어서는 안 됩니다'); + } + + /** + * 테이블 필터가 대상을 좁히는지 검증합니다. + * + * @return void + */ + public function test_테이블_필터로_대상을_좁힌다(): void + { + $this->createProbeTable(); + + $filtered = app(FulltextIndexMaintainer::class)->inspect(['table' => self::TABLE]); + + $this->assertNotEmpty($filtered); + + foreach ($filtered as $health) { + $this->assertSame($this->physicalTable(), $health->details['table']); + } + } + + /** + * 검사용 임시 테이블을 만듭니다. + * + * @param bool $withRows 행을 채울지 여부 + * @return void + */ + private function createProbeTable(bool $withRows = true): void + { + $parser = DatabaseFulltextEngine::supportsNgramParser() ? ' WITH PARSER ngram' : ''; + + $table = $this->physicalTable(); + + DB::statement( + 'CREATE TABLE `'.$table.'` ( + id INT AUTO_INCREMENT PRIMARY KEY, + body TEXT + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' + ); + + if ($withRows) { + foreach ([ + '검수용 본문 이용약관 Terms of Service', + '검수용 본문 개인정보처리방침 Privacy Policy', + '검수용 본문 자주 묻는 질문 Frequently Asked', + ] as $body) { + DB::statement('INSERT INTO `'.$table.'` (body) VALUES (?)', [$body]); + } + } + + DB::statement( + 'ALTER TABLE `'.$table.'` ADD FULLTEXT INDEX `ft_zz_probe_body` (`body`)'.$parser + ); + } + + /** + * 검사용 임시 테이블을 제거합니다. + * + * @return void + */ + private function dropProbeTable(): void + { + DB::statement('DROP TABLE IF EXISTS `'.$this->physicalTable().'`'); + } + + /** + * 검사용 인덱스의 판정 결과를 찾습니다. + * + * @return SearchIndexHealth|null + */ + private function findProbeHealth(): ?SearchIndexHealth + { + foreach (app(FulltextIndexMaintainer::class)->inspect() as $health) { + if (($health->details['index'] ?? null) === 'ft_zz_probe_body') { + return $health; + } + } + + return null; + } +} diff --git a/tests/Feature/Search/SearchIndexNoticeReachesClientTest.php b/tests/Feature/Search/SearchIndexNoticeReachesClientTest.php new file mode 100644 index 00000000..c43f72a6 --- /dev/null +++ b/tests/Feature/Search/SearchIndexNoticeReachesClientTest.php @@ -0,0 +1,131 @@ + 'sample-module'])) + ->additional(['search_index' => ['rebuilt' => false, 'stale_count' => 1]]); + + $body = json_decode( + ResponseHelper::successWithResource('messages.success', $resource)->getContent(), + true + ); + + $this->assertArrayHasKey('search_index', $body, '색인 안내가 응답에서 사라졌습니다'); + $this->assertFalse($body['search_index']['rebuilt']); + $this->assertSame(1, $body['search_index']['stale_count']); + $this->assertSame('sample-module', $body['data']['identifier'], '리소스 본문은 그대로 유지되어야 합니다'); + } + + /** + * 부가 데이터가 없을 때 응답 형태가 달라지지 않는지 검증합니다. + * + * @return void + */ + public function test_부가데이터가_없으면_응답_형태가_그대로다(): void + { + $body = json_decode( + ResponseHelper::successWithResource( + 'messages.success', + new PayloadCarrierResource(['identifier' => 'sample-module']) + )->getContent(), + true + ); + + $this->assertSame(['success', 'message', 'data'], array_keys($body)); + } + + /** + * 업데이트 성공 메시지의 치환자가 실제 값으로 바뀌는지 검증합니다. + * + * 치환자가 남으면 API 소비자(외부 클라이언트 포함)가 `:module` 같은 원문을 그대로 봅니다. + * + * @return void + */ + public function test_업데이트_성공_메시지의_치환자가_치환된다(): void + { + $body = json_decode( + ResponseHelper::successWithResource( + 'modules.update_success', + new PayloadCarrierResource(['identifier' => 'sirsoft-page']), + 200, + ['module' => 'sirsoft-page', 'version' => '1.0.2'] + )->getContent(), + true + ); + + $this->assertStringNotContainsString(':module', $body['message']); + $this->assertStringNotContainsString(':version', $body['message']); + $this->assertStringContainsString('sirsoft-page', $body['message']); + $this->assertStringContainsString('1.0.2', $body['message']); + } + + /** + * 모듈 업데이트 컨트롤러가 메시지 파라미터를 실제로 넘기는지 정적으로 고정합니다. + * + * @return void + */ + public function test_모듈_플러그인_컨트롤러가_메시지_파라미터를_넘긴다(): void + { + foreach ([ + app_path('Http/Controllers/Api/Admin/ModuleController.php'), + app_path('Http/Controllers/Api/Admin/PluginController.php'), + ] as $path) { + $source = file_get_contents($path); + + $this->assertMatchesRegularExpression( + "/update_success',\s*\n?\s*\(new \w+Resource/", + $source, + basename($path).': 업데이트 성공 응답 형태가 바뀌었습니다 — 파라미터 전달 여부를 재확인하세요' + ); + $this->assertStringContainsString( + "'version' =>", + $source, + basename($path).': 성공 메시지에 version 파라미터가 전달되지 않습니다' + ); + } + } +} + +/** + * 부가 데이터 유실을 검증하기 위한 최소 리소스. + */ +class PayloadCarrierResource extends JsonResource +{ + /** + * 리소스를 배열로 변환합니다. + * + * @param Request $request 요청 + * @return array + */ + public function toArray($request): array + { + return ['identifier' => $this->resource['identifier']]; + } +} diff --git a/tests/Feature/Search/SearchIndexRebuildOptInTest.php b/tests/Feature/Search/SearchIndexRebuildOptInTest.php new file mode 100644 index 00000000..214296ea --- /dev/null +++ b/tests/Feature/Search/SearchIndexRebuildOptInTest.php @@ -0,0 +1,150 @@ + 'optin-test-engine']); + SpyIndexMaintainer::$rebuilt = []; + + $this->app->bind(SpyIndexMaintainer::class, fn () => new SpyIndexMaintainer); + + HookManager::addFilter( + SearchIndexMaintenanceManager::MAINTAINERS_FILTER, + fn (array $maintainers) => $maintainers + ['optin-test-engine' => SpyIndexMaintainer::class] + ); + } + + /** + * 체크하지 않으면 재생성하지 않고 점검 결과만 돌려주는지 검증합니다. + * + * @return void + */ + public function test_요청하지_않으면_재생성하지_않는다(): void + { + $payload = $this->rebuildSearchIndexIfRequested(false); + + $this->assertSame([], SpyIndexMaintainer::$rebuilt, '요청 없이 재생성이 일어났습니다'); + $this->assertFalse($payload['rebuilt']); + $this->assertSame(1, $payload['stale_count'], '재생성하지 않아도 누락 사실은 알려야 합니다'); + $this->assertSame(['idx-stale'], $payload['stale']); + } + + /** + * 체크했을 때만 재생성하는지 검증합니다. + * + * @return void + */ + public function test_요청했을_때만_재생성한다(): void + { + $payload = $this->rebuildSearchIndexIfRequested(true); + + $this->assertSame(['idx-stale'], SpyIndexMaintainer::$rebuilt); + $this->assertTrue($payload['rebuilt']); + $this->assertSame(['idx-stale'], $payload['repaired']); + } + + /** + * 점검을 제공하지 않는 엔진에서는 응답에 실을 정보가 없음을 검증합니다. + * + * @return void + */ + public function test_점검_미제공_엔진에서는_null_을_돌려준다(): void + { + config(['scout.driver' => 'engine-without-maintainer']); + + $this->assertNull($this->rebuildSearchIndexIfRequested(true)); + $this->assertSame([], SpyIndexMaintainer::$rebuilt); + } + + /** + * 확장 업데이트 API 가 기본적으로 재생성하지 않는지 검증합니다. + * + * FormRequest 에 `rebuild_search_index` 를 넣지 않은 요청이 재생성을 유발하면 안 됩니다. + * + * @return void + */ + public function test_업데이트_요청_본문에_없으면_재생성하지_않는다(): void + { + $rules = (new PerformModuleUpdateRequest)->rules(); + + $this->assertArrayHasKey('rebuild_search_index', $rules, '선택 필드가 게이트에 선언되어 있어야 합니다'); + $this->assertContains('nullable', $rules['rebuild_search_index'], '미전송이 허용되어야 기본이 "미수행" 이 됩니다'); + $this->assertContains('boolean', $rules['rebuild_search_index']); + } +} + +/** + * 재생성 호출을 기록하는 가짜 유지보수기. + */ +class SpyIndexMaintainer implements SearchIndexMaintainer +{ + /** @var array rebuild() 가 호출된 식별자 */ + public static array $rebuilt = []; + + /** {@inheritDoc} */ + public function driver(): string + { + return 'optin-test-engine'; + } + + /** {@inheritDoc} */ + public function isAvailable(): bool + { + return true; + } + + /** {@inheritDoc} */ + public function unavailableReason(): ?string + { + return null; + } + + /** {@inheritDoc} */ + public function inspect(array $filters = []): array + { + $staleStatus = in_array('idx-stale', self::$rebuilt, true) + ? SearchIndexStatus::Healthy + : SearchIndexStatus::Stale; + + return [ + new SearchIndexHealth('optin-test-engine', 'idx-healthy', SearchIndexStatus::Healthy, 'ok'), + new SearchIndexHealth('optin-test-engine', 'idx-stale', $staleStatus, 'probe'), + ]; + } + + /** {@inheritDoc} */ + public function rebuild(SearchIndexHealth $health): void + { + self::$rebuilt[] = $health->identifier; + } +} diff --git a/tests/Playwright/specs/admin/extension-update-search-index-optin.spec.ts b/tests/Playwright/specs/admin/extension-update-search-index-optin.spec.ts new file mode 100644 index 00000000..0f744dc4 --- /dev/null +++ b/tests/Playwright/specs/admin/extension-update-search-index-optin.spec.ts @@ -0,0 +1,154 @@ +/** + * E2E: 확장 업데이트의 「검색 인덱스 재생성」 선택이 그 창에서만 유효한지 (#492 검색 인덱스 실측) + * + * 재생성은 인덱스 잠금·전체 재색인을 유발한다. 체크 상태가 전역에 남으면 운영자가 한 번 + * 체크한 뒤로는 아무것도 누르지 않아도 다음 확장 업데이트마다 재생성이 실행된다 + * (브라우저 실측으로 실제 재생성이 일어남을 확인했다). + * + * 응답의 `search_index` 페이로드도 함께 본다 — 색인이 비면 검색은 오류 없이 0건을 + * 돌려주므로, 이 페이로드가 사라지면 운영자가 알 방법이 없다. + * + * @scenario case=extension_update_rebuild_optin + * + * @effects rebuild_checkbox_defaults_unchecked, rebuild_checkbox_resets_on_reopen, + * update_response_carries_search_index + */ +import type { Page } from '@playwright/test'; +import { test, expect, authenticatePage, issueToken } from '../../fixtures/auth'; + +/** 모달 안 체크박스 셀렉터 (모듈/플러그인 공통 규칙) */ +const CHECKBOX = (kind: 'module' | 'plugin') => `#${kind}_rebuild_search_index input[type="checkbox"]`; + +/** + * 목록 화면에 진입해 렌더가 끝날 때까지 기다립니다. + * + * @param page 대상 페이지 + * @param path 이동할 경로 + * @returns void + */ +async function gotoAndSettle(page: Page, path: string): Promise { + // 고정 대기만 두면 느린 회차에서 목록이 아직 없는 채로 판정해 조용히 skip 이 된다. + const listed = page + .waitForResponse( + (r) => + (r.url().includes('/api/admin/modules') || r.url().includes('/api/admin/plugins')) && + r.status() === 200, + { timeout: 30000 } + ) + .catch(() => null); + + await page.goto(path); + await page.waitForLoadState('domcontentloaded'); + await listed; + await page.waitForTimeout(1500); +} + +/** + * 업데이트 모달을 엽니다. 업데이트 가능한 확장이 없으면 false 를 돌려줍니다. + * + * @param page 대상 페이지 + * @param kind module | plugin + * @returns Promise 모달이 열렸으면 true + */ +async function openUpdateModal(page: Page, kind: 'module' | 'plugin'): Promise { + // 두 가지를 함께 흡수한다. + // - 접근성 이름에 아이콘 텍스트가 섞인다 (예: 'refresh 업데이트') + // - 테스트 계정의 로케일이 ko 라는 보장이 없다 (실제로 en 으로 떨어져 한국어 매칭이 전부 skip 됐다) + // 목록 상단의 '업데이트 확인 / Check Updates' 는 이름이 그 단어로 끝나지 않아 자연히 제외된다. + const button = page + .getByRole('button', { name: /(업데이트|Update|アップデート|更新)$/ }) + .first(); + + if ((await button.count()) === 0) { + return false; + } + + await button.click(); + await page.waitForTimeout(1500); + + return (await page.locator(CHECKBOX(kind)).count()) > 0; +} + +for (const kind of ['module', 'plugin'] as const) { + const listPath = kind === 'module' ? '/admin/modules' : '/admin/plugins'; + const label = kind === 'module' ? '모듈' : '플러그인'; + + test.describe(`${label} 업데이트 — 검색 인덱스 재생성 옵트인`, () => { + test(`체크는 기본 해제이고 모달을 다시 열면 초기화된다`, async ({ page }) => { + await authenticatePage(page, issueToken('core.modules.read', 'core.modules.install', 'core.plugins.read', 'core.plugins.install')); + await gotoAndSettle(page, listPath); + + const opened = await openUpdateModal(page, kind); + test.skip(!opened, `업데이트 가능한 ${label}이 없어 모달을 열 수 없습니다`); + + const checkbox = page.locator(CHECKBOX(kind)); + + // 진입 시 기본 미체크 + expect(await checkbox.isChecked(), '재생성 체크가 기본으로 켜져 있습니다').toBe(false); + + // 체크 후 취소로 닫는다. + // + // 체크박스는 `