diff --git a/api/Sources/Api/Environment/Ephemeral.swift b/api/Sources/Api/Environment/Ephemeral.swift index 9f2c608b..8d740961 100644 --- a/api/Sources/Api/Environment/Ephemeral.swift +++ b/api/Sources/Api/Environment/Ephemeral.swift @@ -95,6 +95,9 @@ actor Ephemeral { } func getPendingAppConnection(_ code: Int) -> User.Id? { + #if DEBUG + if code == 999_999 { return AdminBetsy.Ids.jimmysId } + #endif guard let (userId, expiration) = pendingAppConnections[code], expiration > Current.date() else { return nil diff --git a/api/Sources/Api/Models/Admin/Device+Model.swift b/api/Sources/Api/Models/Admin/Device+Model.swift index 44cb8e97..fd9c723f 100644 --- a/api/Sources/Api/Models/Admin/Device+Model.swift +++ b/api/Sources/Api/Models/Admin/Device+Model.swift @@ -123,6 +123,7 @@ extension Device.Model.Chip { } enum MacOS: Encodable { + case sonoma case ventura case monterey case bigSur diff --git a/api/Sources/Api/PairQL/MacApp/Resolvers/ConnectUser.swift b/api/Sources/Api/PairQL/MacApp/Resolvers/ConnectUser.swift index bd0bf2c8..3b232126 100644 --- a/api/Sources/Api/PairQL/MacApp/Resolvers/ConnectUser.swift +++ b/api/Sources/Api/PairQL/MacApp/Resolvers/ConnectUser.swift @@ -8,7 +8,8 @@ extension ConnectUser: Resolver { else { throw context.error( id: "6e7fc234", type: .unauthorized, - debugMessage: "ConnectApp verification code not found", + debugMessage: "verification code not found", + userMessage: "Connection code not found, or expired. Please try again.", appTag: .connectionCodeNotFound ) } @@ -37,7 +38,12 @@ extension ConnectUser: Resolver { // sanity check - we only "transfer" a device, if the admin accounts match let existingUser = try await existingUserDevice.user() if existingUser.adminId != user.adminId { - throw Abort(.forbidden, reason: "Device already registered to another admin's user") + throw context.error( + id: "41a43089", + type: .unauthorized, + debugMessage: "invalid connect transfer attempt", + userMessage: "This user is associated with another Gertrude parent account." + ) } let oldUserId = existingUserDevice.userId diff --git a/api/Tests/ApiTests/MacappPairResolvers/ConnectUserResolverTests.swift b/api/Tests/ApiTests/MacappPairResolvers/ConnectUserResolverTests.swift index 6ccaa778..5960614f 100644 --- a/api/Tests/ApiTests/MacappPairResolvers/ConnectUserResolverTests.swift +++ b/api/Tests/ApiTests/MacappPairResolvers/ConnectUserResolverTests.swift @@ -114,7 +114,7 @@ final class ConnectUserResolversTests: ApiTestCase { try await expectErrorFrom { [self] in _ = try await ConnectUser.resolve(with: input, in: self.context) - }.toContain("registered to another admin") + }.toContain("associated with another Gertrude parent account") // old token is not deleted let retrievedOldToken = try? await Current.db.find(existingUserToken.id) diff --git a/gertie/Sources/Gertie/WebSocketMessage.swift b/gertie/Sources/Gertie/WebSocketMessage.swift index 285c1aea..c0821d4b 100644 --- a/gertie/Sources/Gertie/WebSocketMessage.swift +++ b/gertie/Sources/Gertie/WebSocketMessage.swift @@ -14,7 +14,7 @@ public enum WebSocketMessage { case suspendFilterRequestDenied(parentComment: String?) } - public enum FromAppToApi: Codable { + public enum FromAppToApi: Codable, Equatable { case currentFilterState(UserFilterState) case goingOffline } diff --git a/macapp/App/Sources/App/AdminFeature.swift b/macapp/App/Sources/App/AdminFeature.swift index 6a730ad5..06dc7e1e 100644 --- a/macapp/App/Sources/App/AdminFeature.swift +++ b/macapp/App/Sources/App/AdminFeature.swift @@ -24,6 +24,7 @@ extension AdminFeature.RootReducer: RootReducing, AdminAuthenticating { case .adminWindow(.webview(.inactiveAccountDisconnectAppClicked)), .blockedRequests(.webview(.inactiveAccountDisconnectAppClicked)), .requestSuspension(.webview(.inactiveAccountDisconnectAppClicked)), + .requestSuspension(.webview(.noFilterCommunicationAdministrateClicked)), .blockedRequests(.webview(.noFilterCommunicationAdministrateClicked)): return adminAuthenticated(action) @@ -49,7 +50,10 @@ extension AdminFeature.RootReducer: RootReducing, AdminAuthenticating { state.requestSuspension.windowOpen = false state.adminWindow.windowOpen = true state.adminWindow.screen = .healthCheck - return .none + return .exec { send in + // we've already authed the admin, this kicks off a full health check + await send(.adminAuthed(.menuBar(.administrateClicked))) + } default: return .none diff --git a/macapp/App/Sources/App/App+PersistantState.swift b/macapp/App/Sources/App/App+PersistantState.swift index ae46d23b..3e5c0417 100644 --- a/macapp/App/Sources/App/App+PersistantState.swift +++ b/macapp/App/Sources/App/App+PersistantState.swift @@ -11,6 +11,9 @@ enum Persistent { var appUpdateReleaseChannel: ReleaseChannel var filterVersion: String var user: UserData? + + // added for v2.1.0, but is backwards compatible + var resumeOnboarding: OnboardingFeature.Resume? } // v2.0.0 - v2.0.3 @@ -28,7 +31,8 @@ extension AppReducer.State { appVersion: appUpdates.installedVersion, appUpdateReleaseChannel: appUpdates.releaseChannel, filterVersion: filter.version, - user: user.data + user: user.data, + resumeOnboarding: nil ) } } diff --git a/macapp/App/Sources/App/App.swift b/macapp/App/Sources/App/App.swift index 204d79be..ae8fede5 100644 --- a/macapp/App/Sources/App/App.swift +++ b/macapp/App/Sources/App/App.swift @@ -10,8 +10,12 @@ typealias UserData = GetUserData.Output var blockedRequestsWindow: BlockedRequestsWindow var adminWindow: AdminWindow var requestSuspensionWindow: RequestSuspensionWindow + var onboardingWindow: OnboardingWindow let store = Store( - initialState: AppReducer.State(), + initialState: AppReducer.State(appVersion: { + @Dependency(\.app) var appClient + return appClient.installedVersion() + }()), reducer: { AppReducer()._printChanges(.filteredBy { action in switch action { @@ -45,6 +49,10 @@ typealias UserData = GetUserData.Output state: { $0 }, action: AppReducer.Action.requestSuspension )) + onboardingWindow = OnboardingWindow(store: store.scope( + state: { $0 }, + action: AppReducer.Action.onboarding + )) #if !DEBUG setEventReporter { kind, eventId, detail in diff --git a/macapp/App/Sources/App/AppReducer.swift b/macapp/App/Sources/App/AppReducer.swift index 995a0319..ba3f32f1 100644 --- a/macapp/App/Sources/App/AppReducer.swift +++ b/macapp/App/Sources/App/AppReducer.swift @@ -11,14 +11,25 @@ struct AppReducer: Reducer, Sendable { struct State: Equatable, Sendable { var admin = AdminFeature.State() var adminWindow = AdminWindowFeature.State() - var appUpdates = AppUpdatesFeature.State() + var appUpdates: AppUpdatesFeature.State var blockedRequests = BlockedRequestsFeature.State() - var filter = FilterFeature.State() + var filter: FilterFeature.State var history = HistoryFeature.State() var menuBar = MenuBarFeature.State() + var onboarding = OnboardingFeature.State() var monitoring = MonitoringFeature.State() var requestSuspension = RequestSuspensionFeature.State() var user = UserFeature.State() + + init(appVersion: String?) { + appUpdates = .init(installedVersion: appVersion) + filter = .init(appVersion: appVersion) + } + } + + enum CancelId { + case heartbeatInterval + case websocketMessages } enum Action: Equatable, Sendable { @@ -43,46 +54,105 @@ struct AppReducer: Reducer, Sendable { case history(HistoryFeature.Action) case menuBar(MenuBarFeature.Action) case monitoring(MonitoringFeature.Action) + case onboarding(OnboardingFeature.Action) case loadedPersistentState(Persistent.State?) case user(UserFeature.Action) - case heartbeat(Heartbeat.Interval) + case heartbeat(HeartbeatInterval) case blockedRequests(BlockedRequestsFeature.Action) case requestSuspension(RequestSuspensionFeature.Action) + case startProtecting(user: UserData) case websocket(WebSocketFeature.Action) indirect case adminAuthed(Action) } @Dependency(\.api) var api + @Dependency(\.app) var app @Dependency(\.device) var device @Dependency(\.backgroundQueue) var bgQueue + @Dependency(\.mainQueue) var mainQueue + @Dependency(\.network) var network + @Dependency(\.storage) var storage + @Dependency(\.websocket) var websocket var body: some ReducerOf { Reduce { state, action in #if !DEBUG os_log("[G•] APP received action: %{public}@", String(describing: action)) #endif + switch action { - case .loadedPersistentState(.some(let persistent)): - state.appUpdates.releaseChannel = persistent.appUpdateReleaseChannel - state.filter.version = persistent.filterVersion - guard let user = persistent.user else { return .none } - state.user = .init(data: user) - return .exec { [filterVersion = state.filter.version] send in - await api.setUserToken(user.token) - try await bgQueue.sleep(for: .milliseconds(10)) // <- unit test determinism - return await send(.checkIn( - result: TaskResult { try await api.appCheckIn(filterVersion) }, - reason: .appLaunched - )) + case .loadedPersistentState(.none): + state.onboarding.windowOpen = true + return .exec { [new = state.persistent] _ in + try await storage.savePersistentState(new) } + case .loadedPersistentState(.some(let persisted)): + state.appUpdates.releaseChannel = persisted.appUpdateReleaseChannel + state.filter.version = persisted.filterVersion + var effects: [Effect] = [] + if let user = persisted.user { + state.user = .init(data: user) + if persisted.resumeOnboarding == nil { + effects.append(.exec { send in + await send(.startProtecting(user: user)) + }) + } else { + state.onboarding.connectChildRequest = .succeeded(payload: user.name) + } + } + if let onboardingStep = persisted.resumeOnboarding { + effects.append(.exec { send in + await send(.onboarding(.resume(onboardingStep))) + }) + effects.append(.exec { [persist = state.persistent] _ in + var withoutResume = persist + withoutResume.resumeOnboarding = nil + try await storage.savePersistentState(withoutResume) + }) + } + return .merge(effects) + + case .startProtecting(let user): + let onboardingWindowOpen = state.onboarding.windowOpen + return .merge( + .exec { [filterVersion = state.filter.version] send in + await api.setUserToken(user.token) + guard network.isConnected() else { return } + await send(.checkIn( + result: TaskResult { try await api.appCheckIn(filterVersion) }, + reason: .startProtecting + )) + }, + .exec { _ in + if onboardingWindowOpen == false, (await app.isLaunchAtLoginEnabled()) == false { + await app.enableLaunchAtLogin() + } + }, + .publisher { + websocket.receive() + .map { .websocket(.receivedMessage($0)) } + .receive(on: mainQueue) + }.cancellable(id: CancelId.websocketMessages), + .exec { send in + var numTicks = 0 + for await _ in bgQueue.timer(interval: .seconds(60)) { + numTicks += 1 + for interval in heartbeatIntervals(for: numTicks) { + await send(.heartbeat(interval)) + } + } + }.cancellable(id: CancelId.heartbeatInterval) + ) + case .focusedNotification(let notification): // dismiss windows/dropdowns so notification is visible, i.e. "focused" state.adminWindow.windowOpen = false state.menuBar.dropdownOpen = false state.blockedRequests.windowOpen = false state.requestSuspension.windowOpen = false + state.onboarding.windowOpen = false return .exec { _ in switch notification { case .unexpectedError: @@ -92,6 +162,23 @@ struct AppReducer: Reducer, Sendable { } } + case .onboarding(.delegate(.saveForResume(let resume))): + OnboardingFeature.Reducer() + .log("save for resume: \(String(describing: resume))", "93e00bac") + return .exec { [persist = state.persistent] _ in + var copy = persist + copy.resumeOnboarding = resume + try await storage.savePersistentState(copy) + } + + case .onboarding(.delegate(.onboardingConfigComplete)): + OnboardingFeature.Reducer().log("finished", "079cbee4") + if let user = state.user.data { + return .exec { send in await send(.startProtecting(user: user)) } + } else { + return .none + } + default: return .none } @@ -144,5 +231,8 @@ struct AppReducer: Reducer, Sendable { Scope(state: \.user, action: /Action.user) { UserFeature.Reducer() } + Scope(state: \.onboarding, action: /Action.onboarding) { + OnboardingFeature.Reducer() + } } } diff --git a/macapp/App/Sources/App/AppUpdatesFeature.swift b/macapp/App/Sources/App/AppUpdatesFeature.swift index b360dd54..18bb75bd 100644 --- a/macapp/App/Sources/App/AppUpdatesFeature.swift +++ b/macapp/App/Sources/App/AppUpdatesFeature.swift @@ -42,10 +42,9 @@ struct AppUpdatesFeature: Feature { } extension AppUpdatesFeature.State { - init() { - @Dependency(\.app) var appClient + init(installedVersion: String?) { self.init( - installedVersion: appClient.installedVersion() ?? "0.0.0", + installedVersion: installedVersion ?? "0.0.0", releaseChannel: .stable, latestVersion: nil ) @@ -56,10 +55,6 @@ extension AppUpdatesFeature.RootReducer: FilterControlling { func reduce(into state: inout State, action: Action) -> Effect { switch action { - case .loadedPersistentState(.none): - return .exec { [new = state.persistent] _ in - try await storage.savePersistentState(new) - } case .loadedPersistentState(.some(let restored)): guard restored.appVersion != state.appUpdates.installedVersion else { @@ -84,7 +79,7 @@ extension AppUpdatesFeature.RootReducer: FilterControlling { // refresh the rules post-update, or else health check will complain await send(.checkIn( result: TaskResult { try await api.appCheckIn(version) }, - reason: .appLaunched + reason: .appUpdated )) // big sur doesn't get notification pushed when filter restarts diff --git a/macapp/App/Sources/App/AppWindow.swift b/macapp/App/Sources/App/AppWindow.swift index 6481a267..36ff2d32 100644 --- a/macapp/App/Sources/App/AppWindow.swift +++ b/macapp/App/Sources/App/AppWindow.swift @@ -20,6 +20,7 @@ protocol AppWindow: AnyObject { var windowLevel: NSWindow.Level { get } var title: String { get } var screen: String { get } + var showTitleBar: Bool { get } var closeWindowAction: Action { get } func embed(_ webviewAction: WebViewAction) -> Action @@ -29,6 +30,7 @@ extension AppWindow { var windowLevel: NSWindow.Level { .normal } var initialSize: NSRect { NSRect(x: 0, y: 0, width: 900, height: 600) } var minSize: NSSize { NSSize(width: 800, height: 500) } + var showTitleBar: Bool { true } @MainActor func bind() { windowDelegate.events @@ -69,10 +71,18 @@ extension AppWindow { window?.delegate = windowDelegate window?.tabbingMode = .disallowed window?.titlebarAppearsTransparent = true + + if !showTitleBar { + window?.titleVisibility = .hidden + window?.styleMask.insert(NSWindow.StyleMask.fullSizeContentView) + window?.isMovableByWindowBackground = true + } + window?.isReleasedWhenClosed = false window?.level = windowLevel let wvc = WebViewController() + wvc.withTitleBar = showTitleBar wvc.send = { [weak self] action in guard let self = self else { return } diff --git a/macapp/App/Sources/App/ApplicationFeature.swift b/macapp/App/Sources/App/ApplicationFeature.swift index 795b8be4..5a05de76 100644 --- a/macapp/App/Sources/App/ApplicationFeature.swift +++ b/macapp/App/Sources/App/ApplicationFeature.swift @@ -1,5 +1,7 @@ import ComposableArchitecture +import Foundation + // public, not nested, because it's used in the AppDelegate public enum ApplicationAction: Equatable, Sendable { case didFinishLaunching @@ -15,11 +17,10 @@ enum ApplicationFeature { @Dependency(\.app) var app @Dependency(\.backgroundQueue) var bgQueue @Dependency(\.device) var device - @Dependency(\.mainQueue) var mainQueue @Dependency(\.storage) var storage @Dependency(\.filterXpc) var filterXpc @Dependency(\.filterExtension) var filterExtension - @Dependency(\.websocket) var websocket + @Dependency(\.mainQueue) var mainQueue } } @@ -29,19 +30,17 @@ extension ApplicationFeature.RootReducer: RootReducing { case .application(.didFinishLaunching): return .merge( - .exec { _ in - // requesting notification authorization at least once - // ensures that the system prefs panel will show Gertrude - // TODO: consider delaying this if no user connected - await device.requestNotificationAuthorization() - }, - .exec { send in + #if DEBUG + // uncomment to test ONBOARDING + // if ProcessInfo.processInfo.environment["SWIFT_DETERMINISTIC_HASHING"] == nil { + // await storage.deleteAll() + // } + #endif await send(.loadedPersistentState(try await storage.loadPersistentState())) }, .exec { send in - try await bgQueue.sleep(for: .milliseconds(5)) // <- unit test determinism let setupState = await filterExtension.setup() await send(.filter(.receivedState(setupState))) if setupState.installed { @@ -49,22 +48,6 @@ extension ApplicationFeature.RootReducer: RootReducing { } }, - .exec { send in - var numTicks = 0 - for await _ in bgQueue.timer(interval: .seconds(60)) { - numTicks += 1 - for interval in heartbeatIntervals(for: numTicks) { - await send(.heartbeat(interval)) - } - } - }.cancellable(id: Heartbeat.CancelId.interval), - - .exec { _ in - if await app.isLaunchAtLoginEnabled() == false { - await app.enableLaunchAtLogin() - } - }, - .publisher { filterExtension.stateChanges() .map { .filter(.receivedState($0)) } @@ -75,37 +58,34 @@ extension ApplicationFeature.RootReducer: RootReducing { filterXpc.events() .map { .xpc($0) } .receive(on: mainQueue) - }, - - .publisher { - websocket.receive() - .map { .websocket(.receivedMessage($0)) } - .receive(on: mainQueue) } ) case .application(.willTerminate): - return .cancel(id: Heartbeat.CancelId.interval) + return .merge( + .cancel(id: AppReducer.CancelId.heartbeatInterval), + .cancel(id: AppReducer.CancelId.websocketMessages) + ) default: return .none } } +} - func heartbeatIntervals(for tick: Int) -> [Heartbeat.Interval] { - var intervals: [Heartbeat.Interval] = [.everyMinute] - if tick % 5 == 0 { - intervals.append(.everyFiveMinutes) - } - if tick % 20 == 0 { - intervals.append(.everyTwentyMinutes) - } - if tick % 60 == 0 { - intervals.append(.everyHour) - } - if tick % 360 == 0 { - intervals.append(.everySixHours) - } - return intervals +func heartbeatIntervals(for tick: Int) -> [HeartbeatInterval] { + var intervals: [HeartbeatInterval] = [.everyMinute] + if tick % 5 == 0 { + intervals.append(.everyFiveMinutes) + } + if tick % 20 == 0 { + intervals.append(.everyTwentyMinutes) + } + if tick % 60 == 0 { + intervals.append(.everyHour) + } + if tick % 360 == 0 { + intervals.append(.everySixHours) } + return intervals } diff --git a/macapp/App/Sources/App/CheckInFeature.swift b/macapp/App/Sources/App/CheckInFeature.swift index 783fce9b..8f4c212d 100644 --- a/macapp/App/Sources/App/CheckInFeature.swift +++ b/macapp/App/Sources/App/CheckInFeature.swift @@ -129,9 +129,10 @@ extension CheckInFeature.RootReducer { extension CheckIn { enum Reason: Equatable, Sendable { - case appLaunched + case appUpdated case healthCheck case heartbeat + case startProtecting case inactiveAccountRechecked case receivedWebsocketMessage case userRefreshedRules diff --git a/macapp/App/Sources/App/Dependencies/DeviceClient/DeviceClient+Os.swift b/macapp/App/Sources/App/Dependencies/DeviceClient/DeviceClient+Os.swift new file mode 100644 index 00000000..a524913f --- /dev/null +++ b/macapp/App/Sources/App/Dependencies/DeviceClient/DeviceClient+Os.swift @@ -0,0 +1,69 @@ +import Foundation + +struct MacOSVersion: Sendable { + enum Name: String { + case catalina + case bigSur + case monterey + case ventura + case sonoma + case next + } + + let major: Int + let minor: Int + let patch: Int + + var semver: String { + "\(major).\(minor).\(patch)" + } + + var name: Name { + switch (major, minor) { + case (10, 15): return .catalina + case (11, _): return .bigSur + case (12, _): return .monterey + case (13, _): return .ventura + case (14, _): return .sonoma + default: return .next + } + } + + var description: String { + "\(name.rawValue)@\(semver)" + } +} + +@Sendable func macOSVersion() -> MacOSVersion { + let version = ProcessInfo.processInfo.operatingSystemVersion + return MacOSVersion( + major: version.majorVersion, + minor: version.minorVersion, + patch: version.patchVersion + ) +} + +extension MacOSVersion { + enum DocumentationGroup: String, Encodable { + case catalina + case bigSurOrMonterey + case venturaOrLater + } + + var documentationGroup: DocumentationGroup { + switch name { + case .catalina: + return .catalina + case .bigSur, .monterey: + return .bigSurOrMonterey + case .ventura, .sonoma, .next: + return .venturaOrLater + } + } +} + +#if DEBUG + extension MacOSVersion { + static let sonoma = Self(major: 14, minor: 0, patch: 0) + } +#endif diff --git a/macapp/App/Sources/App/Dependencies/DeviceClient/DeviceClient.swift b/macapp/App/Sources/App/Dependencies/DeviceClient/DeviceClient.swift index cb01fab8..dde14f8a 100644 --- a/macapp/App/Sources/App/Dependencies/DeviceClient/DeviceClient.swift +++ b/macapp/App/Sources/App/Dependencies/DeviceClient/DeviceClient.swift @@ -12,6 +12,7 @@ struct DeviceClient: Sendable { var numericUserId: @Sendable () -> uid_t var openSystemPrefs: @Sendable (SystemPrefsLocation) async -> Void var openWebUrl: @Sendable (URL) async -> Void + var osVersion: @Sendable () -> MacOSVersion var quitBrowsers: @Sendable () async -> Void var requestNotificationAuthorization: @Sendable () async -> Void var showNotification: @Sendable (String, String) async -> Void @@ -30,6 +31,7 @@ extension DeviceClient: DependencyKey { numericUserId: { getuid() }, openSystemPrefs: openSystemPrefs(at:), openWebUrl: { NSWorkspace.shared.open($0) }, + osVersion: { macOSVersion() }, quitBrowsers: quitAllBrowsers, requestNotificationAuthorization: requestNotificationAuth, showNotification: showNotification(title:body:), @@ -40,6 +42,26 @@ extension DeviceClient: DependencyKey { extension DeviceClient: TestDependencyKey { static let testValue = Self( + currentMacOsUserType: unimplemented("DeviceClient.currentMacOsUserType"), + currentUserId: unimplemented("DeviceClient.currentUserId"), + fullUsername: unimplemented("DeviceClient.fullUsername"), + listMacOSUsers: unimplemented("DeviceClient.listMacOSUsers"), + modelIdentifier: unimplemented("DeviceClient.modelIdentifier"), + notificationsSetting: unimplemented("DeviceClient.notificationsSetting"), + numericUserId: unimplemented("DeviceClient.numericUserId"), + openSystemPrefs: unimplemented("DeviceClient.openSystemPrefs"), + openWebUrl: unimplemented("DeviceClient.openWebUrl"), + osVersion: unimplemented("DeviceClient.osVersion"), + quitBrowsers: unimplemented("DeviceClient.quitBrowsers"), + requestNotificationAuthorization: unimplemented( + "DeviceClient.requestNotificationAuthorization" + ), + showNotification: unimplemented("DeviceClient.showNotification"), + serialNumber: unimplemented("DeviceClient.serialNumber"), + username: unimplemented("DeviceClient.username") + ) + + static let mock = Self( currentMacOsUserType: { .standard }, currentUserId: { 502 }, fullUsername: { "test-full-username" }, @@ -52,6 +74,7 @@ extension DeviceClient: TestDependencyKey { numericUserId: { 502 }, openSystemPrefs: { _ in }, openWebUrl: { _ in }, + osVersion: { .init(major: 14, minor: 0, patch: 0) }, quitBrowsers: {}, requestNotificationAuthorization: {}, showNotification: { _, _ in }, diff --git a/macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient+TakeScreenshot.swift b/macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient+Screenshots.swift similarity index 79% rename from macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient+TakeScreenshot.swift rename to macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient+Screenshots.swift index 8ccd2344..f842e00b 100644 --- a/macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient+TakeScreenshot.swift +++ b/macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient+Screenshots.swift @@ -7,8 +7,7 @@ import SystemConfiguration typealias ScreenshotData = (data: Data, width: Int, height: Int, createdAt: Date) -@Sendable -func takeScreenshot(width: Int) async throws { +@Sendable func takeScreenshot(width: Int) async throws { guard currentUserProbablyHasScreen(), screensaverRunning() == false else { return } @@ -66,6 +65,32 @@ enum ScreenshotError: Error { case downsampleFailed } +// this technique should be reliable for all supported os's, (including catalina) +// and does not cause a system prompt for screen recording permission +// @see https://www.ryanthomson.net/articles/screen-recording-permissions-catalina-mess/ +@Sendable func isScreenRecordingPermissionGranted() -> Bool { + guard let windowList = CGWindowListCopyWindowInfo(.excludeDesktopElements, kCGNullWindowID) + as NSArray? else { return false } + + for case let windowInfo as NSDictionary in windowList { + // Ignore windows owned by this application + let windowPID = windowInfo[kCGWindowOwnerPID] as? pid_t + if windowPID == NSRunningApplication.current.processIdentifier { + continue + } + // Ignore system UI elements + if windowInfo[kCGWindowOwnerName] as? String == "Window Server" { + continue + } + + if windowInfo[kCGWindowName] != nil { + return true + } + } + + return false +} + // helpers private func diskUrl(filename: String) -> URL { diff --git a/macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient.swift b/macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient.swift index d359169d..618ce815 100644 --- a/macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient.swift +++ b/macapp/App/Sources/App/Dependencies/MonitoringClient/MonitoringClient.swift @@ -33,17 +33,7 @@ extension MonitoringClient: DependencyKey { #endif }, restorePendingKeystrokes: restoreKeystrokes(_:), - screenRecordingPermissionGranted: { - if #available(macOS 11, *) { - // apple docs say available in 10.15, but that's not the case: - // https://developer.apple.com/forums/thread/683860 - return CGPreflightScreenCaptureAccess() - } else { - // no way in Catalina to check this :/ - // @see https://www.ryanthomson.net/articles/screen-recording-permissions-catalina-mess/ - return true - } - }, + screenRecordingPermissionGranted: isScreenRecordingPermissionGranted, startLoggingKeystrokes: startKeylogging, stopLoggingKeystrokes: stopKeylogging, takePendingKeystrokes: takeKeystrokes, @@ -54,6 +44,21 @@ extension MonitoringClient: DependencyKey { extension MonitoringClient: TestDependencyKey { static let testValue = Self( + commitPendingKeystrokes: unimplemented("MonitoringClient.commitPendingKeystrokes"), + keystrokeRecordingPermissionGranted: unimplemented( + "MonitoringClient.keystrokeRecordingPermissionGranted" + ), + restorePendingKeystrokes: unimplemented("MonitoringClient.restorePendingKeystrokes"), + screenRecordingPermissionGranted: unimplemented( + "MonitoringClient.screenRecordingPermissionGranted" + ), + startLoggingKeystrokes: unimplemented("MonitoringClient.startLoggingKeystrokes"), + stopLoggingKeystrokes: unimplemented("MonitoringClient.stopLoggingKeystrokes"), + takePendingKeystrokes: unimplemented("MonitoringClient.takePendingKeystrokes"), + takePendingScreenshots: unimplemented("MonitoringClient.takePendingScreenshots"), + takeScreenshot: unimplemented("MonitoringClient.takeScreenshot") + ) + static let mock = Self( commitPendingKeystrokes: { _ in }, keystrokeRecordingPermissionGranted: { true }, restorePendingKeystrokes: { _ in }, diff --git a/macapp/App/Sources/App/Dependencies/SecurityClient.swift b/macapp/App/Sources/App/Dependencies/SecurityClient.swift index 2d60d1d9..929984ef 100644 --- a/macapp/App/Sources/App/Dependencies/SecurityClient.swift +++ b/macapp/App/Sources/App/Dependencies/SecurityClient.swift @@ -37,6 +37,9 @@ extension SecurityClient: DependencyKey { extension SecurityClient: TestDependencyKey { public static let testValue = Self( + didAuthenticateAsAdmin: unimplemented("SecurityClient.didAuthenticateAsAdmin") + ) + public static let mock = Self( didAuthenticateAsAdmin: { false } ) } diff --git a/macapp/App/Sources/App/Dependencies/StorageClient.swift b/macapp/App/Sources/App/Dependencies/StorageClient.swift index 652647fb..35863229 100644 --- a/macapp/App/Sources/App/Dependencies/StorageClient.swift +++ b/macapp/App/Sources/App/Dependencies/StorageClient.swift @@ -42,6 +42,12 @@ extension StorageClient: DependencyKey { extension StorageClient: TestDependencyKey { static let testValue = Self( + savePersistentState: unimplemented("StorageClient.savePersistentState"), + loadPersistentState: unimplemented("StorageClient.loadPersistentState"), + deleteAllPersistentState: unimplemented("StorageClient.deleteAllPersistentState"), + deleteAll: unimplemented("StorageClient.deleteAll") + ) + static let mock = Self( savePersistentState: { _ in }, loadPersistentState: { nil }, deleteAllPersistentState: {}, diff --git a/macapp/App/Sources/App/Dependencies/UpdaterClient.swift b/macapp/App/Sources/App/Dependencies/UpdaterClient.swift index 6d44a9bf..aa738534 100644 --- a/macapp/App/Sources/App/Dependencies/UpdaterClient.swift +++ b/macapp/App/Sources/App/Dependencies/UpdaterClient.swift @@ -12,6 +12,9 @@ public struct UpdaterClient: Sendable { extension UpdaterClient: TestDependencyKey { public static let testValue = Self( + triggerUpdate: unimplemented("UpdaterClient.triggerUpdate") + ) + public static let mock = Self( triggerUpdate: { _ in } ) } diff --git a/macapp/App/Sources/App/FilterFeature.swift b/macapp/App/Sources/App/FilterFeature.swift index e23b73bc..ae5313b4 100644 --- a/macapp/App/Sources/App/FilterFeature.swift +++ b/macapp/App/Sources/App/FilterFeature.swift @@ -47,12 +47,11 @@ struct FilterFeature: Feature { } extension FilterFeature.State { - init() { - @Dependency(\.app) var appClient + init(appVersion: String?) { self.init( currentSuspensionExpiration: nil, extension: .unknown, - version: appClient.installedVersion() ?? "unknown" + version: appVersion ?? "unknown" ) } @@ -143,37 +142,33 @@ extension FilterFeature.RootReducer { return .merge( .exec { send in if !extensionInstalled { - switch await filterExtension.install() { + let installResult = await filterExtension.install() + switch installResult { case .installedSuccessfully: break case .timedOutWaiting: // event `9ffabfe5` logged w/ more detail in FilterFeature.swift await send(.focusedNotification(.filterInstallTimeout)) case .userClickedDontAllow: + interestingEvent(id: "01f94ff3") await send(.focusedNotification(.filterInstallDenied)) - case .activationRequestFailed(let error): - unexpectedError(id: "61d0eda0", error) - case .failedToGetBundleIdentifier: - unexpectedError(id: "d4a652e9") - case .failedToLoadConfig: - unexpectedError(id: "bd04ba1a") - case .failedToSaveConfig: - unexpectedError(id: "161ed707") - case .alreadyInstalled: - unexpectedError(id: "ff51a770") + case .activationRequestFailed, + .failedToGetBundleIdentifier, + .failedToLoadConfig, + .failedToSaveConfig, + .alreadyInstalled: + unexpectedError(id: "8a8762e7", detail: "result: \(installResult)") } } else { - switch await filterExtension.start() { + let state = await filterExtension.start() + switch state { case .installedAndRunning: break - case .errorLoadingConfig: - unexpectedError(id: "c291bcef") - case .installedButNotRunning: - unexpectedError(id: "99f3465c") - case .notInstalled: - unexpectedError(id: "6e4f30ac") - case .unknown: - unexpectedError(id: "24f31d4c") + case .errorLoadingConfig, + .installedButNotRunning, + .notInstalled, + .unknown: + unexpectedError(id: "cb2a0564", detail: "state: \(state)") } } }, diff --git a/macapp/App/Sources/App/Generated/OnboardingFeature+Codable.swift b/macapp/App/Sources/App/Generated/OnboardingFeature+Codable.swift new file mode 100644 index 00000000..bf6e1b7a --- /dev/null +++ b/macapp/App/Sources/App/Generated/OnboardingFeature+Codable.swift @@ -0,0 +1,75 @@ +// auto-generated, do not edit +import Foundation + +extension OnboardingFeature.Action.View { + private struct _NamedCase: Codable { + var `case`: String + static func extract(from decoder: Decoder) throws -> String { + let container = try decoder.singleValueContainer() + return try container.decode(_NamedCase.self).case + } + } + + private struct _TypeScriptDecodeError: Error { + var message: String + } + + private struct _CaseConnectChildSubmitted: Codable { + var `case` = "connectChildSubmitted" + var code: Int + } + + private struct _CaseInfoModalOpened: Codable { + var `case` = "infoModalOpened" + var step: OnboardingFeature.State.Step + var detail: String? + } + + func encode(to encoder: Encoder) throws { + switch self { + case .connectChildSubmitted(let code): + try _CaseConnectChildSubmitted(code: code).encode(to: encoder) + case .infoModalOpened(let step, let detail): + try _CaseInfoModalOpened(step: step, detail: detail).encode(to: encoder) + case .closeWindow: + try _NamedCase(case: "closeWindow").encode(to: encoder) + case .primaryBtnClicked: + try _NamedCase(case: "primaryBtnClicked").encode(to: encoder) + case .secondaryBtnClicked: + try _NamedCase(case: "secondaryBtnClicked").encode(to: encoder) + case .chooseSwitchToNonAdminUserClicked: + try _NamedCase(case: "chooseSwitchToNonAdminUserClicked").encode(to: encoder) + case .chooseCreateNonAdminClicked: + try _NamedCase(case: "chooseCreateNonAdminClicked").encode(to: encoder) + case .chooseDemoteAdminClicked: + try _NamedCase(case: "chooseDemoteAdminClicked").encode(to: encoder) + } + } + + init(from decoder: Decoder) throws { + let caseName = try _NamedCase.extract(from: decoder) + let container = try decoder.singleValueContainer() + switch caseName { + case "connectChildSubmitted": + let value = try container.decode(_CaseConnectChildSubmitted.self) + self = .connectChildSubmitted(code: value.code) + case "infoModalOpened": + let value = try container.decode(_CaseInfoModalOpened.self) + self = .infoModalOpened(step: value.step, detail: value.detail) + case "closeWindow": + self = .closeWindow + case "primaryBtnClicked": + self = .primaryBtnClicked + case "secondaryBtnClicked": + self = .secondaryBtnClicked + case "chooseSwitchToNonAdminUserClicked": + self = .chooseSwitchToNonAdminUserClicked + case "chooseCreateNonAdminClicked": + self = .chooseCreateNonAdminClicked + case "chooseDemoteAdminClicked": + self = .chooseDemoteAdminClicked + default: + throw _TypeScriptDecodeError(message: "Unexpected case name: `\(caseName)`") + } + } +} diff --git a/macapp/App/Sources/App/HistoryFeature.swift b/macapp/App/Sources/App/HistoryFeature.swift index ac9a051b..ffc2c1cf 100644 --- a/macapp/App/Sources/App/HistoryFeature.swift +++ b/macapp/App/Sources/App/HistoryFeature.swift @@ -52,7 +52,7 @@ extension HistoryFeature.RootReducer: RootReducing { state.history.userConnection = .connecting return .exec { send in await send(.history(.userConnection(.connect(TaskResult { - try await api.connectUser(connectUserInput(code: code)) + try await api.connectUser(.init(code: code, device: device, app: app)) })))) } @@ -75,7 +75,16 @@ extension HistoryFeature.RootReducer: RootReducing { case .history(.userConnection(.connect(.success(let user)))): state.user = .init(data: user) + return .exec { [persistent = state.persistent] send in + await send(.startProtecting(user: user)) + try await storage.savePersistentState(persistent) + } + + case .onboarding(.connectUser(.success(let user))): + state.user = .init(data: user) + state.history.userConnection = .established(welcomeDismissed: true) return .exec { [persistent = state.persistent] _ in + await api.setUserToken(user.token) try await storage.savePersistentState(persistent) } @@ -89,13 +98,15 @@ extension HistoryFeature.RootReducer: RootReducing { return .none } } +} - private func connectUserInput(code: Int) throws -> ConnectUser.Input { +extension ConnectUser.Input { + init(code: Int, device: DeviceClient, app: AppClient) throws { guard let serialNumber = device.serialNumber() else { struct NoSerialNumber: Error {} throw NoSerialNumber() } - return ConnectUser.Input( + self.init( verificationCode: code, appVersion: app.installedVersion() ?? "unknown", modelIdentifier: device.modelIdentifier() ?? "unknown", diff --git a/macapp/App/Sources/App/MonitoringFeature.swift b/macapp/App/Sources/App/MonitoringFeature.swift index e76b009c..8e57961a 100644 --- a/macapp/App/Sources/App/MonitoringFeature.swift +++ b/macapp/App/Sources/App/MonitoringFeature.swift @@ -37,6 +37,7 @@ extension MonitoringFeature.RootReducer { switch action { case .loadedPersistentState(.some(let persistent)): + guard persistent.resumeOnboarding == nil else { return .none } return configureMonitoring(current: persistent.user, previous: nil) case .user(.updated(let previous)): @@ -45,6 +46,9 @@ extension MonitoringFeature.RootReducer { case .history(.userConnection(.connect(.success(let user)))): return configureMonitoring(current: user, previous: nil) + case .onboarding(.delegate(.onboardingConfigComplete)): + return configureMonitoring(current: state.user.data, previous: nil) + case .monitoring(.timerTriggeredTakeScreenshot): let width = state.user.data?.screenshotSize ?? 800 let filterSuspended = state.filter.isSuspended @@ -125,7 +129,9 @@ extension MonitoringFeature.RootReducer { flushKeystrokes(state.filter.isSuspended) ) - case .adminAuthed(.adminWindow(.webview(.disconnectUserClicked))): + case .adminAuthed(.adminWindow(.webview(.disconnectUserClicked))), + .history(.userConnection(.disconnectMissingUser)), + .websocket(.receivedMessage(.userDeleted)): return .cancel(id: CancelId.screenshots) // try to catch the moment when they've fixed monitoring permissions issues diff --git a/macapp/App/Sources/App/Onboarding/OnboardingFeature.swift b/macapp/App/Sources/App/Onboarding/OnboardingFeature.swift new file mode 100644 index 00000000..59033b16 --- /dev/null +++ b/macapp/App/Sources/App/Onboarding/OnboardingFeature.swift @@ -0,0 +1,557 @@ +import ClientInterfaces +import ComposableArchitecture +import Foundation + +struct OnboardingFeature: Feature { + struct State: Equatable, Encodable, Sendable { + struct MacUser: Equatable, Encodable { + var id: uid_t + var name: String + var isAdmin: Bool + + enum RemediationStep: String, Equatable, Encodable { + case create + case `switch` + case demote + case choose + } + } + + var windowOpen = false + var step: Step = .welcome + var userRemediationStep: MacUser.RemediationStep? + var currentUser: MacUser? + var connectChildRequest: PayloadRequestState = .idle + var users: [MacUser] = [] + } + + enum Resume: Codable, Equatable, Sendable { + case checkingScreenRecordingPermission + case at(step: State.Step) + } + + enum Action: Equatable, Sendable { + enum View: Equatable, Sendable, Decodable { + case closeWindow + case primaryBtnClicked + case secondaryBtnClicked + case chooseSwitchToNonAdminUserClicked + case chooseCreateNonAdminClicked + case chooseDemoteAdminClicked + case connectChildSubmitted(code: Int) + case infoModalOpened(step: State.Step, detail: String?) + } + + enum Delegate: Equatable, Sendable { + case saveForResume(Resume?) + case onboardingConfigComplete + } + + case webview(View) + case delegate(Delegate) + case resume(Resume) + case receivedDeviceData(currentUserId: uid_t, users: [MacOSUser]) + case connectUser(TaskResult) + case setStep(State.Step) + case closeWindow + } + + struct Reducer: FeatureReducer { + @Dependency(\.api) var api + @Dependency(\.app) var app + @Dependency(\.device) var device + @Dependency(\.filterExtension) var systemExtension + @Dependency(\.mainQueue) var mainQueue + @Dependency(\.monitoring) var monitoring + @Dependency(\.storage) var storage + + func reduce(into state: inout State, action: Action) -> Effect { + let step = state.step + let userIsAdmin = state.currentUser?.isAdmin != false + switch action { + + case .resume(.at(let step)): + log("resuming at \(step)", "711355aa") + state.windowOpen = true + state.step = step + return .exec { send in + await send(.receivedDeviceData( + currentUserId: device.currentUserId(), + users: try await device.listMacOSUsers() + )) + } + + case .resume(.checkingScreenRecordingPermission): + state.windowOpen = true + return .exec { send in + let granted = await monitoring.screenRecordingPermissionGranted() + log("resume checking screen recording, granted=\(granted)", "5d1d27fe") + if granted { + await send(.setStep(.allowScreenshots_success)) + } else { + await send(.delegate(.saveForResume(.checkingScreenRecordingPermission))) + await send(.setStep(.allowScreenshots_failed)) + } + } + + case .receivedDeviceData(let currentUserId, let users): + state.users = users.map(State.MacUser.init) + state.currentUser = state.users.first(where: { $0.id == currentUserId }) + return .none + + case .webview(.primaryBtnClicked) where step == .welcome: + log(step, action, "e712e261") + state.step = .confirmGertrudeAccount + return .exec { send in + await send(.receivedDeviceData( + currentUserId: device.currentUserId(), + users: try await device.listMacOSUsers() + )) + } + + case .webview(.primaryBtnClicked) where step == .confirmGertrudeAccount: + log(step, action, "36a1852c") + state.step = .macosUserAccountType + return .none + + case .webview(.secondaryBtnClicked) where step == .confirmGertrudeAccount: + log(step, action, "85958bee") + state.step = .noGertrudeAccount + return .none + + case .webview(.primaryBtnClicked) where step == .noGertrudeAccount: + log(step, action, "05820945") + state.step = .macosUserAccountType + return .none + + case .webview(.secondaryBtnClicked) where step == .noGertrudeAccount: + log("quit from no gertrude acct", "236defcb") + return .exec { _ in + await storage.deleteAll() + await app.quit() + } + + case .webview(.primaryBtnClicked) where step == .macosUserAccountType && !userIsAdmin: + log("macos account type correct next clicked", "0a29be72") + state.step = .getChildConnectionCode + return .none + + // they choose to ignore the warning about user type and proceed + case .webview(.secondaryBtnClicked) where step == .macosUserAccountType && userIsAdmin: + log("skip admin user account warning", "d044eb17") + state.step = .getChildConnectionCode + return .none + + // they click "show me how to fix" on the BAD mac os user landing page + case .webview(.primaryBtnClicked) where step == .macosUserAccountType && userIsAdmin: + state.userRemediationStep = state.users.count == 1 ? .create : .choose + log("show me how to fix admin user clicked, \(state.users.count) users", "74179c5c") + return .exec { send in + await send(.delegate(.saveForResume(.at(step: .macosUserAccountType)))) + } + + case .webview(.chooseDemoteAdminClicked): + log(step, action, "d638fa96") + state.userRemediationStep = .demote + return .none + + case .webview(.chooseCreateNonAdminClicked): + log(step, action, "c63bf016") + state.userRemediationStep = .create + return .none + + case .webview(.chooseSwitchToNonAdminUserClicked): + log(step, action, "68fdb44a") + state.userRemediationStep = .switch + return .none + + case .webview(.primaryBtnClicked) where step == .getChildConnectionCode: + log(step, action, "550d9504") + state.step = .connectChild + return .none + + case .webview(.connectChildSubmitted(let code)): + log(step, action, "3d6b89a8") + state.connectChildRequest = .ongoing + return .exec { send in + await send(.connectUser((TaskResult { + try await api.connectUser(.init(code: code, device: device, app: app)) + }))) + } + + case .connectUser(.success(let user)): + log("connect user success", "3a1ac301") + state.connectChildRequest = .succeeded(payload: user.name) + return .none + + case .connectUser(.failure(let error)): + log("connect user failed \(error)", "0ed97f9a") + state.connectChildRequest = .failed(error: error.userMessage()) + return .none + + case .webview(.primaryBtnClicked) + where step == .connectChild && state.connectChildRequest.isFailed: + log("retry connect user", "c69844b8") + state.connectChildRequest = .idle + state.step = .getChildConnectionCode + return .none + + case .webview(.secondaryBtnClicked) + where step == .connectChild && state.connectChildRequest.isFailed: + log("connect user failed secondary", "08de43c1") + return .exec { _ in + await device.openWebUrl(.contact) + } + + case .webview(.primaryBtnClicked) + where step == .connectChild && state.connectChildRequest.isSucceeded: + log("next from connect user success", "34221891") + return .exec { send in + await nextRequiredStage(from: .connectChild, send) + } + + case .webview(.primaryBtnClicked) where step == .allowNotifications_start: + log(step, action, "b183d96d") + state.step = .allowNotifications_grant + return .exec { _ in + await device.requestNotificationAuthorization() + await device.openSystemPrefs(.notifications) + } + + case .webview(.primaryBtnClicked) where step == .allowNotifications_grant: + log(step, action, "9fa094ac") + return .exec { send in + if await device.notificationsSetting() == .none { + await send(.setStep(.allowNotifications_failed)) + } else { + await nextRequiredStage(from: step, send) + } + } + + case .webview(.secondaryBtnClicked) where step == .allowNotifications_grant: + log(step, action, "8f9d3c9c") + state.step = .allowNotifications_failed + return .none + + case .webview(.primaryBtnClicked) where step == .allowNotifications_failed: + log(step, action, "b183d96d") + return .exec { send in + if await device.notificationsSetting() == .none { + await device.requestNotificationAuthorization() + await device.openSystemPrefs(.notifications) + await send(.setStep(.allowNotifications_grant)) + } else { + await nextRequiredStage(from: step, send) + } + } + + case .webview(.secondaryBtnClicked) + where step == .allowNotifications_start || step == .allowNotifications_failed: + log(step, action, "8cf52d46") + return .exec { send in + await nextRequiredStage(from: step, send) + } + + case .webview(.primaryBtnClicked) where step == .allowScreenshots_required: + return .exec { send in + let granted = await monitoring.screenRecordingPermissionGranted() + log("primary from .allowScreenshots_required, already granted=\(granted)", "ce78b67b") + if granted { + await nextRequiredStage(from: step, send) + } else { + try? await monitoring.takeScreenshot(500) // trigger permission prompt + await send(.delegate(.saveForResume(.checkingScreenRecordingPermission))) + await send(.setStep(.allowScreenshots_grantAndRestart)) + } + } + + case .webview(.secondaryBtnClicked) where step == .allowScreenshots_required: + log(step, action, "b2907efa") + return .exec { send in + await nextRequiredStage(from: step, send) + } + + case .webview(.primaryBtnClicked) where step == .allowScreenshots_grantAndRestart: + log(step, action, "c7e2bed4") + state.step = .allowScreenshots_failed + return .none + + case .webview(.secondaryBtnClicked) where step == .allowScreenshots_grantAndRestart: + log(step, action, "a85b700c") + return .exec { send in + await nextRequiredStage(from: step, send) + } + + case .webview(.primaryBtnClicked) where step == .allowScreenshots_failed: + log(step, action, "cfb65d32") + return .exec { send in + if await monitoring.screenRecordingPermissionGranted() { + await send(.setStep(.allowScreenshots_success)) + } else { + await device.openSystemPrefs(.security(.screenRecording)) + await send(.setStep(.allowScreenshots_grantAndRestart)) + } + } + + case .webview(.secondaryBtnClicked) where step == .allowScreenshots_failed: + log(step, action, "9616ea42") + return .exec { send in + await nextRequiredStage(from: step, send) + } + + case .webview(.primaryBtnClicked) where step == .allowScreenshots_success: + log(step, action, "fc9a6916") + return .exec { send in + await nextRequiredStage(from: step, send) + } + + case .webview(.primaryBtnClicked) where step == .allowKeylogging_required: + log(step, action, "6db8471f") + return .exec { send in + await nextRequiredStage(from: step, send) + } + + case .webview(.secondaryBtnClicked) where step == .allowKeylogging_required: + log(step, action, "61a87bb2") + return .exec { send in + await nextRequiredStage(from: .installSysExt_explain, send) + } + + case .webview(.primaryBtnClicked) where step == .allowKeylogging_grant: + return .exec { send in + let granted = await monitoring.keystrokeRecordingPermissionGranted() + log("primary from .allowKeylogging_grant, granted=\(granted)", "ce78b67b") + if granted { + await nextRequiredStage(from: step, send) + } else { + await send(.setStep(.allowKeylogging_failed)) + } + } + + case .webview(.secondaryBtnClicked) where step == .allowKeylogging_grant: + log(step, action, "5ccce8b9") + return .exec { send in + if await monitoring.keystrokeRecordingPermissionGranted() { + await nextRequiredStage(from: step, send) + } else { + await send(.setStep(.allowKeylogging_failed)) + } + } + + case .webview(.primaryBtnClicked) where step == .allowKeylogging_failed: + log(step, action, "36181833") + return .exec { send in + if await monitoring.keystrokeRecordingPermissionGranted() { + await nextRequiredStage(from: step, send) + } else { + await device.openSystemPrefs(.security(.accessibility)) + await send(.setStep(.allowKeylogging_grant)) + } + } + + case .webview(.secondaryBtnClicked) where step == .allowKeylogging_failed: + log(step, action, "775f57f9") + return .exec { send in + await nextRequiredStage(from: step, send) + } + + case .webview(.primaryBtnClicked) where step == .installSysExt_explain: + return .exec { send in + let startingState = await systemExtension.state() + log("primary from .installSysExt_explain, state=\(startingState)", "e585331d") + switch startingState { + case .notInstalled: + await send(.setStep(.installSysExt_allow)) + try? await mainQueue.sleep(for: .seconds(3)) // let them see the explanation gif + let installResult = await systemExtension.installOverridingTimeout(60 * 4) // 4 minutes + log("sys ext install result=\(installResult)", "adbc0453") + switch installResult { + case .installedSuccessfully: + await send(.setStep(.installSysExt_success)) + case .timedOutWaiting, .userClickedDontAllow: + await send(.setStep(.installSysExt_failed)) + case .alreadyInstalled: + // should never happen, since checked the condition above + await send(.setStep(.installSysExt_failed)) + case .activationRequestFailed, + .failedToGetBundleIdentifier, + .failedToLoadConfig, + .failedToSaveConfig: + await send(.setStep(.installSysExt_failed)) + } + case .errorLoadingConfig, .unknown: + await send(.setStep(.installSysExt_failed)) + case .installedAndRunning: + await send(.setStep(.installSysExt_success)) + case .installedButNotRunning: + if await systemExtension.start() == .installedAndRunning { + log("non-running sys ext started successfully", "d0021f5d") + await send(.setStep(.installSysExt_success)) + } else { + // TODO: should we try to replace once? + await send(.setStep(.installSysExt_failed)) + } + } + } + + case .webview(.primaryBtnClicked) where step == .installSysExt_allow, + .webview(.secondaryBtnClicked) where step == .installSysExt_allow: + return .exec { send in + let state = await systemExtension.state() + log("\(action) from .installSysExt_allow, state=\(state)", "b0e6e683") + if state == .installedAndRunning { + await send(.setStep(.installSysExt_success)) + } else { + await send(.setStep(.installSysExt_failed)) + } + } + + case .webview(.primaryBtnClicked) where step == .installSysExt_failed: + log(step, action, "2e246f1d") + state.step = .installSysExt_explain + return .none + + case .webview(.primaryBtnClicked) where step == .installSysExt_success, + .webview(.secondaryBtnClicked) where step == .installSysExt_failed: + log(step, action, "78bded66") + state.step = .locateMenuBarIcon + return .exec { send in + await send(.delegate(.onboardingConfigComplete)) + } + + case .webview(.primaryBtnClicked) where step == .locateMenuBarIcon: + log(step, action, "d0a159fd") + state.step = .viewHealthCheck + return .none + + case .webview(.primaryBtnClicked) where step == .viewHealthCheck: + log(step, action, "5c73a171") + state.step = .howToUseGertrude + return .none + + case .webview(.primaryBtnClicked) where step == .howToUseGertrude: + log(step, action, "eb044990") + state.step = .finish + return .none + + case .webview(.primaryBtnClicked) where step == .finish, + .closeWindow, + .webview(.closeWindow): + log(step, action, "2760db29") + state.windowOpen = false + guard state.connectChildRequest.isSucceeded else { return .none } + return .exec { send in + if await app.isLaunchAtLoginEnabled() == false { + await app.enableLaunchAtLogin() + } + if step < .locateMenuBarIcon { + // unexpected early bail, so we need to start protection + await send(.delegate(.onboardingConfigComplete)) + } + } + + case .webview(.primaryBtnClicked): + assertionFailure("Unhandled primary button click") + unexpectedError(id: "56bce346", detail: "step: \(step)") + state.step = step.primaryFallbackNextStep + return .none + + case .webview(.secondaryBtnClicked): + assertionFailure("Unhandled secondary button click") + unexpectedError(id: "22bfde1a", detail: "step: \(step)") + state.step = step.secondaryFallbackNextStep + return .none + + case .setStep(let step): + state.step = step + return .none + + case .webview(.infoModalOpened(let step, let detail)): + log("info modal opened at .\(step), detail=\(detail ?? "(nil)")", "f77ef50c") + return .none + + case .delegate: + return .none + } + } + + func nextRequiredStage(from current: State.Step, _ send: Send) async { + if current < .allowNotifications_start { + if await device.notificationsSetting() != .alert { + log("notifications not .alert yet", "ec99a6ea") + return await send(.setStep(.allowNotifications_start)) + } + log("notifications already .alert, skipping stage", "f2988b3c") + } + + if current < .allowScreenshots_required { + if await monitoring.screenRecordingPermissionGranted() == false { + log("screen recording not granted yet", "3edcf34f") + return await send(.setStep(.allowScreenshots_required)) + } + log("screenshots already allowed, skipping stage", "6e2e204c") + } + + // if we're past the screenshot stage, we know we don't want to resume again + // so, defensively always remove the onboarding resume state at this point + await send(.delegate(.saveForResume(nil))) + + // checking keylogging pops up the system prompt, so we always + // have to land them on this screen once, and test later. + // if they click "next" from .allowKeylogging_required, and the perms + // have already been granted, they'll move straight on, and not see a prompt + if current < .allowKeylogging_required { + log("can't test keylogging yet, go to required", "fd0bfa95") + return await send(.setStep(.allowKeylogging_required)) + } + + if current == .allowKeylogging_required { + if await monitoring.keystrokeRecordingPermissionGranted() == false { + log("keylogging not granted yet", "5d5275e5") + return await send(.setStep(.allowKeylogging_grant)) + } + log("keylogging already allowed, skipping stage", "51ed2be8") + } + + if await systemExtension.state() != .installedAndRunning { + log("sys ext not installed and running yet", "b493ebde") + return await send(.setStep(.installSysExt_explain)) + } + + log("sys ext already installed and running, skipping stage", "b0e6e683") + + await send(.delegate(.onboardingConfigComplete)) + await send(.setStep(.locateMenuBarIcon)) + } + } +} + +extension OnboardingFeature.State.MacUser { + init(_ user: MacOSUser) { + id = user.id + name = user.name + isAdmin = user.type == .admin + } +} + +extension OnboardingFeature.Reducer { + func eventMeta() -> String { + "os: \(device.osVersion().name), sn: \(device.serialNumber() ?? ""), time: \(Date())" + } + + func log(_ msg: String, _ id: String) { + #if !DEBUG + Task { interestingEvent(id: id, "[onboarding]: \(msg), \(eventMeta())") } + #else + if ProcessInfo.processInfo.environment["SWIFT_DETERMINISTIC_HASHING"] == nil { + print("\n[onboarding]: `\(id)` \(msg), \(eventMeta())\n") + } + #endif + } + + func log(_ step: State.Step, _ action: Action, _ id: String) { + let shortAction = "\(action)" + .replacingOccurrences(of: "App.OnboardingFeature.Action.View", with: "") + log("received .\(shortAction) from step .\(step)", id) + } +} diff --git a/macapp/App/Sources/App/Onboarding/OnboardingState+View.swift b/macapp/App/Sources/App/Onboarding/OnboardingState+View.swift new file mode 100644 index 00000000..bfe40d80 --- /dev/null +++ b/macapp/App/Sources/App/Onboarding/OnboardingState+View.swift @@ -0,0 +1,24 @@ +import Dependencies + +extension OnboardingFeature.State { + struct View: Equatable, Encodable, Sendable { + var os: MacOSVersion.DocumentationGroup + var windowOpen: Bool + var step: Step + var userRemediationStep: MacUser.RemediationStep? + var currentUser: MacUser? + var connectChildRequest: PayloadRequestState + var users: [MacUser] + + init(state: AppReducer.State) { + @Dependency(\.device) var device + os = device.osVersion().documentationGroup + windowOpen = state.onboarding.windowOpen + step = state.onboarding.step + userRemediationStep = state.onboarding.userRemediationStep + currentUser = state.onboarding.currentUser + connectChildRequest = state.onboarding.connectChildRequest + users = state.onboarding.users + } + } +} diff --git a/macapp/App/Sources/App/Onboarding/OnboardingStep.swift b/macapp/App/Sources/App/Onboarding/OnboardingStep.swift new file mode 100644 index 00000000..e7609b85 --- /dev/null +++ b/macapp/App/Sources/App/Onboarding/OnboardingStep.swift @@ -0,0 +1,190 @@ + +extension OnboardingFeature.State { + enum Step: String, Equatable, Codable { + case welcome + + // account + case confirmGertrudeAccount + case noGertrudeAccount + + // os user type + case macosUserAccountType + + // connection + case getChildConnectionCode + case connectChild + + // notifications + case allowNotifications_start + case allowNotifications_grant + case allowNotifications_failed + + // screenshots + case allowScreenshots_required + case allowScreenshots_grantAndRestart + // these two states exist to give us a landing spot for resuming + // onboarding after the grant -> quit & reopen flow + case allowScreenshots_failed + case allowScreenshots_success + + // keylogging + case allowKeylogging_required + case allowKeylogging_grant + case allowKeylogging_failed + + // sys ext + case installSysExt_explain + case installSysExt_allow + case installSysExt_failed + case installSysExt_success + + // wrap up + case locateMenuBarIcon + case viewHealthCheck + case howToUseGertrude + case finish + } +} + +extension OnboardingFeature.State.Step { + var primaryFallbackNextStep: Self { + switch self { + case .welcome: + return .confirmGertrudeAccount + case .confirmGertrudeAccount: + return .macosUserAccountType + case .noGertrudeAccount: + return .macosUserAccountType + case .macosUserAccountType: + return .getChildConnectionCode + case .getChildConnectionCode: + return .connectChild + case .connectChild: + return .allowNotifications_start + case .allowNotifications_start: + return .allowNotifications_grant + case .allowNotifications_grant: + return .allowScreenshots_required + case .allowNotifications_failed: + return .allowScreenshots_required + case .allowScreenshots_required: + return .allowScreenshots_grantAndRestart + case .allowScreenshots_grantAndRestart: + return .allowScreenshots_success + case .allowScreenshots_failed: + return .allowKeylogging_required + case .allowScreenshots_success: + return .allowKeylogging_required + case .allowKeylogging_required: + return .allowKeylogging_grant + case .allowKeylogging_grant: + return .installSysExt_explain + case .allowKeylogging_failed: + return .installSysExt_explain + case .installSysExt_explain: + return .installSysExt_allow + case .installSysExt_allow: + return .installSysExt_success + case .installSysExt_failed: + return .installSysExt_success + case .installSysExt_success: + return .locateMenuBarIcon + case .locateMenuBarIcon: + return .viewHealthCheck + case .viewHealthCheck: + return .howToUseGertrude + case .howToUseGertrude: + return .finish + case .finish: + return .finish + } + } + + var secondaryFallbackNextStep: Self { + switch self { + case .welcome: + return .welcome + case .confirmGertrudeAccount: + return .welcome + case .noGertrudeAccount: + return .confirmGertrudeAccount + case .macosUserAccountType: + return .confirmGertrudeAccount + case .getChildConnectionCode: + return .macosUserAccountType + case .connectChild: + return .getChildConnectionCode + case .allowNotifications_start: + return .connectChild + case .allowNotifications_grant: + return .allowNotifications_start + case .allowNotifications_failed: + return .allowNotifications_grant + case .allowScreenshots_required: + return .allowNotifications_start + case .allowScreenshots_grantAndRestart: + return .allowScreenshots_required + case .allowScreenshots_failed: + return .allowScreenshots_required + case .allowScreenshots_success: + return .allowScreenshots_grantAndRestart + case .allowKeylogging_required: + return .allowScreenshots_required + case .allowKeylogging_grant: + return .allowKeylogging_required + case .allowKeylogging_failed: + return .allowKeylogging_required + case .installSysExt_explain: + return .allowKeylogging_required + case .installSysExt_allow: + return .installSysExt_explain + case .installSysExt_failed: + return .installSysExt_explain + case .installSysExt_success: + return .installSysExt_explain + case .locateMenuBarIcon: + return .installSysExt_explain + case .viewHealthCheck: + return .locateMenuBarIcon + case .howToUseGertrude: + return .viewHealthCheck + case .finish: + return .howToUseGertrude + } + } +} + +extension OnboardingFeature.State.Step: Comparable { + public static func < (lhs: Self, rhs: Self) -> Bool { + lhs.asInt < rhs.asInt + } + + private var asInt: Int { + switch self { + case .welcome: return 0 + case .confirmGertrudeAccount: return 5 + case .noGertrudeAccount: return 10 + case .macosUserAccountType: return 15 + case .getChildConnectionCode: return 20 + case .connectChild: return 25 + case .allowNotifications_start: return 30 + case .allowNotifications_grant: return 35 + case .allowNotifications_failed: return 40 + case .allowScreenshots_required: return 45 + case .allowScreenshots_grantAndRestart: return 55 + case .allowScreenshots_failed: return 60 + case .allowScreenshots_success: return 65 + case .allowKeylogging_required: return 70 + case .allowKeylogging_grant: return 80 + case .allowKeylogging_failed: return 85 + case .installSysExt_explain: return 90 + case .installSysExt_allow: return 95 + case .installSysExt_failed: return 100 + case .installSysExt_success: return 105 + case .locateMenuBarIcon: return 110 + case .viewHealthCheck: return 115 + case .howToUseGertrude: return 120 + case .finish: return 125 + } + } +} diff --git a/macapp/App/Sources/App/Onboarding/OnboardingWindow.swift b/macapp/App/Sources/App/Onboarding/OnboardingWindow.swift new file mode 100644 index 00000000..28a39668 --- /dev/null +++ b/macapp/App/Sources/App/Onboarding/OnboardingWindow.swift @@ -0,0 +1,35 @@ +import AppKit +import Combine +import ComposableArchitecture + +class OnboardingWindow: AppWindow { + typealias Feature = OnboardingFeature + typealias State = Feature.State.View + typealias Action = Feature.Action + typealias WebViewAction = Feature.Action.View + + var title = "Onboarding" + var screen = "Onboarding" + var openPublisher: StorePublisher + var cancellables = Set() + var windowDelegate = AppWindowDelegate() + var viewStore: ViewStore + var window: NSWindow? + var closeWindowAction = Action.closeWindow + var initialSize = NSRect(x: 0, y: 0, width: 900, height: 700) + var minSize = NSSize(width: 800, height: 600) + var showTitleBar = false + + @Dependency(\.mainQueue) var mainQueue + @Dependency(\.app) var appClient + + @MainActor init(store: Store) { + viewStore = ViewStore(store, observe: OnboardingFeature.State.View.init) + openPublisher = viewStore.publisher.windowOpen + bind() + } + + func embed(_ webviewAction: WebViewAction) -> Action { + .webview(webviewAction) + } +} diff --git a/macapp/App/Sources/App/PqlError+Message.swift b/macapp/App/Sources/App/PqlError+Message.swift index f859cd2b..f908dfd1 100644 --- a/macapp/App/Sources/App/PqlError+Message.swift +++ b/macapp/App/Sources/App/PqlError+Message.swift @@ -2,15 +2,17 @@ import PairQL extension Error { func userMessage(_ tags: [PqlError.AppTag: String] = [:], generic: String? = nil) -> String { + let fallback = + "Sorry, something went wrong. Please try again, or contact help if the problem persists." guard let pqlError = self as? PqlError else { - return generic ?? "Please try again, or contact help if the problem persists." + return generic ?? fallback } if let appTag = pqlError.appTag, let message = tags[appTag] { return message } else if let userMessage = pqlError.userMessage { return userMessage } else { - return generic ?? "Please try again, or contact help if the problem persists." + return generic ?? fallback } } } diff --git a/macapp/App/Sources/App/RequestState.swift b/macapp/App/Sources/App/RequestState.swift index 27a03509..d2fc062e 100644 --- a/macapp/App/Sources/App/RequestState.swift +++ b/macapp/App/Sources/App/RequestState.swift @@ -12,11 +12,30 @@ enum PayloadRequestState { case ongoing case succeeded(payload: T) case failed(error: E) + + var isSucceeded: Bool { + switch self { + case .succeeded: + return true + default: + return false + } + } + + var isFailed: Bool { + switch self { + case .failed: + return true + default: + return false + } + } } extension RequestState: Equatable where E: Equatable {} extension RequestState: Sendable where E: Sendable {} extension PayloadRequestState: Equatable where T: Equatable, E: Equatable {} +extension PayloadRequestState: Sendable where T: Sendable, E: Sendable {} extension RequestState: Codable where E: Codable { private struct _NamedCase: Codable { @@ -67,3 +86,59 @@ extension RequestState: Codable where E: Codable { } } } + +extension PayloadRequestState: Codable where T: Codable, E: Codable { + private struct _NamedCase: Codable { + var `case`: String + static func extract(from decoder: Decoder) throws -> String { + let container = try decoder.singleValueContainer() + return try container.decode(_NamedCase.self).case + } + } + + private struct _TypeScriptDecodeError: Error { + var message: String + } + + private struct _CaseSucceeded: Codable { + var `case` = "succeeded" + var payload: T + } + + private struct _CaseFailed: Codable { + var `case` = "failed" + var error: E + } + + func encode(to encoder: Encoder) throws { + switch self { + case .succeeded(let payload): + try _CaseSucceeded(payload: payload).encode(to: encoder) + case .failed(let error): + try _CaseFailed(error: error).encode(to: encoder) + case .idle: + try _NamedCase(case: "idle").encode(to: encoder) + case .ongoing: + try _NamedCase(case: "ongoing").encode(to: encoder) + } + } + + init(from decoder: Decoder) throws { + let caseName = try _NamedCase.extract(from: decoder) + let container = try decoder.singleValueContainer() + switch caseName { + case "succeeded": + let value = try container.decode(_CaseSucceeded.self) + self = .succeeded(payload: value.payload) + case "failed": + let value = try container.decode(_CaseFailed.self) + self = .failed(error: value.error) + case "idle": + self = .idle + case "ongoing": + self = .ongoing + default: + throw _TypeScriptDecodeError(message: "Unexpected case name: `\(caseName)`") + } + } +} diff --git a/macapp/App/Sources/App/RequestSuspension/RequestSuspensionWindow.swift b/macapp/App/Sources/App/RequestSuspension/RequestSuspensionWindow.swift index a42e95af..75bd66c9 100644 --- a/macapp/App/Sources/App/RequestSuspension/RequestSuspensionWindow.swift +++ b/macapp/App/Sources/App/RequestSuspension/RequestSuspensionWindow.swift @@ -19,6 +19,7 @@ class RequestSuspensionWindow: AppWindow { var closeWindowAction = Action.closeWindow var initialSize = NSRect(x: 0, y: 0, width: 680, height: 360) var minSize = NSSize(width: 600, height: 360) + var showTitleBar = false // above almost everything, but below filter installation system prompt var windowLevel = NSWindow.Level.modalPanel diff --git a/macapp/App/Sources/App/Types.swift b/macapp/App/Sources/App/Types.swift index 1af06b7b..4cad1fee 100644 --- a/macapp/App/Sources/App/Types.swift +++ b/macapp/App/Sources/App/Types.swift @@ -37,16 +37,12 @@ extension AdminAuthenticating where Action == AppReducer.Action { } } -enum Heartbeat { - enum Interval: Equatable, Sendable { - case everyMinute - case everyFiveMinutes - case everyTwentyMinutes - case everyHour - case everySixHours - } - - enum CancelId { case interval } +enum HeartbeatInterval: Equatable, Sendable { + case everyMinute + case everyFiveMinutes + case everyTwentyMinutes + case everyHour + case everySixHours } enum NotificationsSetting: String, Equatable, Codable { @@ -54,3 +50,7 @@ enum NotificationsSetting: String, Equatable, Codable { case banner case alert } + +extension URL { + static let contact = URL(string: "https://gertrude.app/contact")! +} diff --git a/macapp/App/Sources/App/UserConnectionFeature.swift b/macapp/App/Sources/App/UserConnectionFeature.swift index ab347ab2..a7d3030c 100644 --- a/macapp/App/Sources/App/UserConnectionFeature.swift +++ b/macapp/App/Sources/App/UserConnectionFeature.swift @@ -22,6 +22,7 @@ enum UserConnectionFeature: Feature { struct RootReducer: RootReducing { @Dependency(\.api) var api + @Dependency(\.app) var app @Dependency(\.storage) var storage @Dependency(\.filterXpc) var xpc } @@ -30,11 +31,9 @@ enum UserConnectionFeature: Feature { extension UserConnectionFeature.Reducer { func reduce(into state: inout State, action: Action) -> Effect { switch action { - case .connect(.success(let user)): + case .connect(.success): state = .established(welcomeDismissed: false) - return .exec { _ in - await api.setUserToken(user.token) - } + return .none case .connect(.failure(let error)): let codeNotFound = "Code not found, or expired. Try reentering, or create a new code." @@ -76,12 +75,17 @@ extension UserConnectionFeature.RootReducer { } } - func disconnectUser(persisting updatedState: Persistent.State) -> Effect { - .exec { send in - await api.clearUserToken() - try await storage.savePersistentState(updatedState) - _ = await xpc.disconnectUser() - } + func disconnectUser(persisting updated: Persistent.State) -> Effect { + .merge( + .exec { _ in + await api.clearUserToken() + try await storage.savePersistentState(updated) + _ = await xpc.disconnectUser() + await app.disableLaunchAtLogin() + }, + .cancel(id: AppReducer.CancelId.heartbeatInterval), + .cancel(id: AppReducer.CancelId.websocketMessages) + ) } } diff --git a/macapp/App/Sources/App/WebSocketFeature.swift b/macapp/App/Sources/App/WebSocketFeature.swift index ce042b90..7d08f287 100644 --- a/macapp/App/Sources/App/WebSocketFeature.swift +++ b/macapp/App/Sources/App/WebSocketFeature.swift @@ -34,9 +34,8 @@ extension WebSocketFeature.RootReducer { } } - case .loadedPersistentState(.some(let persistent)): + case .startProtecting(user: let user): guard state.admin.accountStatus != .inactive else { return .none } - guard let user = persistent.user else { return .none } return connect(user) case .filter(.receivedState(let filterState)): @@ -50,6 +49,8 @@ extension WebSocketFeature.RootReducer { case .application(.willSleep), .application(.willTerminate), + .websocket(.receivedMessage(.userDeleted)), + .history(.userConnection(.disconnectMissingUser)), .adminAuthed(.adminWindow(.webview(.confirmQuitAppClicked))), .adminAuthed(.adminWindow(.webview(.disconnectUserClicked))): return .exec { _ in @@ -104,8 +105,17 @@ extension WebSocketFeature.RootReducer { ) } - case .receivedMessage: - return .none + case .receivedMessage(.userDeleted): + return .none // handled above, with other disconnect-like actions + + case .receivedMessage(.userUpdated): + return .none // handled by user feature, which triggers a checkin + + case .receivedMessage(.suspendFilter): + return .none // handled by filter feature + + case .receivedMessage(.filterSuspensionRequestDecided): + return .none // handled by filter feature AND monitoring feature } case .adminAuthed(.adminWindow(.webview(.advanced(.websocketEndpointSet(let url))))): diff --git a/macapp/App/Sources/App/WebViewController.swift b/macapp/App/Sources/App/WebViewController.swift index 988ef9ec..505f70ef 100644 --- a/macapp/App/Sources/App/WebViewController.swift +++ b/macapp/App/Sources/App/WebViewController.swift @@ -11,6 +11,7 @@ class WebViewController: var webView: WKWebView! var isReady: CurrentValueSubject = .init(false) var send: (Action) -> Void = { _ in } + var withTitleBar = false @Dependency(\.app) var app @@ -27,7 +28,16 @@ class WebViewController: func loadWebView(screen: String) { let webConfiguration = WKWebViewConfiguration() webConfiguration.setValue(true, forKey: "allowUniversalAccessFromFileURLs") - webView = WKWebView(frame: .zero, configuration: webConfiguration) + if #available(macOS 12.3, *) { + // allow embedded youtube videos to go fullscreen + webConfiguration.preferences.isElementFullscreenEnabled = true + } + + if withTitleBar { + webView = WKWebView(frame: .zero, configuration: webConfiguration) + } else { + webView = NoTitleWebView(frame: .zero, configuration: webConfiguration) + } webView.uiDelegate = self webView.setValue(false, forKey: "drawsBackground") @@ -73,10 +83,13 @@ class WebViewController: await self?.send(action: action) } } catch { + let actionType = String(reflecting: Action.self) + let errMsg = "ERR: could not decode action from webview: \(message) as \(actionType)\n" #if DEBUG - let actionType = String(reflecting: Action.self) - print("ERR: could not decode action from webview: \(message) as \(actionType)\n") + print(errMsg) print(error) + #else + unexpectedError(id: "0645e891", detail: errMsg) #endif } } @@ -91,6 +104,10 @@ class WebViewController: } } +class NoTitleWebView: WKWebView { + override var mouseDownCanMoveWindow: Bool { true } +} + typealias WebViewControllerOf = WebViewController< F.Reducer.State, F.Reducer.Action diff --git a/macapp/App/Sources/ClientInterfaces/ApiClient.swift b/macapp/App/Sources/ClientInterfaces/ApiClient.swift index cabb54a7..28928dbe 100644 --- a/macapp/App/Sources/ClientInterfaces/ApiClient.swift +++ b/macapp/App/Sources/ClientInterfaces/ApiClient.swift @@ -84,6 +84,20 @@ public extension ApiClient { extension ApiClient: TestDependencyKey { public static let testValue = Self( + checkIn: unimplemented("ApiClient.checkIn"), + clearUserToken: unimplemented("ApiClient.clearUserToken"), + connectUser: unimplemented("ApiClient.connectUser"), + createKeystrokeLines: unimplemented("ApiClient.createKeystrokeLines"), + createSuspendFilterRequest: unimplemented("ApiClient.createSuspendFilterRequest"), + createUnlockRequests: unimplemented("ApiClient.createUnlockRequests"), + logInterestingEvent: unimplemented("ApiClient.logInterestingEvent"), + recentAppVersions: unimplemented("ApiClient.recentAppVersions"), + setAccountActive: unimplemented("ApiClient.setAccountActive"), + setUserToken: unimplemented("ApiClient.setUserToken"), + uploadScreenshot: unimplemented("ApiClient.uploadScreenshot") + ) + + public static let mock = Self( checkIn: { _ in throw Error.unexpectedError(statusCode: 999) }, clearUserToken: {}, connectUser: { _ in throw Error.unexpectedError(statusCode: 888) }, @@ -98,24 +112,6 @@ extension ApiClient: TestDependencyKey { ) } -#if DEBUG - public extension ApiClient { - static let failing = Self( - checkIn: unimplemented("ApiClient.checkIn"), - clearUserToken: unimplemented("ApiClient.clearUserToken"), - connectUser: unimplemented("ApiClient.connectUser"), - createKeystrokeLines: unimplemented("ApiClient.createKeystrokeLines"), - createSuspendFilterRequest: unimplemented("ApiClient.createSuspendFilterRequest"), - createUnlockRequests: unimplemented("ApiClient.createUnlockRequests"), - logInterestingEvent: unimplemented("ApiClient.logInterestingEvent"), - recentAppVersions: unimplemented("ApiClient.recentAppVersions"), - setAccountActive: unimplemented("ApiClient.setAccountActive"), - setUserToken: unimplemented("ApiClient.setUserToken"), - uploadScreenshot: unimplemented("ApiClient.uploadScreenshot") - ) - } -#endif - public extension DependencyValues { var api: ApiClient { get { self[ApiClient.self] } diff --git a/macapp/App/Sources/ClientInterfaces/AppClient.swift b/macapp/App/Sources/ClientInterfaces/AppClient.swift index 0b55a7b2..d9b7fa9b 100644 --- a/macapp/App/Sources/ClientInterfaces/AppClient.swift +++ b/macapp/App/Sources/ClientInterfaces/AppClient.swift @@ -37,6 +37,15 @@ public struct AppClient: Sendable { extension AppClient: TestDependencyKey { public static let testValue = Self( + colorScheme: unimplemented("AppClient.colorScheme"), + colorSchemeChanges: unimplemented("AppClient.colorSchemeChanges"), + disableLaunchAtLogin: unimplemented("AppClient.disableLaunchAtLogin"), + enableLaunchAtLogin: unimplemented("AppClient.enableLaunchAtLogin"), + isLaunchAtLoginEnabled: unimplemented("AppClient.isLaunchAtLoginEnabled"), + installedVersion: unimplemented("AppClient.installedVersion"), + quit: unimplemented("AppClient.quit") + ) + public static let mock = Self( colorScheme: { .light }, colorSchemeChanges: { Empty().eraseToAnyPublisher() }, disableLaunchAtLogin: {}, diff --git a/macapp/App/Sources/ClientInterfaces/FilterExtensionClient.swift b/macapp/App/Sources/ClientInterfaces/FilterExtensionClient.swift index 54ca4b16..60d23c56 100644 --- a/macapp/App/Sources/ClientInterfaces/FilterExtensionClient.swift +++ b/macapp/App/Sources/ClientInterfaces/FilterExtensionClient.swift @@ -11,6 +11,7 @@ public struct FilterExtensionClient: Sendable { public var replace: @Sendable () async -> FilterInstallResult public var state: @Sendable () async -> FilterExtensionState public var install: @Sendable () async -> FilterInstallResult + public var installOverridingTimeout: @Sendable (_ timeout: Int) async -> FilterInstallResult public var stateChanges: @Sendable () -> AnyPublisher public var uninstall: @Sendable () async -> Bool @@ -23,6 +24,7 @@ public struct FilterExtensionClient: Sendable { replace: @escaping @Sendable () async -> FilterInstallResult, state: @escaping @Sendable () async -> FilterExtensionState, install: @escaping @Sendable () async -> FilterInstallResult, + installOverridingTimeout: @escaping @Sendable (_ timeout: Int) async -> FilterInstallResult, stateChanges: @escaping @Sendable () -> AnyPublisher, uninstall: @escaping @Sendable () async -> Bool ) { @@ -34,6 +36,7 @@ public struct FilterExtensionClient: Sendable { self.replace = replace self.state = state self.install = install + self.installOverridingTimeout = installOverridingTimeout self.stateChanges = stateChanges self.uninstall = uninstall } @@ -41,6 +44,20 @@ public struct FilterExtensionClient: Sendable { extension FilterExtensionClient: TestDependencyKey { public static let testValue = Self( + setup: unimplemented("FilterExtensionClient.setup"), + start: unimplemented("FilterExtensionClient.start"), + stop: unimplemented("FilterExtensionClient.stop"), + reinstall: unimplemented("FilterExtensionClient.reinstall"), + restart: unimplemented("FilterExtensionClient.restart"), + replace: unimplemented("FilterExtensionClient.replace"), + state: unimplemented("FilterExtensionClient.state"), + install: unimplemented("FilterExtensionClient.install"), + installOverridingTimeout: unimplemented("FilterExtensionClient.installOverridingTimeout"), + stateChanges: unimplemented("FilterExtensionClient.stateChanges"), + uninstall: unimplemented("FilterExtensionClient.uninstall") + ) + + public static let mock = Self( setup: { .installedAndRunning }, start: { .installedAndRunning }, stop: { .installedButNotRunning }, @@ -49,6 +66,7 @@ extension FilterExtensionClient: TestDependencyKey { replace: { .installedSuccessfully }, state: { .installedAndRunning }, install: { .installedSuccessfully }, + installOverridingTimeout: { _ in .installedSuccessfully }, stateChanges: { Empty().eraseToAnyPublisher() }, uninstall: { true } ) diff --git a/macapp/App/Sources/ClientInterfaces/FilterXPCClient.swift b/macapp/App/Sources/ClientInterfaces/FilterXPCClient.swift index c6a0f678..d2885ac0 100644 --- a/macapp/App/Sources/ClientInterfaces/FilterXPCClient.swift +++ b/macapp/App/Sources/ClientInterfaces/FilterXPCClient.swift @@ -64,6 +64,23 @@ public struct FilterXPCClient: Sendable { extension FilterXPCClient: TestDependencyKey { public static var testValue: Self { + .init( + establishConnection: unimplemented("FilterXPCClient.establishConnection"), + checkConnectionHealth: unimplemented("FilterXPCClient.checkConnectionHealth"), + disconnectUser: unimplemented("FilterXPCClient.disconnectUser"), + endFilterSuspension: unimplemented("FilterXPCClient.endFilterSuspension"), + requestAck: unimplemented("FilterXPCClient.requestAck"), + requestExemptUserIds: unimplemented("FilterXPCClient.requestExemptUserIds"), + sendDeleteAllStoredState: unimplemented("FilterXPCClient.sendDeleteAllStoredState"), + sendUserRules: unimplemented("FilterXPCClient.sendUserRules"), + setBlockStreaming: unimplemented("FilterXPCClient.setBlockStreaming"), + setUserExemption: unimplemented("FilterXPCClient.setUserExemption"), + suspendFilter: unimplemented("FilterXPCClient.suspendFilter"), + events: unimplemented("FilterXPCClient.events") + ) + } + + public static var mock: Self { .init( establishConnection: { .success(()) }, checkConnectionHealth: { .success(()) }, diff --git a/macapp/App/Sources/ClientInterfaces/WebSocketClient.swift b/macapp/App/Sources/ClientInterfaces/WebSocketClient.swift index 56eac9a7..6685dddc 100644 --- a/macapp/App/Sources/ClientInterfaces/WebSocketClient.swift +++ b/macapp/App/Sources/ClientInterfaces/WebSocketClient.swift @@ -46,6 +46,14 @@ extension WebSocketClient: EndpointOverridable { extension WebSocketClient: TestDependencyKey { public static let testValue = Self( + connect: unimplemented("WebSocketClient.connect"), + disconnect: unimplemented("WebSocketClient.disconnect"), + receive: unimplemented("WebSocketClient.receive"), + send: unimplemented("WebSocketClient.send"), + state: unimplemented("WebSocketClient.state") + ) + + public static let mock = Self( connect: { _ in .connected }, disconnect: {}, receive: { Empty().eraseToAnyPublisher() }, diff --git a/macapp/App/Sources/Core/UserDefaultsClient.swift b/macapp/App/Sources/Core/UserDefaultsClient.swift index aca97fb4..1c4a7851 100644 --- a/macapp/App/Sources/Core/UserDefaultsClient.swift +++ b/macapp/App/Sources/Core/UserDefaultsClient.swift @@ -44,6 +44,12 @@ extension UserDefaultsClient: DependencyKey { extension UserDefaultsClient: TestDependencyKey { public static let testValue = Self( + setString: unimplemented("UserDefaultsClient.setString"), + getString: unimplemented("UserDefaultsClient.getString"), + remove: unimplemented("UserDefaultsClient.remove"), + removeAll: unimplemented("UserDefaultsClient.removeAll") + ) + public static let mock = Self( setString: { _, _ in }, getString: { _ in nil }, remove: { _ in }, diff --git a/macapp/App/Sources/Filter/Dependencies/ExtensionClient.swift b/macapp/App/Sources/Filter/Dependencies/ExtensionClient.swift index 33913eeb..84d31d66 100644 --- a/macapp/App/Sources/Filter/Dependencies/ExtensionClient.swift +++ b/macapp/App/Sources/Filter/Dependencies/ExtensionClient.swift @@ -16,6 +16,9 @@ extension ExtensionClient: DependencyKey { extension ExtensionClient: TestDependencyKey { static let testValue = Self( + version: unimplemented("ExtensionClient.version") + ) + static let mock = Self( version: { "1.0.0" } ) } diff --git a/macapp/App/Sources/Filter/Dependencies/SecurityClient.swift b/macapp/App/Sources/Filter/Dependencies/SecurityClient.swift index d31c8ce9..790d0ebc 100644 --- a/macapp/App/Sources/Filter/Dependencies/SecurityClient.swift +++ b/macapp/App/Sources/Filter/Dependencies/SecurityClient.swift @@ -87,6 +87,10 @@ extension SecurityClient: DependencyKey { extension SecurityClient: TestDependencyKey { public static let testValue = Self( + userIdFromAuditToken: unimplemented("SecurityClient.userIdFromAuditToken"), + rootAppFromAuditToken: unimplemented("SecurityClient.rootAppFromAuditToken") + ) + public static let mock = Self( userIdFromAuditToken: { _ in nil }, rootAppFromAuditToken: { _ in (nil, nil) } ) diff --git a/macapp/App/Sources/Filter/Dependencies/StorageClient.swift b/macapp/App/Sources/Filter/Dependencies/StorageClient.swift index ac073ba2..c41a07e2 100644 --- a/macapp/App/Sources/Filter/Dependencies/StorageClient.swift +++ b/macapp/App/Sources/Filter/Dependencies/StorageClient.swift @@ -47,6 +47,13 @@ extension StorageClient: DependencyKey { extension StorageClient: TestDependencyKey { static let testValue = Self( + savePersistentState: unimplemented("StorageClient.savePersistentState"), + loadPersistentState: unimplemented("StorageClient.loadPersistentState"), + loadPersistentStateSync: unimplemented("StorageClient.loadPersistentStateSync"), + deleteAllPersistentState: unimplemented("StorageClient.deleteAllPersistentState"), + deleteAll: unimplemented("StorageClient.deleteAll") + ) + static let mock = Self( savePersistentState: { _ in }, loadPersistentState: { nil }, loadPersistentStateSync: { nil }, diff --git a/macapp/App/Sources/Filter/Dependencies/XPCClient.swift b/macapp/App/Sources/Filter/Dependencies/XPCClient.swift index 662534e1..c943ee80 100644 --- a/macapp/App/Sources/Filter/Dependencies/XPCClient.swift +++ b/macapp/App/Sources/Filter/Dependencies/XPCClient.swift @@ -38,6 +38,13 @@ extension XPCClient: DependencyKey { extension XPCClient: TestDependencyKey { static let testValue = Self( + notifyFilterSuspensionEnded: unimplemented("XPCClient.notifyFilterSuspensionEnded"), + startListener: unimplemented("XPCClient.startListener"), + stopListener: unimplemented("XPCClient.stopListener"), + sendBlockedRequest: unimplemented("XPCClient.sendBlockedRequest"), + events: unimplemented("XPCClient.events") + ) + static let mock = Self( notifyFilterSuspensionEnded: { _ in }, startListener: {}, stopListener: {}, diff --git a/macapp/App/Sources/LiveFilterExtensionClient/FilterManager.swift b/macapp/App/Sources/LiveFilterExtensionClient/FilterManager.swift index 858ed0c5..64050487 100644 --- a/macapp/App/Sources/LiveFilterExtensionClient/FilterManager.swift +++ b/macapp/App/Sources/LiveFilterExtensionClient/FilterManager.swift @@ -78,7 +78,7 @@ final class FilterManager: NSObject { return await loadState() } - func installFilter() async -> FilterInstallResult { + func installFilter(timeout: Int? = nil) async -> FilterInstallResult { switch await loadState() { case .installedAndRunning, .installedButNotRunning: os_log("[G•] APP FilterManager.installFilter() already installed #1") @@ -92,10 +92,10 @@ final class FilterManager: NSObject { return .alreadyInstalled } - return await activateExtension() + return await activateExtension(timeout: timeout ?? 90) } - func activateExtension() async -> FilterInstallResult { + func activateExtension(timeout: Int) async -> FilterInstallResult { os_log("[G•] APP FilterManager.activateExtension()") system.requestExtensionActivation(self) @@ -134,7 +134,7 @@ final class FilterManager: NSObject { // no resolution after 90 seconds, user probably confused, missed a step case .idle, .configuring, .waitingForDelegateRequest: - if waited > 90 { + if waited > timeout { configureTask?.cancel() interestingEvent(id: "9ffabfe5", "status: \(currentStatus)") await activationRequest.setValue(.idle) @@ -156,7 +156,7 @@ final class FilterManager: NSObject { system.disableNEFilterManagerShared() defer { system.enableNEFilterManagerShared() } - return await activateExtension() + return await activateExtension(timeout: 90) } func uninstallFilter() async -> Bool { diff --git a/macapp/App/Sources/LiveFilterExtensionClient/LiveFilterExtensionClient.swift b/macapp/App/Sources/LiveFilterExtensionClient/LiveFilterExtensionClient.swift index 8309ea4a..ccf3d939 100644 --- a/macapp/App/Sources/LiveFilterExtensionClient/LiveFilterExtensionClient.swift +++ b/macapp/App/Sources/LiveFilterExtensionClient/LiveFilterExtensionClient.swift @@ -20,6 +20,7 @@ extension FilterExtensionClient: DependencyKey { replace: { await manager.replaceFilter() }, state: { await manager.loadState() }, install: { await manager.installFilter() }, + installOverridingTimeout: { await manager.installFilter(timeout: $0) }, stateChanges: { filterStateChanges.withValue { subject in Move(subject.eraseToAnyPublisher()) @@ -53,8 +54,8 @@ actor ThreadSafeFilterManager { await manager.replaceFilter() } - func installFilter() async -> FilterInstallResult { - await manager.installFilter() + func installFilter(timeout: Int? = nil) async -> FilterInstallResult { + await manager.installFilter(timeout: timeout) } func uninstallFilter() async -> Bool { diff --git a/macapp/App/Sources/LiveFilterExtensionClient/SystemClient.swift b/macapp/App/Sources/LiveFilterExtensionClient/SystemClient.swift index 6b9325b2..93e815f7 100644 --- a/macapp/App/Sources/LiveFilterExtensionClient/SystemClient.swift +++ b/macapp/App/Sources/LiveFilterExtensionClient/SystemClient.swift @@ -74,6 +74,19 @@ extension SystemClient: DependencyKey { extension SystemClient: TestDependencyKey { static let testValue = Self( + loadFilterConfiguration: unimplemented("SystemClient.loadFilterConfiguration"), + isNEFilterManagerSharedEnabled: unimplemented("SystemClient.isNEFilterManagerSharedEnabled"), + enableNEFilterManagerShared: unimplemented("SystemClient.enableNEFilterManagerShared"), + disableNEFilterManagerShared: unimplemented("SystemClient.disableNEFilterManagerShared"), + filterProviderConfiguration: unimplemented("SystemClient.filterProviderConfiguration"), + removeFilterConfiguration: unimplemented("SystemClient.removeFilterConfiguration"), + requestExtensionActivation: unimplemented("SystemClient.requestExtensionActivation"), + updateNEFilterManagerShared: unimplemented("SystemClient.updateNEFilterManagerShared"), + saveNEFilterManagerShared: unimplemented("SystemClient.saveNEFilterManagerShared"), + filterDidChangePublisher: unimplemented("SystemClient.filterDidChangePublisher") + ) + + static let mock = Self( loadFilterConfiguration: { .doesNotExistOrLoadedSuccessfully }, isNEFilterManagerSharedEnabled: { true }, enableNEFilterManagerShared: {}, diff --git a/macapp/App/Tests/AppTests/AdminWindowTests.swift b/macapp/App/Tests/AppTests/AdminWindowTests.swift index 332d61e5..33e227ee 100644 --- a/macapp/App/Tests/AppTests/AdminWindowTests.swift +++ b/macapp/App/Tests/AppTests/AdminWindowTests.swift @@ -38,7 +38,8 @@ import XExpect appVersion: "1.0.0", appUpdateReleaseChannel: .stable, filterVersion: "1.0.0", - user: nil + user: nil, + resumeOnboarding: nil )]) } diff --git a/macapp/App/Tests/AppTests/AppMigratorTests.swift b/macapp/App/Tests/AppTests/AppMigratorTests.swift index 63ef82fb..0deb379e 100644 --- a/macapp/App/Tests/AppTests/AppMigratorTests.swift +++ b/macapp/App/Tests/AppTests/AppMigratorTests.swift @@ -11,7 +11,7 @@ class AppMigratorTests: XCTestCase { typealias V1 = AppMigrator.Legacy.V1.StorageKey var testMigrator: AppMigrator { - AppMigrator(api: .failing, userDefaults: .failing) + AppMigrator(api: .testValue, userDefaults: .failing) } func testNoStoredDataAtAllReturnsNil() async { @@ -79,165 +79,174 @@ class AppMigratorTests: XCTestCase { } func testMigratesLegacyV1DataFromApiCallSuccess() async { - var migrator = testMigrator - - let apiUser = UserData( - id: .zeros, - token: .deadbeef, - deviceId: .twos, - name: "Big Mac", - keyloggingEnabled: true, - screenshotsEnabled: false, - screenshotFrequency: 6, - screenshotSize: 7, - connectedAt: Date(timeIntervalSince1970: 33) - ) - - let checkIn = spy( - on: CheckIn.Input.self, - returning: CheckIn.Output.mock { $0.userData = apiUser } - ) - migrator.api.checkIn = checkIn.fn - - let setApiToken = spy(on: UUID.self, returning: ()) - migrator.api.setUserToken = setApiToken.fn - - let setStringInvocations = LockIsolated<[Both]>([]) - migrator.userDefaults.setString = { key, value in - setStringInvocations.append(.init(key, value)) - } + await withDependencies { + $0.app.installedVersion = { "1.0.0" } + } operation: { + + var migrator = testMigrator + + let apiUser = UserData( + id: .zeros, + token: .deadbeef, + deviceId: .twos, + name: "Big Mac", + keyloggingEnabled: true, + screenshotsEnabled: false, + screenshotFrequency: 6, + screenshotSize: 7, + connectedAt: Date(timeIntervalSince1970: 33) + ) + + let checkIn = spy( + on: CheckIn.Input.self, + returning: CheckIn.Output.mock { $0.userData = apiUser } + ) + migrator.api.checkIn = checkIn.fn + + let setApiToken = spy(on: UUID.self, returning: ()) + migrator.api.setUserToken = setApiToken.fn + + let setStringInvocations = LockIsolated<[Both]>([]) + migrator.userDefaults.setString = { key, value in + setStringInvocations.append(.init(key, value)) + } - let getStringInvocations = LockIsolated<[String]>([]) - migrator.userDefaults.getString = { key in - getStringInvocations.append(key) - switch key { - case "persistent.state.v1": - return nil - case "persistent.state.v2": - return nil - case V1.userToken.namespaced: - return UUID.deadbeef.uuidString - case V1.installedAppVersion.namespaced: - return "1.77.88" - default: - XCTFail("Unexpected key: \(key)") - return nil + let getStringInvocations = LockIsolated<[String]>([]) + migrator.userDefaults.getString = { key in + getStringInvocations.append(key) + switch key { + case "persistent.state.v1": + return nil + case "persistent.state.v2": + return nil + case V1.userToken.namespaced: + return UUID.deadbeef.uuidString + case V1.installedAppVersion.namespaced: + return "1.77.88" + default: + XCTFail("Unexpected key: \(key)") + return nil + } } - } - let result = await migrator.migrate() - await expect(setApiToken.invocations).toEqual([.deadbeef]) - expect(await checkIn.invocations.value).toHaveCount(1) - expect(getStringInvocations.value).toEqual([ - "persistent.state.v2", - "persistent.state.v1", - V1.userToken.namespaced, - V1.installedAppVersion.namespaced, - ]) - expect(result).toEqual(.init( - appVersion: "1.77.88", - appUpdateReleaseChannel: .stable, - filterVersion: "1.77.88", - user: apiUser - )) - expect(setStringInvocations.value).toEqual([ - Both( + let result = await migrator.migrate() + await expect(setApiToken.invocations).toEqual([.deadbeef]) + expect(await checkIn.invocations.value).toHaveCount(1) + expect(getStringInvocations.value).toEqual([ "persistent.state.v2", - try! JSON.encode(Persistent.V2( - appVersion: "1.77.88", - appUpdateReleaseChannel: .stable, - filterVersion: "1.77.88", - user: apiUser - )) - ), - ]) + "persistent.state.v1", + V1.userToken.namespaced, + V1.installedAppVersion.namespaced, + ]) + expect(result).toEqual(.init( + appVersion: "1.77.88", + appUpdateReleaseChannel: .stable, + filterVersion: "1.77.88", + user: apiUser + )) + expect(setStringInvocations.value).toEqual([ + Both( + "persistent.state.v2", + try! JSON.encode(Persistent.V2( + appVersion: "1.77.88", + appUpdateReleaseChannel: .stable, + filterVersion: "1.77.88", + user: apiUser + )) + ), + ]) + } } func testMigratesLegacyV1DataWhenApiCallFails() async { - var migrator = testMigrator + await withDependencies { + $0.app.installedVersion = { "1.0.0" } + } operation: { + var migrator = testMigrator - // simulate that we can't fetch the user from the api - // so we need to pull all of the old info from storage - migrator.api.checkIn = { _ in throw TestErr("oh noes") } + // simulate that we can't fetch the user from the api + // so we need to pull all of the old info from storage + migrator.api.checkIn = { _ in throw TestErr("oh noes") } - let setApiToken = spy(on: UUID.self, returning: ()) - migrator.api.setUserToken = setApiToken.fn + let setApiToken = spy(on: UUID.self, returning: ()) + migrator.api.setUserToken = setApiToken.fn - let setStringInvocations = LockIsolated<[Both]>([]) - migrator.userDefaults.setString = { key, value in - setStringInvocations.append(.init(key, value)) - } + let setStringInvocations = LockIsolated<[Both]>([]) + migrator.userDefaults.setString = { key, value in + setStringInvocations.append(.init(key, value)) + } - let getStringInvocations = LockIsolated<[String]>([]) - migrator.userDefaults.getString = { key in - getStringInvocations.append(key) - switch key { - case "persistent.state.v1": - return nil - case "persistent.state.v2": - return nil - case V1.userToken.namespaced: - return UUID.ones.uuidString - case V1.installedAppVersion.namespaced: - return "1.77.88" - case V1.gertrudeUserId.namespaced: - return UUID.twos.uuidString - case V1.gertrudeDeviceId.namespaced: - return UUID.zeros.uuidString - case V1.keyloggingEnabled.namespaced: - return "true" - case V1.screenshotsEnabled.namespaced: - return "false" - case V1.screenshotFrequency.namespaced: - return "444" - case V1.screenshotSize.namespaced: - return "777" - default: - XCTFail("Unexpected key: \(key)") - return nil + let getStringInvocations = LockIsolated<[String]>([]) + migrator.userDefaults.getString = { key in + getStringInvocations.append(key) + switch key { + case "persistent.state.v1": + return nil + case "persistent.state.v2": + return nil + case V1.userToken.namespaced: + return UUID.ones.uuidString + case V1.installedAppVersion.namespaced: + return "1.77.88" + case V1.gertrudeUserId.namespaced: + return UUID.twos.uuidString + case V1.gertrudeDeviceId.namespaced: + return UUID.zeros.uuidString + case V1.keyloggingEnabled.namespaced: + return "true" + case V1.screenshotsEnabled.namespaced: + return "false" + case V1.screenshotFrequency.namespaced: + return "444" + case V1.screenshotSize.namespaced: + return "777" + default: + XCTFail("Unexpected key: \(key)") + return nil + } } - } - let result = await migrator.migrate() - let expectedUser = UserData( - id: .twos, - token: .ones, - deviceId: .zeros, - name: "(unknown)", - keyloggingEnabled: true, - screenshotsEnabled: false, - screenshotFrequency: 444, - screenshotSize: 777, - connectedAt: Date(timeIntervalSince1970: 0) - ) - - await expect(setApiToken.invocations).toEqual([.ones]) - expect(getStringInvocations.value).toEqual([ - "persistent.state.v2", - "persistent.state.v1", - V1.userToken.namespaced, - V1.installedAppVersion.namespaced, - V1.gertrudeUserId.namespaced, - V1.gertrudeDeviceId.namespaced, - V1.keyloggingEnabled.namespaced, - V1.screenshotsEnabled.namespaced, - V1.screenshotFrequency.namespaced, - V1.screenshotSize.namespaced, - ]) - expect(result).toEqual(.init( - appVersion: "1.77.88", - appUpdateReleaseChannel: .stable, - filterVersion: "1.77.88", - user: expectedUser - )) - expect(setStringInvocations.value).toEqual([Both( - "persistent.state.v2", - try! JSON.encode(Persistent.V2( + let result = await migrator.migrate() + let expectedUser = UserData( + id: .twos, + token: .ones, + deviceId: .zeros, + name: "(unknown)", + keyloggingEnabled: true, + screenshotsEnabled: false, + screenshotFrequency: 444, + screenshotSize: 777, + connectedAt: Date(timeIntervalSince1970: 0) + ) + + await expect(setApiToken.invocations).toEqual([.ones]) + expect(getStringInvocations.value).toEqual([ + "persistent.state.v2", + "persistent.state.v1", + V1.userToken.namespaced, + V1.installedAppVersion.namespaced, + V1.gertrudeUserId.namespaced, + V1.gertrudeDeviceId.namespaced, + V1.keyloggingEnabled.namespaced, + V1.screenshotsEnabled.namespaced, + V1.screenshotFrequency.namespaced, + V1.screenshotSize.namespaced, + ]) + expect(result).toEqual(.init( appVersion: "1.77.88", appUpdateReleaseChannel: .stable, filterVersion: "1.77.88", user: expectedUser )) - )]) + expect(setStringInvocations.value).toEqual([Both( + "persistent.state.v2", + try! JSON.encode(Persistent.V2( + appVersion: "1.77.88", + appUpdateReleaseChannel: .stable, + filterVersion: "1.77.88", + user: expectedUser + )) + )]) + } } } diff --git a/macapp/App/Tests/AppTests/AppReducerTests.swift b/macapp/App/Tests/AppTests/AppReducerTests.swift index bb8701de..0cd9e404 100644 --- a/macapp/App/Tests/AppTests/AppReducerTests.swift +++ b/macapp/App/Tests/AppTests/AppReducerTests.swift @@ -11,20 +11,18 @@ import XExpect @MainActor final class AppReducerTests: XCTestCase { func testDidFinishLaunching_Exhaustive() async { - let (store, bgQueue) = AppReducer.testStore(exhaustive: true) - - let filterSetupSpy = ActorIsolated(false) - store.deps.filterExtension.setup = { - await filterSetupSpy.setValue(true) - return .installedButNotRunning - } - - let tokenSetSpy = ActorIsolated(nil) - store.deps.api.setUserToken = { await tokenSetSpy.setValue($0) } + let (store, _) = AppReducer.testStore(exhaustive: true) + let extSetup = mock(always: FilterExtensionState.installedButNotRunning) + store.deps.filterExtension.setup = extSetup.fn + let setUserToken = spy(on: UUID.self, returning: ()) + store.deps.api.setUserToken = setUserToken.fn let filterStateSubject = PassthroughSubject() store.deps.filterExtension.stateChanges = { filterStateSubject.eraseToAnyPublisher() } store.deps.storage.loadPersistentState = { .mock } + store.deps.app.isLaunchAtLoginEnabled = { false } + let enableLaunchAtLogin = mock(always: ()) + store.deps.app.enableLaunchAtLogin = enableLaunchAtLogin.fn await store.send(.application(.didFinishLaunching)) @@ -33,21 +31,21 @@ import XExpect $0.history.userConnection = .established(welcomeDismissed: true) } - await store.receive(.websocket(.connectedSuccessfully)) - - await expect(tokenSetSpy).toEqual(UserData.mock.token) - - await bgQueue.advance(by: .milliseconds(5)) - await expect(filterSetupSpy).toEqual(true) + await expect(extSetup.invocations).toEqual(1) await store.receive(.filter(.receivedState(.installedButNotRunning))) { $0.filter.extension = .installedButNotRunning } + await store.receive(.startProtecting(user: .mock)) + await store.receive(.websocket(.connectedSuccessfully)) + + await expect(setUserToken.invocations).toEqual([UserData.mock.token]) + await expect(enableLaunchAtLogin.invocations).toEqual(1) + let prevUser = store.state.user.data - await bgQueue.advance(by: .milliseconds(5)) - await store.receive(.checkIn(result: .success(.mock), reason: .appLaunched)) { + await store.receive(.checkIn(result: .success(.mock), reason: .startProtecting)) { $0.appUpdates.latestVersion = .init(semver: "2.0.4") $0.user.data?.screenshotsEnabled = true $0.user.data?.keyloggingEnabled = true @@ -71,6 +69,16 @@ import XExpect await store.send(.application(.willTerminate)) // cancel heartbeat } + func testOnboardingDelegateSaveStepPersists() async { + let (store, _) = AppReducer.testStore() + let saveState = spy(on: Persistent.State.self, returning: ()) + store.deps.storage.savePersistentState = saveState.fn + + await store.send(.onboarding(.delegate(.saveForResume(.at(step: .macosUserAccountType))))) + await expect(saveState.invocations.value[0].resumeOnboarding) + .toEqual(.at(step: .macosUserAccountType)) + } + func testDidFinishLaunching_EstablishesConnectionIfFilterOn() async { let (store, bgQueue) = AppReducer.testStore() store.deps.storage.loadPersistentState = { nil } @@ -127,20 +135,31 @@ import XExpect extension AppReducer { static func testStore>( exhaustive: Bool = false, + mockDeps: Bool = true, reducer: R = AppReducer(), mutateState: @escaping (inout State) -> Void = { _ in } ) -> (TestStoreOf, TestSchedulerOf) { - var state = State() + var state = State(appVersion: "1.0.0") mutateState(&state) let store = TestStore(initialState: state, reducer: { reducer }) + store.useMainSerialExecutor = true store.exhaustivity = exhaustive ? .on : .off let scheduler = DispatchQueue.test - store.deps.date = .constant(Date(timeIntervalSince1970: 0)) - store.deps.backgroundQueue = scheduler.eraseToAnyScheduler() - store.deps.mainQueue = .immediate - store.deps.storage.loadPersistentState = { .mock } - store.deps.api.checkIn = { _ in .mock } - store.deps.filterExtension.setup = { .installedAndRunning } + if mockDeps { + store.deps.date = .constant(Date(timeIntervalSince1970: 0)) + store.deps.backgroundQueue = scheduler.eraseToAnyScheduler() + store.deps.mainQueue = .immediate + store.deps.monitoring = .mock + store.deps.storage = .mock + store.deps.storage.loadPersistentState = { .mock } + store.deps.app = .mock + store.deps.api = .mock + store.deps.device = .mock + store.deps.api.checkIn = { _ in .mock } + store.deps.filterExtension = .mock + store.deps.filterXpc = .mock + store.deps.websocket = .mock + } return (store, scheduler) } } diff --git a/macapp/App/Tests/AppTests/BlockedRequestsFeatureTests.swift b/macapp/App/Tests/AppTests/BlockedRequestsFeatureTests.swift index d3033a6a..1d7d9bef 100644 --- a/macapp/App/Tests/AppTests/BlockedRequestsFeatureTests.swift +++ b/macapp/App/Tests/AppTests/BlockedRequestsFeatureTests.swift @@ -200,8 +200,9 @@ import XExpect } await store.receive(.createUnlockRequests(.failure(TestErr("")))) { - $0.createUnlockRequests = - .failed(error: "Please try again, or contact help if the problem persists.") + $0.createUnlockRequests = .failed( + error: "Sorry, something went wrong. Please try again, or contact help if the problem persists." + ) } // toggling a request brings back to idle so the user can try again diff --git a/macapp/App/Tests/AppTests/FilterFeatureTests.swift b/macapp/App/Tests/AppTests/FilterFeatureTests.swift index 9964e09d..3acbc0d2 100644 --- a/macapp/App/Tests/AppTests/FilterFeatureTests.swift +++ b/macapp/App/Tests/AppTests/FilterFeatureTests.swift @@ -57,7 +57,11 @@ import XExpect } func testManualAdminSuspensionLifecycle() async { - let store = TestStore(initialState: AppReducer.State()) { AppReducer() } + let store = TestStore(initialState: AppReducer.State(appVersion: "1.0.0")) { + AppReducer() + } + store.deps.websocket = .mock + store.deps.device = .mock store.deps.date = .constant(Date(timeIntervalSince1970: 0)) let suspendFilter = spy(on: Seconds.self, returning: Result.success(())) store.deps.filterXpc.suspendFilter = suspendFilter.fn diff --git a/macapp/App/Tests/AppTests/HistoryUserConnectionTests.swift b/macapp/App/Tests/AppTests/HistoryUserConnectionTests.swift index 8bd16499..190e17d4 100644 --- a/macapp/App/Tests/AppTests/HistoryUserConnectionTests.swift +++ b/macapp/App/Tests/AppTests/HistoryUserConnectionTests.swift @@ -10,13 +10,12 @@ import XExpect let (store, _) = AppReducer.testStore() store.dependencies.api.connectUser = { _ in .mock } - let savedState = LockIsolated(nil) - store.dependencies.storage.savePersistentState = { state in - savedState.setValue(state) - } + store.deps.api.checkIn = { _ in throw TestErr("stop check in") } + let saveState = spy(on: Persistent.State.self, returning: ()) + store.deps.storage.savePersistentState = saveState.fn - let apiUserToken = ActorIsolated(nil) - store.deps.api.setUserToken = { await apiUserToken.setValue($0) } + let setUserToken = spy(on: UUID.self, returning: ()) + store.deps.api.setUserToken = setUserToken.fn await store.send(.menuBar(.connectClicked)) { $0.history.userConnection = .enteringConnectionCode @@ -33,8 +32,8 @@ import XExpect await store.receive(.websocket(.connectedSuccessfully)) - expect(savedState).toEqual(.mock) - await expect(apiUserToken).toEqual(UserData.mock.token) + await expect(saveState.invocations).toEqual([.mock]) + await expect(setUserToken.invocations).toEqual([UserData.mock.token]) await store.send(.menuBar(.welcomeAdminClicked)) { $0.history.userConnection = .established(welcomeDismissed: true) @@ -57,7 +56,9 @@ import XExpect await store.receive(.history(.userConnection(.connect(.failure(TestErr("Oh no!")))))) { $0.history.userConnection = - .connectFailed("Please try again, or contact help if the problem persists.") + .connectFailed( + "Sorry, something went wrong. Please try again, or contact help if the problem persists." + ) } await store.send(.menuBar(.connectFailedHelpClicked)) diff --git a/macapp/App/Tests/AppTests/MonitoringFeatureTests.swift b/macapp/App/Tests/AppTests/MonitoringFeatureTests.swift index 107074fe..9fa03803 100644 --- a/macapp/App/Tests/AppTests/MonitoringFeatureTests.swift +++ b/macapp/App/Tests/AppTests/MonitoringFeatureTests.swift @@ -515,8 +515,8 @@ import XExpect } } await store.send(.application(.didFinishLaunching)) - await bgQueue.advance(by: .seconds(600)) // <-- no fatal error - await expect(keylogging.take.invoked).toEqual(true) // we always check + await bgQueue.advance(by: .seconds(600)) // <-- no fatal error, heartbeat not running + await expect(keylogging.take.invoked).toEqual(false) await expect(keylogging.upload.invoked).toEqual(false) } @@ -617,17 +617,19 @@ import XExpect func testConnectingUserStartsMonitoring() async { let (store, bgQueue) = AppReducer.testStore() - store.deps.storage.loadPersistentState = { nil } store.deps.api.checkIn = { _ in throw TestErr("stop launch checkin") } + store.deps.storage.loadPersistentState = { .init( + appVersion: "1.0.0", + appUpdateReleaseChannel: .stable, + filterVersion: "1.0.0", + user: nil, // <-- no user + resumeOnboarding: nil + ) } let (takeScreenshot, uploadScreenshot, _) = spyScreenshots(store) - let keylogging = spyKeylogging(store, keystrokes: mock( - returning: [nil], - then: [.mock] - )) + let keylogging = spyKeylogging(store) await store.send(.application(.didFinishLaunching)) - await bgQueue.advance(by: .seconds(60 * 5)) // <- to heartbeat await expect(takeScreenshot.invocations.value.count).toEqual(0) await expect(uploadScreenshot.invocations.value.count).toEqual(0) await expect(keylogging.upload.invocations.value.count).toEqual(0) diff --git a/macapp/App/Tests/AppTests/OnboardingFeatureTests.swift b/macapp/App/Tests/AppTests/OnboardingFeatureTests.swift new file mode 100644 index 00000000..332e511a --- /dev/null +++ b/macapp/App/Tests/AppTests/OnboardingFeatureTests.swift @@ -0,0 +1,1065 @@ +import ClientInterfaces +import Combine +import ComposableArchitecture +import Core +import Gertie +import MacAppRoute +import TestSupport +import XCTest +import XExpect + +@testable import App + +@MainActor final class OnboardingFeatureTests: XCTestCase { + func testFirstBootOnboardingHappyPathExhaustive() async { + let (store, _) = AppReducer.testStore(exhaustive: true, mockDeps: false) + let scheduler = DispatchQueue.test + store.deps.backgroundQueue = scheduler.eraseToAnyScheduler() + store.deps.mainQueue = .immediate + store.deps.filterExtension.stateChanges = { Empty().eraseToAnyPublisher() } + store.deps.filterXpc.events = { Empty().eraseToAnyPublisher() } + store.deps.websocket.state = { .notConnected } + + store.deps.device.currentUserId = { 502 } + store.deps.device.listMacOSUsers = DeviceClient.mock.listMacOSUsers + store.deps.device.notificationsSetting = { .none } + + store.deps.storage.loadPersistentState = { nil } // <-- first boot + let saveState = spy(on: Persistent.State.self, returning: ()) + store.deps.storage.savePersistentState = saveState.fn + let extSetup = mock(always: FilterExtensionState.notInstalled) + store.deps.filterExtension.setup = extSetup.fn + + await store.send(.application(.didFinishLaunching)) + + await store.receive(.loadedPersistentState(nil)) { + $0.onboarding.windowOpen = true + $0.onboarding.step = .welcome + } + + await store.receive(.filter(.receivedState(.notInstalled))) { + $0.filter.extension = .notInstalled + } + + await expect(extSetup.invocations).toEqual(1) + await expect(saveState.invocations.value).toHaveCount(1) + + // they click next on the welcome screen... + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .confirmGertrudeAccount // ... and go to confirm account + } + + await store.receive(.onboarding(.receivedDeviceData( + currentUserId: 502, + users: [ + .init(id: 501, name: "Dad", type: .admin), + .init(id: 502, name: "liljimmy", type: .standard), + ] + ))) { + $0.onboarding.users = [ + .init(id: 501, name: "Dad", isAdmin: true), + .init(id: 502, name: "liljimmy", isAdmin: false), + ] + $0.onboarding.currentUser = .init(id: 502, name: "liljimmy", isAdmin: false) + } + + // next they confirm that they have a gertrude account... + store.deps.device.currentMacOsUserType = { .standard } + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .macosUserAccountType // ...and end up on the macos user screen + } + + // they click next on the macos user confirmation good page... + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .getChildConnectionCode // ...and go to the get connection screen + } + + // they click the "got it" button on get connection code screen... + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .connectChild // ... and end up on the connect child screen + $0.onboarding.connectChildRequest = .idle + } + + // lots happens when the user connection is made... + let user = UserData.mock { $0.name = "lil suzy" } + let connectUser = spy(on: ConnectUser.Input.self, returning: user) + store.deps.api.connectUser = connectUser.fn + store.deps.app.installedVersion = { "1.0.0" } + store.deps.device = .mock // lots of data used by connect user request + let setUserToken = spy(on: UUID.self, returning: ()) + store.deps.api.setUserToken = setUserToken.fn + + // they enter code `123456` and click submit... + await store.send(.onboarding(.webview(.connectChildSubmitted(code: 123_456)))) { + $0.onboarding.step = .connectChild + $0.onboarding.connectChildRequest = .ongoing // ... and see a throbber + } + + await expect(setUserToken.invocations).toEqual([UserData.mock.token]) + await expect(connectUser.invocations.value).toHaveCount(1) + await expect(connectUser.invocations.value[0].verificationCode).toEqual(123_456) + + await store.receive(.onboarding(.connectUser(.success(user)))) { + $0.user.data = user + $0.history.userConnection = .established(welcomeDismissed: true) + $0.onboarding.step = .connectChild + $0.onboarding.connectChildRequest = .succeeded(payload: "lil suzy") + } + + // we persisted the user data + await expect(saveState.invocations.value).toHaveCount(2) + await expect(saveState.invocations.value[1].user).toEqual(user) + + // notifications not enabled + let notifsSettings = mock(returning: [.none], then: NotificationsSetting.alert) + store.deps.device.notificationsSetting = notifsSettings.fn + + // they click "next" on the connected child success screen... + await store.send(.onboarding(.webview(.primaryBtnClicked))) + + // ... and end up on the notifications screen + await store.receive(.onboarding(.setStep(.allowNotifications_start))) { + $0.onboarding.step = .allowNotifications_start + } + + let requestNotifAuth = mock(always: ()) + store.deps.device.requestNotificationAuthorization = requestNotifAuth.fn + let openSysPrefs = spy(on: SystemPrefsLocation.self, returning: ()) + store.deps.device.openSystemPrefs = openSysPrefs.fn + + // they click "Open System Settings" on the notifications start screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .allowNotifications_grant // ...and go to grant + } + + // ... and we requested authorization and then opened system prefs + await expect(requestNotifAuth.invocations).toEqual(1) + await expect(openSysPrefs.invocations).toEqual([.notifications]) + + // they have not previously granted permission... + let screenshotsAllowed = mock(returning: [false, false], then: true) + store.deps.monitoring.screenRecordingPermissionGranted = screenshotsAllowed.fn + + // ... and then clicked "Done" on the notifications grant screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) + + // ...and we confirmed the setting and moved them on the happy path + await expect(notifsSettings.invocations).toEqual(2) + await store.receive(.onboarding(.setStep(.allowScreenshots_required))) { + $0.onboarding.step = .allowScreenshots_required + } + + let takeScreenshot = spy(on: Int.self, returning: ()) + store.deps.monitoring.takeScreenshot = takeScreenshot.fn + + // they click "Grant Permission" on the allow screenshots start screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) + + await store.receive(.onboarding(.delegate(.saveForResume(.checkingScreenRecordingPermission)))) + + // check that we persisted the onboarding resumption state + await expect(saveState.invocations.value).toHaveCount(3) + await expect(saveState.invocations.value[2].resumeOnboarding) + .toEqual(.checkingScreenRecordingPermission) + + await store.receive(.onboarding(.setStep(.allowScreenshots_grantAndRestart))) { + $0.onboarding.step = .allowScreenshots_grantAndRestart + } + + // ...and we check the setting, and take a screenshot, and moved them on + await expect(screenshotsAllowed.invocations).toEqual(2) + // taking a screenshot ensures the full permissions prompt + await expect(takeScreenshot.invocations.value).toHaveCount(1) + + // NB: here technically they RESTART the app, but instead of starting a new test + // we simulate receiving the resume action to carry on where they should + // we have other tests testing the resume from persisted state flow. + await store.send(.onboarding(.resume(.checkingScreenRecordingPermission))) + + await store.receive(.onboarding(.setStep(.allowScreenshots_success))) { + $0.onboarding.step = .allowScreenshots_success + } + + // they click the "Next" button from the screen recording success + await store.send(.onboarding(.webview(.primaryBtnClicked))) + await store.receive(.onboarding(.delegate(.saveForResume(nil)))) + await store.receive(.onboarding(.setStep(.allowKeylogging_required))) { + $0.onboarding.step = .allowKeylogging_required + } + + // they have not previously granted permission... + let keyloggingAllowed = mock(returning: [false], then: true) + store.deps.monitoring.keystrokeRecordingPermissionGranted = keyloggingAllowed.fn + + // they click "Grant Permission" on the allow keylogging start screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) + + await store.receive(.onboarding(.delegate(.saveForResume(nil)))) + await store.receive(.onboarding(.setStep(.allowKeylogging_grant))) { + $0.onboarding.step = .allowKeylogging_grant // ...and go to grant + } + + // ...and we checked the setting (which pops up prompt) and moved them on + await expect(keyloggingAllowed.invocations).toEqual(1) + + // moving on from keylogging tests filter extension state, to possibly skip + let filterState = mock(returning: [ + FilterExtensionState.notInstalled, + .notInstalled, + .installedAndRunning, + ]) + store.deps.filterExtension.state = filterState.fn + + // they click "Done" indicating they think they've allowed keylogging + await store.send(.onboarding(.webview(.primaryBtnClicked))) + + // we confirm, and see that they did it correct... + await expect(keyloggingAllowed.invocations).toEqual(2) + // ...so they get sent off to the next happy path step + await store.receive(.onboarding(.delegate(.saveForResume(nil)))) + await store.receive(.onboarding(.setStep(.installSysExt_explain))) { + $0.onboarding.step = .installSysExt_explain // ...and go to sys ext start + } + + let installSysExt = spy( + on: Int.self, + returning: FilterInstallResult.installedSuccessfully // <-- success + ) + store.deps.filterExtension.installOverridingTimeout = installSysExt.fn + + // they click "Next" on the install sys ext start screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) + await expect(installSysExt.invocations.value).toHaveCount(1) + await store.receive(.onboarding(.setStep(.installSysExt_allow))) { + $0.onboarding.step = .installSysExt_allow // ...and go to sys ext allow + } + + // becuase filterExtension.install is mocked to return success, we go to success + await store.receive(.onboarding(.setStep(.installSysExt_success))) { + $0.onboarding.step = .installSysExt_success + } + + // we kick off protection when they move to .locateMenuBarIcon step, lots happens... + let setAccountActive = spy(on: Bool.self, returning: ()) + store.deps.api.setAccountActive = setAccountActive.fn + let checkInResult = CheckIn.Output.empty { $0.userData = user } + let checkIn = spy(on: CheckIn.Input.self, returning: checkInResult) + store.deps.api.checkIn = checkIn.fn + store.deps.websocket.receive = { Empty().eraseToAnyPublisher() } + let connectWebsocket = succeed(with: WebSocketClient.State.connected, capturing: UUID.self) + store.deps.websocket.connect = connectWebsocket.fn + let wsSend = succeed(with: (), capturing: WebSocketMessage.FromAppToApi.self) + store.deps.websocket.send = wsSend.fn + store.deps.monitoring = .mock + let stopLoggingKeystrokes = mock(always: ()) + store.deps.monitoring.stopLoggingKeystrokes = stopLoggingKeystrokes.fn + + // they click "Next" on the install sys ext success screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .locateMenuBarIcon // ...and go to locate icon + } + + // this proves that we turned on all monitoring + await expect(stopLoggingKeystrokes.invocations).toEqual(1) + + await store.receive(.onboarding(.delegate(.onboardingConfigComplete))) + await store.receive(.startProtecting(user: user)) + await store.receive(.websocket(.connectedSuccessfully)) + + await store.receive(.checkIn(result: .success(checkInResult), reason: .startProtecting)) { + $0.appUpdates.latestVersion = checkInResult.latestRelease + } + await store.receive(.user(.updated(previous: user))) + + // we need to ensure the websocket connection is setup, so they can do the tutorial vid + await expect(connectWebsocket.invocations).toEqual([UserData.mock.token]) + await expect(wsSend.invocations).toEqual([.currentFilterState(.off)]) + + await expect(setUserToken.invocations).toEqual([UserData.mock.token, UserData.mock.token]) + await expect(setAccountActive.invocations).toEqual([true]) + + // they click "Next" on the locate menu bar icon screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .viewHealthCheck // ...and go to health check + } + + // they click "Next" on the health check screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .howToUseGertrude // ...and go to how to use + } + + // they click "Next" on the how to use screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .finish // ...and go to finish + } + + // primary button on finish screen closes window, sends delegate that starts protection + store.deps.app.isLaunchAtLoginEnabled = { false } + let enableLaunchAtLogin = mock(always: ()) + store.deps.app.enableLaunchAtLogin = enableLaunchAtLogin.fn + + // close the final "finish" screen + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.windowOpen = false + } + + await expect(checkIn.invocations).toEqual([.init(appVersion: "1.0.0", filterVersion: "1.0.0")]) + await expect(enableLaunchAtLogin.invocations).toEqual(1) + + // shutdown tries fo flush keystrokes + store.deps.monitoring.takePendingKeystrokes = { nil } + await store.send(.application(.willTerminate)) + } + + func testResumingFromAdminUserDemotion() async { + let (store, _) = AppReducer.testStore(mockDeps: true) + store.deps.device = .testValue + store.deps.device.currentUserId = { 502 } + store.deps.device.listMacOSUsers = { [ + .init(id: 501, name: "jared", type: .admin), + .init(id: 502, name: "franny", type: .standard), + ] } + + store.deps.storage.loadPersistentState = { .mock { + $0.user = .monitored + $0.resumeOnboarding = .at(step: .macosUserAccountType) + }} + + await store.send(.application(.didFinishLaunching)) + await store.receive(.onboarding(.resume(.at(step: .macosUserAccountType)))) { + $0.onboarding.step = .macosUserAccountType + $0.onboarding.windowOpen = true + } + + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .getChildConnectionCode + // we need to make sure we initialize the user data when resuming + $0.onboarding.currentUser = .init(id: 502, name: "franny", isAdmin: false) + $0.onboarding.users = [ + .init(id: 501, name: "jared", isAdmin: true), + .init(id: 502, name: "franny", isAdmin: false), + ] + } + } + + func testResumingToCheckScreenRecordingRestoresUserConnection() async { + let (store, _) = AppReducer.testStore() + let saveState = spy(on: Persistent.State.self, returning: ()) + store.deps.monitoring.screenRecordingPermissionGranted = { true } + store.deps.storage.savePersistentState = saveState.fn + store.deps.api.checkIn = { _ in throw TestErr("stop checkin") } + store.deps.app.isLaunchAtLoginEnabled = { fatalError("don't check launch at login") } + + // it's critical this is not called when we first resume, so that they + // don't get a prompt until they advance to the keylogging screen + store.deps.monitoring.keystrokeRecordingPermissionGranted = { + fatalError("keystrokeRecordingPermissionGranted should not be called") + } + + store.deps.storage.loadPersistentState = { .mock { + $0.user = .mock { $0.keyloggingEnabled = true } + $0.resumeOnboarding = .checkingScreenRecordingPermission // <-- resume here + }} + + await store.send(.application(.didFinishLaunching)) + await store.skipReceivedActions() + + store.assert { + $0.onboarding.windowOpen = true + $0.onboarding.step = .allowScreenshots_success + // user restored + $0.onboarding.connectChildRequest = .succeeded(payload: UserData.mock.name) + $0.user = .init(data: .mock { $0.keyloggingEnabled = true }) + } + + // and we saved the state, removing onboarding resume + await expect(saveState.invocations).toEqual([.mock { + $0.user = .mock { $0.keyloggingEnabled = true } + $0.resumeOnboarding = nil + }]) + } + + func testResumingToCheckScreenRecording_Failure() async { + let (store, _) = AppReducer.testStore() + let saveState = spy(on: Persistent.State.self, returning: ()) + store.deps.monitoring.screenRecordingPermissionGranted = { false } // <-- still no bueno! + store.deps.storage.savePersistentState = saveState.fn + store.deps.api.checkIn = { _ in throw TestErr("stop checkin") } + store.deps.app.isLaunchAtLoginEnabled = { fatalError("don't check launch at login") } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { fatalError("nope") } + + store.deps.storage.loadPersistentState = { .mock { + $0.user = .mock { $0.keyloggingEnabled = true } + $0.resumeOnboarding = .checkingScreenRecordingPermission // <-- resume here + }} + + await store.send(.application(.didFinishLaunching)) + await store.skipReceivedActions() + + store.assert { + $0.onboarding.step = .allowScreenshots_failed + } + + // since the perms are still wrong, we need to save state to + // resume again after a quit & restart + await expect(saveState.invocations).toEqual([ + .mock { + $0.user = .mock { $0.keyloggingEnabled = true } + $0.resumeOnboarding = nil // <-- the first, default clear save + }, + .mock { + $0.user = .mock { $0.keyloggingEnabled = true } + $0.resumeOnboarding = .checkingScreenRecordingPermission // <-- after we detect the failure + }, + ]) + } + + func testQuittingOnboardingEarlyAfterConnectionSuccessStartsProtection() async { + let (store, _) = AppReducer.testStore { + $0.user = .init(data: .mock { $0.name = "franny" }) + $0.onboarding.step = .allowNotifications_start // post child connection.. + $0.onboarding + .connectChildRequest = .succeeded(payload: "franny") // <-- ... w/ a successful connection + } + + await store.send(.onboarding(.webview(.closeWindow))) + await store.receive(.startProtecting(user: .mock { $0.name = "franny" })) + } + + func testSkippingFromAdminUserRemediation() async { + let store = featureStore { + $0.step = .macosUserAccountType + $0.userRemediationStep = .create + } + await store.send(.webview(.secondaryBtnClicked)) { + $0.step = .getChildConnectionCode + } + } + + func testPrimaryBtnFromAllowScreenshotsGrantModalGoesToFailForVideo() async { + let store = featureStore { $0.step = .allowScreenshots_grantAndRestart } + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .allowScreenshots_failed + } + } + + func testSecondaryEscapeHatchFromAllowScreenshotsGrantGoesToNextStage() async { + let store = featureStore { $0.step = .allowScreenshots_grantAndRestart } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { false } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_required)) + } + + func testSecondaryFromAllowNotificationsGrantModalGoesToFail() async { + let store = featureStore { $0.step = .allowNotifications_grant } + await store.send(.webview(.secondaryBtnClicked)) { + $0.step = .allowNotifications_failed + } + } + + func testSkippingAllStepsFromConnectSuccess() async { + let store = featureStore { + $0.step = .connectChild + $0.connectChildRequest = .succeeded(payload: "Lil jimmy") + } + + store.deps.device.notificationsSetting = { .alert } + store.deps.monitoring.screenRecordingPermissionGranted = { true } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { true } + store.deps.filterExtension.state = { .installedAndRunning } + + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_required)) // we always stop here + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.locateMenuBarIcon)) + } + + func testSkippingNotificationStepFromConnectSuccess() async { + let store = featureStore { + $0.step = .connectChild + $0.connectChildRequest = .succeeded(payload: "Lil jimmy") + } + + store.deps.device.notificationsSetting = { .alert } + store.deps.monitoring.screenRecordingPermissionGranted = { false } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { fatalError() } + store.deps.filterExtension.state = { fatalError() } + + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.allowScreenshots_required)) + } + + func testSkippingToKeyloggingFromConnectSuccess() async { + let store = featureStore { + $0.step = .connectChild + $0.connectChildRequest = .succeeded(payload: "Lil jimmy") + } + + store.deps.device.notificationsSetting = { .alert } + store.deps.monitoring.screenRecordingPermissionGranted = { true } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { false } + store.deps.filterExtension.state = { fatalError() } + + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_required)) + } + + func testSkippingToInstallSysExtFromConnectSuccess() async { + let store = featureStore { + $0.step = .connectChild + $0.connectChildRequest = .succeeded(payload: "Lil jimmy") + } + + store.deps.device.notificationsSetting = { .alert } + store.deps.monitoring.screenRecordingPermissionGranted = { true } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { true } + store.deps.filterExtension.state = { .notInstalled } + + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_required)) // we always stop here + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.installSysExt_explain)) + } + + func testSkippingScreenshotsFromFinishNotifications() async { + let store = featureStore { $0.step = .allowNotifications_grant } + store.deps.monitoring.screenRecordingPermissionGranted = { true } // <-- skip + store.deps.monitoring.keystrokeRecordingPermissionGranted = { false } + store.deps.device.notificationsSetting = { .alert } + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_required)) + } + + func testSkippingKeyloggingFromFinishScreenshots() async { + let store = featureStore { $0.step = .allowScreenshots_success } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { true } + store.deps.filterExtension.state = { .notInstalled } + await store.send(.webview(.primaryBtnClicked)) + // we always stop here, because we can't check without prompting + await store.receive(.setStep(.allowKeylogging_required)) + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.installSysExt_explain)) // <-- but they skip the rest + } + + func testFromScreenshotsRequiredScreenshotsAndKeyloggingAlreadyAllowed() async { + let store = featureStore { $0.step = .allowScreenshots_required } + store.deps.monitoring.screenRecordingPermissionGranted = { true } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { true } + store.deps.filterExtension.state = { .notInstalled } + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_required)) // we always stop here + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.installSysExt_explain)) + } + + func testClickingTryAgainPrimaryFromInstallSysExtFailed() async { + let store = featureStore { $0.step = .installSysExt_failed } + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .installSysExt_explain + } + } + + func testClickingSkipSecondaryFromInstallSysExtFailed() async { + let store = featureStore { $0.step = .installSysExt_failed } + await store.send(.webview(.secondaryBtnClicked)) { + $0.step = .locateMenuBarIcon + } + } + + func testClickingHelpSecondaryFromInstallSysExt() async { + let store = featureStore { $0.step = .installSysExt_allow } + store.deps.filterExtension.state = { .notInstalled } // <-- not installed + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.installSysExt_failed)) // <-- goes to failed + } + + func testClickingHelpSecondaryFromInstallSysExt_WhenInstalled() async { + let store = featureStore { $0.step = .installSysExt_allow } + store.deps.filterExtension.state = { .installedAndRunning } // <-- installed + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.installSysExt_success)) // <-- goes to success + } + + // for most users, we will move them along automatically to + // success of failure based on the result of the install request, + // but we do have a button as well, this tests that it works + func testClickingDoneFromInstallSysExt() async { + let store = featureStore { $0.step = .installSysExt_allow } + let filterState = mock(returning: [FilterExtensionState.installedAndRunning]) + store.deps.filterExtension.state = filterState.fn + + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.installSysExt_success)) + await expect(filterState.invocations).toEqual(1) + } + + func testHandleDetectingSysExtInstallFail() async { + let store = featureStore { + $0.step = .installSysExt_explain + } + store.deps.mainQueue = .immediate + let filterState = mock(once: FilterExtensionState.notInstalled) + store.deps.filterExtension.state = filterState.fn + let installSysExt = spy( + on: Int.self, + returning: FilterInstallResult.userClickedDontAllow // <-- fail + ) + store.deps.filterExtension.installOverridingTimeout = installSysExt.fn + + // they click "Next" on the install sys ext explain screen + await store.send(.webview(.primaryBtnClicked)) + await expect(installSysExt.invocations.value).toHaveCount(1) + await store.receive(.setStep(.installSysExt_allow)) { + $0.step = .installSysExt_allow // ...and go to sys ext allow + } + + await store.receive(.setStep(.installSysExt_failed)) + } + + func testSysExtAlreadyInstalledAndRunning() async { + let store = featureStore { + $0.step = .installSysExt_explain + } + let filterState = mock(once: FilterExtensionState.installedAndRunning) + store.deps.filterExtension.state = filterState.fn + + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.installSysExt_success)) { + $0.step = .installSysExt_success + } + } + + func testSysExtAlreadyInstalledButNotRunning_StartsToSuccess() async { + let store = featureStore { $0.step = .installSysExt_explain } + let filterState = mock(once: FilterExtensionState.installedButNotRunning) + store.deps.filterExtension.state = filterState.fn + let filterStart = mock(once: FilterExtensionState.installedAndRunning) + store.deps.filterExtension.start = filterStart.fn + + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.installSysExt_success)) { + $0.step = .installSysExt_success + } + + await expect(filterStart.invocations).toEqual(1) + } + + func testSysExtAlreadyInstalledButNotRunning_StartFailsToError() async { + let store = featureStore { $0.step = .installSysExt_explain } + let filterState = mock(once: FilterExtensionState.installedButNotRunning) + store.deps.filterExtension.state = filterState.fn + let filterStart = mock(once: FilterExtensionState.installedButNotRunning) + store.deps.filterExtension.start = filterStart.fn + + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.installSysExt_failed)) { + $0.step = .installSysExt_failed + } + } + + func testSkipAllowKeylogging() async { + let store = featureStore { $0.step = .allowKeylogging_required } + store.deps.filterExtension.state = { .notInstalled } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.installSysExt_explain)) + } + + func testSkipAllowKeyloggingSysExtAlreadyInstalled() async { + let store = featureStore { $0.step = .allowKeylogging_required } + store.deps.filterExtension.state = { .installedAndRunning } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.locateMenuBarIcon)) + } + + func testFailedToAllowKeylogging() async { + let store = featureStore { $0.step = .allowKeylogging_grant } + let keyloggingAllowed = mock(always: false) // <-- they failed to allow + store.deps.monitoring.keystrokeRecordingPermissionGranted = keyloggingAllowed.fn + + await store.send(.webview(.primaryBtnClicked)) + + // ...and we check the setting and move to failure + await expect(keyloggingAllowed.invocations).toEqual(1) + await store.receive(.setStep(.allowKeylogging_failed)) { + $0.step = .allowKeylogging_failed + } + + let openSysPrefs = spy(on: SystemPrefsLocation.self, returning: ()) + store.deps.device.openSystemPrefs = openSysPrefs.fn + + // now they click the "try again" button + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_grant)) + + // and we tried to open system prefs to the right spot + await expect(openSysPrefs.invocations).toEqual([.security(.accessibility)]) + } + + func testSkipFromKeylogginFail() async { + let store = featureStore { $0.step = .allowKeylogging_failed } + store.deps.filterExtension.state = { .notInstalled } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.installSysExt_explain)) + } + + func testSkipsMostKeyloggingStepsIfPermsPreviouslyGranted() async { + let store = featureStore { $0.step = .allowKeylogging_required } + + let keyloggingAllowed = mock(always: true) // <- they have granted permission + store.deps.monitoring.keystrokeRecordingPermissionGranted = keyloggingAllowed.fn + store.deps.filterExtension.state = { .notInstalled } + + // they click "Grant permission" on the allow screenshots required screen + await store.send(.webview(.primaryBtnClicked)) + + // ...and we check the setting (which pops up prompt) and moved them on + await expect(keyloggingAllowed.invocations).toEqual(1) + await store.receive(.setStep(.installSysExt_explain)) + } + + func testSkipAllowingScreenshots() async { + let store = featureStore { $0.step = .allowScreenshots_required } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { false } + // they click "Skip" on the allow screenshots start screen + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_required)) + } + + func testSkipsMostScreenshotStepsIfPermsPreviouslyGranted() async { + let store = featureStore { $0.step = .allowScreenshots_required } + + let screenshotsAllowed = mock(always: true) // <- they have granted permission + store.deps.monitoring.screenRecordingPermissionGranted = screenshotsAllowed.fn + store.deps.monitoring.keystrokeRecordingPermissionGranted = { false } + + // they click "Grant permission" on the allow screenshots required screen + await store.send(.webview(.primaryBtnClicked)) + + // ...and we check the setting (which pops up prompt) and moved them on + await expect(screenshotsAllowed.invocations).toEqual(1) + await store.receive(.setStep(.allowKeylogging_required)) { + $0.step = .allowKeylogging_required // ...and go to keylogging + } + } + + func testFailureToGrantNotificationsSendsToFailScreen() async { + let store = featureStore { $0.step = .allowNotifications_grant } + + let notifsSettings = mock( + // they did NOT enable notifications (the first 2 times we check) + returning: [NotificationsSetting.none, .none], + then: NotificationsSetting.alert // ... but they fix it before 3rd... + ) + store.deps.device.notificationsSetting = notifsSettings.fn + + // ... and then clicked "Done" on the notifications grant screen + await store.send(.webview(.primaryBtnClicked)) + + // ...and we fail to confirm the setting, moving them to fail screen + await expect(notifsSettings.invocations).toEqual(1) + await store.receive(.setStep(.allowNotifications_failed)) + + let requestNotifAuth = mock(always: ()) + store.deps.device.requestNotificationAuthorization = requestNotifAuth.fn + let openSysPrefs = spy(on: SystemPrefsLocation.self, returning: ()) + store.deps.device.openSystemPrefs = openSysPrefs.fn + + // they did NOT fix it, and clicked Try Again... + await store.send(.webview(.primaryBtnClicked)) + + // so we a) checked the settings again + await expect(notifsSettings.invocations).toEqual(2) + // b) requested permission + await expect(requestNotifAuth.invocations).toEqual(1) + // and c) open system prefs + await expect(openSysPrefs.invocations).toEqual([.notifications]) + // and send them back to the grant screen with instructions + await store.receive(.setStep(.allowNotifications_grant)) + + // NOW (3rd check) they finally fixed it, and clicked Try Again... + store.deps.monitoring.screenRecordingPermissionGranted = { false } + await store.send(.webview(.primaryBtnClicked)) + + // ...and we confirmed the setting and moved them on the happy path + await expect(notifsSettings.invocations).toEqual(3) + await store.receive(.setStep(.allowScreenshots_required)) + } + + func testSkipFromAllowNotificationsFailedStep() async { + let store = featureStore { $0.step = .allowNotifications_failed } + store.deps.monitoring.screenRecordingPermissionGranted = { false } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.allowScreenshots_required)) + } + + func testSkipAllowNotificationsStep() async { + let store = featureStore { $0.step = .allowNotifications_start } + store.deps.monitoring.screenRecordingPermissionGranted = { false } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.allowScreenshots_required)) + } + + func testConnectChildFailure() async { + let (store, _) = AppReducer.testStore { + $0.onboarding.step = .connectChild + $0.onboarding.connectChildRequest = .ongoing + } + let openWebUrl = spy(on: URL.self, returning: ()) + store.deps.device.openWebUrl = openWebUrl.fn + + await store.send(.onboarding(.connectUser(.failure(TestErr("oh noes!"))))) { + $0.onboarding.step = .connectChild + $0.onboarding.connectChildRequest = .failed( + error: "Sorry, something went wrong. Please try again, or contact help if the problem persists." + ) + } + + // they clicked the "get help" button + await store.send(.onboarding(.webview(.secondaryBtnClicked))) + await expect(openWebUrl.invocations).toEqual([.init(string: "https://gertrude.app/contact")!]) + + // they clicked "try again" + await store.send(.onboarding(.webview(.primaryBtnClicked))) { + $0.onboarding.step = .getChildConnectionCode + $0.onboarding.connectChildRequest = .idle + } + } + + func testResumingAtMacOSUserType() async { + let (store, _) = AppReducer.testStore() + let saveState = spy(on: Persistent.State.self, returning: ()) + store.deps.storage.savePersistentState = saveState.fn + store.deps.storage.loadPersistentState = { .init( + appVersion: "1.0.0", + appUpdateReleaseChannel: .stable, + filterVersion: "1.0.0", + user: nil, + resumeOnboarding: .at(step: .macosUserAccountType) + ) } + + await store.send(.application(.didFinishLaunching)) + + await store.receive(.onboarding(.resume(.at(step: .macosUserAccountType)))) { + $0.onboarding.windowOpen = true + $0.onboarding.step = .macosUserAccountType + } + + await expect(saveState.invocations).toEqual([ + .init( + appVersion: "1.0.0", + appUpdateReleaseChannel: .stable, + filterVersion: "1.0.0", + user: nil, + resumeOnboarding: nil // <-- nils out step + ), + ]) + } + + func testResumingCheckScreenRecordingGranted() async { + let (store, _) = AppReducer.testStore() + store.deps.monitoring.screenRecordingPermissionGranted = { true } // <-- granted + store.deps.storage.loadPersistentState = { .init( + appVersion: "1.0.0", + appUpdateReleaseChannel: .stable, + filterVersion: "1.0.0", + user: nil, + resumeOnboarding: .checkingScreenRecordingPermission // <-- check + ) } + + await store.send(.application(.didFinishLaunching)) + + await store.receive(.onboarding(.setStep(.allowScreenshots_success))) { + $0.onboarding.windowOpen = true + $0.onboarding.step = .allowScreenshots_success + } + } + + func testResumingCheckScreenRecordingNotGranted() async { + let (store, _) = AppReducer.testStore() + store.deps.monitoring.screenRecordingPermissionGranted = { false } // <-- NOT granted + store.deps.storage.loadPersistentState = { .init( + appVersion: "1.0.0", + appUpdateReleaseChannel: .stable, + filterVersion: "1.0.0", + user: nil, + resumeOnboarding: .checkingScreenRecordingPermission // <-- check + ) } + + await store.send(.application(.didFinishLaunching)) + + await store.receive(.onboarding(.setStep(.allowScreenshots_failed))) { + $0.onboarding.windowOpen = true + $0.onboarding.step = .allowScreenshots_failed + } + + let openSysPrefs = spy(on: SystemPrefsLocation.self, returning: ()) + store.deps.device.openSystemPrefs = openSysPrefs.fn + + // they now click the primary "try again" button + await store.send(.onboarding(.webview(.primaryBtnClicked))) + await store.receive(.onboarding(.setStep(.allowScreenshots_grantAndRestart))) + + // and we tried to open system prefs to the right spot + await expect(openSysPrefs.invocations).toEqual([.security(.screenRecording)]) + } + + func testTryAgainFromScreenRecFailMovesOnIfPermsGranted() async { + let store = featureStore { $0.step = .allowScreenshots_failed } + store.deps.monitoring.screenRecordingPermissionGranted = { true } + await store.send(.webview(.primaryBtnClicked)) + await store.receive(.setStep(.allowScreenshots_success)) + } + + func testSkipFromScreenRecordingFailed() async { + let store = featureStore { $0.step = .allowScreenshots_failed } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { false } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_required)) + } + + func testNoGertrudeAccountPrimary() async { + let store = featureStore { $0.step = .noGertrudeAccount } + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .macosUserAccountType + } + } + + func testSecondaryHelpFromAllowKeyloggingGrantGoesToFailForVideo() async { + let store = featureStore { $0.step = .allowKeylogging_grant } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { false } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.allowKeylogging_failed)) + } + + func testSecondaryHelpFromAllowKeyloggingGrantGoesToNextIfPermGranted() async { + let store = featureStore { $0.step = .allowKeylogging_grant } + store.deps.monitoring.keystrokeRecordingPermissionGranted = { true } // <-- granted + store.deps.filterExtension.state = { .notInstalled } + await store.send(.webview(.secondaryBtnClicked)) + await store.receive(.setStep(.installSysExt_explain)) + } + + func testNoGertrudeAccountQuit() async { + let store = featureStore() + store.deps.device = .mock + let quit = mock(once: ()) + store.deps.app.quit = quit.fn + let deleteAll = mock(once: ()) + store.deps.storage.deleteAll = deleteAll.fn + + await store.send(.webview(.primaryBtnClicked)) + + await store.send(.webview(.secondaryBtnClicked)) { + $0.step = .noGertrudeAccount + } + + await store.send(.webview(.secondaryBtnClicked)) + await expect(deleteAll.invoked).toEqual(true) + await expect(quit.invoked).toEqual(true) + } + + func testBadUserTypeIgnoresDanger() async { + let store = featureStore() + store.deps.device.currentUserId = { 501 } + store.deps.device.listMacOSUsers = { [.init(id: 501, name: "Dad", type: .admin)] } + store.deps.device.notificationsSetting = { .none } + + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .confirmGertrudeAccount + } + + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .macosUserAccountType + $0.userRemediationStep = nil + } + + await store.send(.webview(.secondaryBtnClicked)) { + $0.step = .getChildConnectionCode + } + } + + func testBadUserTypeNoChoice() async { + let store = featureStore() + store.deps.device.currentUserId = { 501 } + store.deps.device.listMacOSUsers = { [.init(id: 501, name: "Dad", type: .admin)] } + store.deps.device.notificationsSetting = { .none } + + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .confirmGertrudeAccount + } + + // they click confirming they have a gertrude acct... + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .macosUserAccountType // ...landing them on user type warning page + $0.userRemediationStep = nil + } + + // they click the primary btn: show me how to fix it... + await store.send(.webview(.primaryBtnClicked)) { + // because they only have ONE admin user on the system, + // we take them straight to step to create a new user + $0.userRemediationStep = .create + } + } + + func testBadUserTypeWithChoice() async { + let store = featureStore() + + store.deps.device.currentUserId = { 501 } + store.deps.device.listMacOSUsers = { [ + .init(id: 501, name: "Dad", type: .admin), + .init(id: 503, name: "Mom", type: .admin), + .init(id: 502, name: "liljimmy", type: .standard), + ] } + store.deps.device.notificationsSetting = { .none } + + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .confirmGertrudeAccount + } + + // they click confirming they have a gertrude acct... + await store.send(.webview(.primaryBtnClicked)) { + $0.step = .macosUserAccountType // ...landing them on user type warning page + $0.userRemediationStep = nil + } + + // they click the primary btn: show me how to fix it... + await store.send(.webview(.primaryBtnClicked)) { + // because they have options for remediation, they must choose + $0.userRemediationStep = .choose + } + + // remediations require restarting gertrude, so note the step to restart w/ + await store.receive(.delegate(.saveForResume(.at(step: .macosUserAccountType)))) + + await store.send(.webview(.chooseDemoteAdminClicked)) { + $0.userRemediationStep = .demote + } + } + + func testSettingStepDoesntOpenWindow() async { + let store = featureStore { + $0.windowOpen = false + } + // there's a 4 minute timeout for failed sys-ext install + // so the onboarding could be closed by the time that triggers + // sending a .setStep into the system, which should NOT reopen + await store.send(.setStep(.installSysExt_failed)) + store.assert { + $0.windowOpen = false + } + } + + // helpers + func featureStore( + mutateState: @escaping (inout OnboardingFeature.State) -> Void = { _ in } + ) -> TestStoreOf { + var state = OnboardingFeature.State() + mutateState(&state) + let store = TestStore(initialState: state) { + OnboardingFeature.Reducer() + } + store.exhaustivity = .off + return store + } +} diff --git a/macapp/App/Tests/AppTests/UserConnectionTests.swift b/macapp/App/Tests/AppTests/UserConnectionTests.swift new file mode 100644 index 00000000..f284cac3 --- /dev/null +++ b/macapp/App/Tests/AppTests/UserConnectionTests.swift @@ -0,0 +1,99 @@ +import ComposableArchitecture +import Core +import TestSupport +import XCTest +import XExpect + +@testable import App + +@MainActor final class UserConnectionTests: XCTestCase { + func testDisconnectingUserFromAdminWindowStateAndEffects() async { + await disconnectTest( + action: .adminAuthed(.adminWindow(.webview(.disconnectUserClicked))), + setupState: { + $0.adminWindow.windowOpen = true + $0.menuBar.dropdownOpen = false + }, + assertState: { + $0.adminWindow.windowOpen = false + $0.menuBar.dropdownOpen = true + } + ) + } + + func testDisconnectingUserFromWebsocketMsgStateAndEffects() async { + await disconnectTest( + action: .websocket(.receivedMessage(.userDeleted)), + extraReceivedActions: [ + .focusedNotification(.text( + "Child deleted", + "The child associated with this computer was deleted. You'll need to connect to a different child, or quit the app." + )), + ] + ) + } + + func testDisconnectingUserFromMissingUserTokenStateAndEffects() async { + await disconnectTest( + action: .history(.userConnection(.disconnectMissingUser)), + extraReceivedActions: [ + .focusedNotification(.text( + "Child deleted", + "The child associated with this computer was deleted. You'll need to connect to a different child, or quit the app." + )), + ] + ) + } +} + +@MainActor func disconnectTest( + action: AppReducer.Action, + setupState: @escaping (inout AppReducer.State) -> Void = { _ in }, + setupStore: (TestStoreOf) -> Void = { _ in }, + assertState: @escaping (inout AppReducer.State) -> Void = { _ in }, + extraReceivedActions: [AppReducer.Action] = [] +) async { + let (store, bgQueue) = AppReducer.testStore { + $0.user.data = .mock + $0.history.userConnection = .established(welcomeDismissed: true) + setupState(&$0) + } + + store.exhaustivity = .on + await store.withExhaustivity(.off) { + await store.send(.startProtecting(user: .mock)) + await store.skipReceivedActions() + await bgQueue.advance(by: .seconds(60)) + await store.receive(.heartbeat(.everyMinute)) // <-- heartbeat is running + } + + let clearApiToken = mock(always: ()) + store.deps.api.clearUserToken = clearApiToken.fn + let saveState = spy(on: Persistent.State.self, returning: ()) + store.deps.storage.savePersistentState = saveState.fn + let xpcDisconnect = mock(once: Result.success(())) + store.deps.filterXpc.disconnectUser = xpcDisconnect.fn + let disableLaunchAtLogin = mock(always: ()) + store.deps.app.disableLaunchAtLogin = disableLaunchAtLogin.fn + store.deps.monitoring.commitPendingKeystrokes = { _ in fatalError() } + store.deps.monitoring.takeScreenshot = { _ in fatalError() } + setupStore(store) + + await store.send(action) { + $0.user = .init() + $0.history.userConnection = .notConnected + assertState(&$0) + } + + for action in extraReceivedActions { + await store.receive(action) + } + + await expect(clearApiToken.invocations).toEqual(1) + await expect(saveState.invocations.value).toHaveCount(1) + await expect(xpcDisconnect.invocations).toEqual(1) + await expect(disableLaunchAtLogin.invocations).toEqual(1) + + // no heartbeat actions received, no timed screenshots + await bgQueue.advance(by: .seconds(60 * 10)) +} diff --git a/macapp/App/Tests/AppTests/UserFeatureTests.swift b/macapp/App/Tests/AppTests/UserFeatureTests.swift index 9a4cf220..b78127cc 100644 --- a/macapp/App/Tests/AppTests/UserFeatureTests.swift +++ b/macapp/App/Tests/AppTests/UserFeatureTests.swift @@ -41,9 +41,8 @@ import XExpect } func testSuccessfulApiRequestsRestartsCount() async { - let (store, _) = AppReducer.testStore { - $0.user = .init(data: .mock) - } + let (store, _) = AppReducer.testStore { $0.user = .init(data: .mock) } + store.deps.updater = .mock let error = PqlError( id: "123", diff --git a/macapp/App/Tests/Codegen/AppTypeScriptEnums.swift b/macapp/App/Tests/Codegen/AppTypeScriptEnums.swift index ee615fa1..680335c8 100644 --- a/macapp/App/Tests/Codegen/AppTypeScriptEnums.swift +++ b/macapp/App/Tests/Codegen/AppTypeScriptEnums.swift @@ -33,6 +33,12 @@ struct AppTypeScriptEnums: AggregateCodeGenerator { AdminWindowFeature.Action.View.self, ] ), + EnumCodableGen.EnumsGenerator( + name: "OnboardingFeature", + types: [ + OnboardingFeature.Action.View.self, + ] + ), ] func format() throws { diff --git a/macapp/App/Tests/Codegen/AppWebViews.swift b/macapp/App/Tests/Codegen/AppWebViews.swift index 51d91795..77330590 100644 --- a/macapp/App/Tests/Codegen/AppWebViews.swift +++ b/macapp/App/Tests/Codegen/AppWebViews.swift @@ -77,6 +77,26 @@ struct AppWebViews: AggregateCodeGenerator { (AdminAccountStatus.self, "AdminAccountStatus"), ] ), + AppviewStore( + at: "Onboarding/onboarding-store.ts", + namedTypes: [ + .init(OnboardingFeature.State.Step.self, as: "OnboardingStep"), + .init(MacOSVersion.DocumentationGroup.self, as: "OSGroup"), + .init(OnboardingFeature.State.MacUser.RemediationStep.self, as: "UserRemediationStep"), + .init(OnboardingFeature.State.MacUser.self, as: "MacOSUser"), + ], + types: [ + .init(OnboardingFeature.State.View.self, as: "AppState"), + .init(OnboardingFeature.Action.View.self, as: "AppEvent"), + ], + localAliases: [ + (OnboardingFeature.State.Step.self, "OnboardingStep"), + (MacOSVersion.DocumentationGroup.self, "OSGroup"), + (OnboardingFeature.State.MacUser.RemediationStep.self, "UserRemediationStep"), + (OnboardingFeature.State.MacUser.self, "MacOSUser"), + (PayloadRequestState.self, "RequestState"), + ] + ), ] func format() throws { diff --git a/macapp/App/Tests/Codegen/AppviewStore.swift b/macapp/App/Tests/Codegen/AppviewStore.swift index dad3d408..822bad3d 100644 --- a/macapp/App/Tests/Codegen/AppviewStore.swift +++ b/macapp/App/Tests/Codegen/AppviewStore.swift @@ -65,6 +65,9 @@ extension AppviewStore: CodeGenerator { let url = URL(fileURLWithPath: "/Users/jared/gertie/web/appviews/src/\(path)") let file = String(data: try Data(contentsOf: url), encoding: .utf8)! let lines = file.components(separatedBy: "\n") + guard lines.contains("// begin codegen"), lines.contains("// end codegen") else { + fatalError("codegen markers not found in \(path)") + } var updated: [String] = [] var inCodegen = false diff --git a/macapp/App/Tests/FilterTests/FilterReducerTests.swift b/macapp/App/Tests/FilterTests/FilterReducerTests.swift index 4f3c24fd..23bf2c13 100644 --- a/macapp/App/Tests/FilterTests/FilterReducerTests.swift +++ b/macapp/App/Tests/FilterTests/FilterReducerTests.swift @@ -12,7 +12,9 @@ import XExpect func testExtensionStarted_Exhaustive() async { let (store, mainQueue) = Filter.testStore(exhaustive: true) let subject = PassthroughSubject() + store.deps.filterExtension = .mock store.deps.xpc.events = { subject.eraseToAnyPublisher() } + store.deps.xpc.stopListener = {} let startListener = mock(always: ()) store.deps.xpc.startListener = startListener.fn store.deps.storage.loadPersistentState = { .init( @@ -73,6 +75,7 @@ import XExpect func testStreamBlockedRequests() async { let (store, _) = Filter.testStore(exhaustive: true) + store.deps.filterExtension = .mock // user not streaming, so we won't send the request store.deps.xpc.sendBlockedRequest = { _, _ in fatalError() } @@ -114,6 +117,7 @@ import XExpect let (store, _) = Filter.testStore() let sendBlocked = spy2(on: (uid_t.self, BlockedRequest.self), returning: ()) store.deps.xpc.sendBlockedRequest = sendBlocked.fn + store.deps.filterExtension = .mock await store.send(.xpc(.receivedAppMessage(.setBlockStreaming( enabled: true, @@ -159,6 +163,7 @@ import XExpect let save = spy(on: Persistent.State.self, returning: ()) store.deps.storage.savePersistentState = save.fn + store.deps.filterExtension = .mock await store.send(.xpc(.receivedAppMessage(.disconnectUser(userId: 502)))) { $0.userKeys = [503: [key2]] @@ -174,9 +179,8 @@ import XExpect } func testSetUserExemption() async { - let (store, _) = Filter.testStore { - $0.exemptUsers = [501] - } + let (store, _) = Filter.testStore { $0.exemptUsers = [501] } + store.deps.filterExtension = .mock let save = spy(on: Persistent.State.self, returning: ()) store.deps.storage.savePersistentState = save.fn @@ -200,6 +204,7 @@ import XExpect let (store, mainQueue) = Filter.testStore { $0.suspensions = [503: otherUserSuspension] } + store.deps.filterExtension = .mock let notifyExpired = spy(on: uid_t.self, returning: ()) store.deps.xpc.notifyFilterSuspensionEnded = notifyExpired.fn @@ -230,6 +235,7 @@ import XExpect let notifyExpired = spy(on: uid_t.self, returning: ()) store.deps.xpc.notifyFilterSuspensionEnded = notifyExpired.fn + store.deps.filterExtension = .mock await store.send(.xpc(.receivedAppMessage(.suspendFilter(userId: 502, duration: 600)))) { $0.suspensions = [ @@ -251,6 +257,7 @@ import XExpect let notifyExpired = spy(on: uid_t.self, returning: ()) store.deps.xpc.notifyFilterSuspensionEnded = notifyExpired.fn + store.deps.filterExtension = .mock await store.send(.xpc(.receivedAppMessage(.suspendFilter(userId: 502, duration: 600)))) { $0.suspensions = [ @@ -308,6 +315,10 @@ import XExpect func testHeartbeatCleansUpDanglingSuspensionFromSleepConfusingTimer() async { let (store, mainQueue) = Filter.testStore() + store.deps.filterExtension = .mock + store.deps.xpc.events = XPCClient.mock.events + store.deps.xpc.startListener = {} + store.deps.storage.loadPersistentState = StorageClient.mock.loadPersistentState let time = ControllingNow(starting: .epoch, with: mainQueue) store.deps.date = time.generator diff --git a/macapp/Xcode/Gertrude/WebViews/Administrate/index.js b/macapp/Xcode/Gertrude/WebViews/Administrate/index.js index 478dee82..aca36bb5 100644 --- a/macapp/Xcode/Gertrude/WebViews/Administrate/index.js +++ b/macapp/Xcode/Gertrude/WebViews/Administrate/index.js @@ -68,7 +68,7 @@ Error generating stack: `+i.message+` * LICENSE.md file in the root directory of this source tree. * * @license MIT - */function Io(){return Io=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function A0(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function U0(e,t){return e.button===0&&(!t||t==="_self")&&!A0(e)}const b0=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset"],V0=y.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:i,replace:o,state:u,target:s,to:a,preventScrollReset:p}=t,f=j0(t,b0),m=D0(a,{relative:l}),g=H0(a,{replace:o,state:u,target:s,preventScrollReset:p,relative:l});function v(x){r&&r(x),x.defaultPrevented||g(x)}return y.createElement("a",Io({},f,{href:m,onClick:i?r:v,ref:n,target:s}))});var ea;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmitImpl="useSubmitImpl",e.UseFetcher="useFetcher"})(ea||(ea={}));var ta;(function(e){e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(ta||(ta={}));function H0(e,t){let{target:n,replace:r,state:l,preventScrollReset:i,relative:o}=t===void 0?{}:t,u=M0(),s=zu(),a=Ed(e,{relative:o});return y.useCallback(p=>{if(U0(p,n)){p.preventDefault();let f=r!==void 0?r:Ys(s)===Ys(a);u(e,{replace:f,state:l,preventScrollReset:i,relative:o})}},[s,u,a,r,l,n,e,i,o])}const q=({size:e="medium",fullWidth:t=!1,testId:n,color:r,className:l,disabled:i=!1,...o})=>{let u="";if(i)u="bg-slate-50 dark:bg-black/50 text-slate-400 dark:text-slate-600 border border-slate-200 dark:border-slate-800 cursor-not-allowed ring-transparent focus:ring-slate-200";else switch(r){case"primary-on-violet-bg":u="bg-white text-violet-500 hover:bg-violet-50 border-2 border-white hover:border-violet-50 ring-violet-500 ring-offset-violet-500 focus:ring-white";break;case"secondary-on-violet-bg":u="bg-violet-500 text-white border-2 border-white hover:bg-violet-400 ring-violet-500 focus:ring-white ring-offset-violet-500";break;case"primary":u="bg-violet-700 dark:bg-violet-700 border border-violet-700 dark:border-violet-700 hover:border-violet-800 dark:hover:border-violet-700 text-white dark:hover:bg-violet-700 hover:bg-violet-800 ring-transparent focus:ring-violet-700 dark:ring-offset-slate-900";break;case"secondary":u="bg-violet-100 dark:bg-slate-800/80 border border-violet-100 dark:border-slate-700/70 hover:border-violet-200 dark:hover:bg-slate-700/80 text-violet-600 dark:text-white/80 hover:bg-violet-200 ring-transparent focus:ring-violet-300 dark:focus:ring-indigo-500/70 dark:ring-offset-slate-900";break;case"tertiary":u="bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 border dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 ring-transparent focus:ring-indigo-400/50 focus:border-indigo-200 dark:ring-offset-slate-900";break;case"warning":u="bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-300 border-red-100 dark:border-red-600/50 border hover:text-red-700 hover:bg-red-100 dark:hover:bg-red-500/20 dark:hover:text-red-200 ring-transparent focus:ring-red-500 focus:border-red-500 dark:ring-offset-slate-900";break}const s=o.type==="button"||o.type==="submit";let a="";switch(e){case"small":a="text-base px-4 py-1.5 font-semibold";break;case"medium":a="text-base px-5 py-3 sm:py-2.5";break;default:a="text-lg px-10 py-2.5";break}const p=W(u,"ring ring-offset-0 focus:ring-offset-2 rounded-xl font-bold transition duration-100 outline-none block",a,t?"w-full":"w-auto",!s&&"text-center",l);return s?k("button",{type:o.type,className:p,disabled:i,...n?{"data-test":n}:{},...o.type==="button"?{onClick:i?()=>{}:()=>o.onClick()}:{},children:o.children}):o.type==="external"?k("a",{className:p,...n?{"data-test":n}:{},...i?{onClick:f=>f.preventDefault()}:{href:o.href},children:o.children}):k(V0,{className:p,to:i?"#":o.to,...n?{"data-test":n}:{},onClick:i?f=>f.preventDefault():()=>{},children:o.children})},Cd=({children:e,htmlFor:t,className:n})=>k("label",{htmlFor:t,className:W("text-left text-slate-500 dark:text-slate-300 font-semibold text-md mb-1",n),children:e});function B0(e,t){return t===1?e:`${e}s`}function W0(e){return Q0(new Date(e),"dateInput")}function Q0(e,t){return t==="short"?e.toLocaleDateString():t==="url"?[`${e.getMonth()+1}`.padStart(2,"0"),`${e.getDate()}`.padStart(2,"0"),`${e.getFullYear()}`].join("-"):t==="dateInput"?[`${e.getFullYear()}`,`${e.getMonth()+1}`.padStart(2,"0"),`${e.getDate()}`.padStart(2,"0")].join("-"):[e.toLocaleDateString("en-US",{weekday:"long"}),", ",e.toLocaleDateString("en-US",{month:t==="long"?"long":"short"})," ",e.getDate(),", ",e.getFullYear()].join("")}const _i=({label:e,optional:t,value:n,setValue:r,required:l=!1,autoFocus:i=!1,placeholder:o,className:u,disabled:s,name:a,testId:p,...f})=>{const[m,g]=y.useState(n),v=y.useId(),x=bt(f)?"input":"textarea";return L("div",{className:W("flex flex-col space-y-1 w-full",u),children:[(e||t)&&L("div",{className:"flex flex-row justify-between items-center",children:[e&&k(Cd,{htmlFor:v,children:e}),t&&k("span",{className:"text-violet-500/80 translate-y-px text-sm antialiased italic",children:"*optional"})]}),L("div",{className:"flex shadow-sm rounded-lg",children:[bt(f)&&f.prefix&&k("div",{className:"hidden xs:flex justify-center items-center p-3 bg-slate-50 dark:bg-slate-700/50 border border-r-0 dark:border-slate-700 rounded-l-lg",children:k("h3",{className:"text-slate-500 dark:text-slate-400",children:f.prefix})}),k(x,{id:v,type:f.type==="positiveInteger"?"number":f.type,value:m,required:!!l,autoFocus:i,placeholder:o,disabled:s,name:a,...p?{"data-test":p}:{},...f.type==="url"?{autoCapitalize:"none",autoCorrect:"off"}:{},...f.type==="password"?{minLength:4}:{},...f.type==="date"?{min:W0(new Date().toISOString())}:{},...bt(f)?{}:{rows:f.rows},onChange:N=>{const d=N.target.value;g(d),(f.type!=="positiveInteger"||K0(d))&&r(d)},className:W("py-3 px-4 flex-grow w-12","border border-slate-200 rounded-lg","transition-[border-color,ring-color] duration-150","text-slate-600 placeholder:text-slate-400/90 placeholder:antialiased","ring-0 ring-slate-200 outline-none focus:shadow-md focus:border-indigo-500 focus:ring-indigo-500 focus:ring-1","dark:bg-slate-700/20 dark:border-slate-700 dark:placeholder:text-slate-500 dark:text-white",!bt(f)&&f.noResize&&"resize-none",bt(f)&&f.unit&&"rounded-r-none",bt(f)&&f.prefix&&"xs:rounded-l-none")}),bt(f)&&f.unit&&k("div",{className:"flex justify-center items-center p-3 bg-slate-50 dark:bg-slate-700/50 border border-l-0 dark:border-slate-700 rounded-r-lg",children:k("h3",{className:"text-slate-500 dark:text-slate-400",children:f.unit})})]})]})};function K0(e){return e.match(/^[0-9]+$/)!==null&&Number.isInteger(Number(e))&&Number(e)>=0}function bt(e){return e.type!=="textarea"}var G0=Object.defineProperty,Y0=(e,t,n)=>t in e?G0(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Li=(e,t,n)=>(Y0(e,typeof t!="symbol"?t+"":t,n),n);let X0=class{constructor(){Li(this,"current",this.detect()),Li(this,"handoffState","pending"),Li(this,"currentId",0)}set(t){this.current!==t&&(this.handoffState="pending",this.currentId=0,this.current=t)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return this.current==="server"}get isClient(){return this.current==="client"}detect(){return typeof window>"u"||typeof document>"u"?"server":"client"}handoff(){this.handoffState==="pending"&&(this.handoffState="complete")}get isHandoffComplete(){return this.handoffState==="complete"}},zt=new X0,be=(e,t)=>{zt.isServer?y.useEffect(e,t):y.useLayoutEffect(e,t)};function ct(e){let t=y.useRef(e);return be(()=>{t.current=e},[e]),t}function Nd(e,t){let[n,r]=y.useState(e),l=ct(e);return be(()=>r(l.current),[l,r,...t]),n}function Z0(e){typeof queueMicrotask=="function"?queueMicrotask(e):Promise.resolve().then(e).catch(t=>setTimeout(()=>{throw t}))}function tn(){let e=[],t=[],n={enqueue(r){t.push(r)},addEventListener(r,l,i,o){return r.addEventListener(l,i,o),n.add(()=>r.removeEventListener(l,i,o))},requestAnimationFrame(...r){let l=requestAnimationFrame(...r);return n.add(()=>cancelAnimationFrame(l))},nextFrame(...r){return n.requestAnimationFrame(()=>n.requestAnimationFrame(...r))},setTimeout(...r){let l=setTimeout(...r);return n.add(()=>clearTimeout(l))},microTask(...r){let l={current:!0};return Z0(()=>{l.current&&r[0]()}),n.add(()=>{l.current=!1})},add(r){return e.push(r),()=>{let l=e.indexOf(r);if(l>=0){let[i]=e.splice(l,1);i()}}},dispose(){for(let r of e.splice(0))r()},async workQueue(){for(let r of t.splice(0))await r()},style(r,l,i){let o=r.style.getPropertyValue(l);return Object.assign(r.style,{[l]:i}),this.add(()=>{Object.assign(r.style,{[l]:o})})}};return n}function In(){let[e]=y.useState(tn);return y.useEffect(()=>()=>e.dispose(),[e]),e}let A=function(e){let t=ct(e);return ue.useCallback((...n)=>t.current(...n),[t])};function $u(){let[e,t]=y.useState(zt.isHandoffComplete);return e&&zt.isHandoffComplete===!1&&t(!1),y.useEffect(()=>{e!==!0&&t(!0)},[e]),y.useEffect(()=>zt.handoff(),[]),e}var na;let Xl=(na=ue.useId)!=null?na:function(){let e=$u(),[t,n]=ue.useState(e?()=>zt.nextId():null);return be(()=>{t===null&&n(zt.nextId())},[t]),t!=null?""+t:void 0};function ce(e,t,...n){if(e in t){let l=t[e];return typeof l=="function"?l(...n):l}let r=new Error(`Tried to handle "${e}" but there is no handler defined. Only defined handlers are: ${Object.keys(t).map(l=>`"${l}"`).join(", ")}.`);throw Error.captureStackTrace&&Error.captureStackTrace(r,ce),r}function Pd(e){return zt.isServer?null:e instanceof Node?e.ownerDocument:e!=null&&e.hasOwnProperty("current")&&e.current instanceof Node?e.current.ownerDocument:document}let ra=["[contentEditable=true]","[tabindex]","a[href]","area[href]","button:not([disabled])","iframe","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].map(e=>`${e}:not([tabindex='-1'])`).join(",");var J0=(e=>(e[e.First=1]="First",e[e.Previous=2]="Previous",e[e.Next=4]="Next",e[e.Last=8]="Last",e[e.WrapAround=16]="WrapAround",e[e.NoScroll=32]="NoScroll",e))(J0||{}),q0=(e=>(e[e.Error=0]="Error",e[e.Overflow=1]="Overflow",e[e.Success=2]="Success",e[e.Underflow=3]="Underflow",e))(q0||{}),eh=(e=>(e[e.Previous=-1]="Previous",e[e.Next=1]="Next",e))(eh||{}),Iu=(e=>(e[e.Strict=0]="Strict",e[e.Loose=1]="Loose",e))(Iu||{});function Od(e,t=0){var n;return e===((n=Pd(e))==null?void 0:n.body)?!1:ce(t,{[0](){return e.matches(ra)},[1](){let r=e;for(;r!==null;){if(r.matches(ra))return!0;r=r.parentElement}return!1}})}function th(e,t=n=>n){return e.slice().sort((n,r)=>{let l=t(n),i=t(r);if(l===null||i===null)return 0;let o=l.compareDocumentPosition(i);return o&Node.DOCUMENT_POSITION_FOLLOWING?-1:o&Node.DOCUMENT_POSITION_PRECEDING?1:0})}function Ti(e,t,n){let r=ct(t);y.useEffect(()=>{function l(i){r.current(i)}return document.addEventListener(e,l,n),()=>document.removeEventListener(e,l,n)},[e,n])}function nh(e,t,n=!0){let r=y.useRef(!1);y.useEffect(()=>{requestAnimationFrame(()=>{r.current=n})},[n]);function l(o,u){if(!r.current||o.defaultPrevented)return;let s=function p(f){return typeof f=="function"?p(f()):Array.isArray(f)||f instanceof Set?f:[f]}(e),a=u(o);if(a!==null&&a.getRootNode().contains(a)){for(let p of s){if(p===null)continue;let f=p instanceof HTMLElement?p:p.current;if(f!=null&&f.contains(a)||o.composed&&o.composedPath().includes(f))return}return!Od(a,Iu.Loose)&&a.tabIndex!==-1&&o.preventDefault(),t(o,a)}}let i=y.useRef(null);Ti("mousedown",o=>{var u,s;r.current&&(i.current=((s=(u=o.composedPath)==null?void 0:u.call(o))==null?void 0:s[0])||o.target)},!0),Ti("click",o=>{!i.current||(l(o,()=>i.current),i.current=null)},!0),Ti("blur",o=>l(o,()=>window.document.activeElement instanceof HTMLIFrameElement?window.document.activeElement:null),!0)}function la(e){var t;if(e.type)return e.type;let n=(t=e.as)!=null?t:"button";if(typeof n=="string"&&n.toLowerCase()==="button")return"button"}function rh(e,t){let[n,r]=y.useState(()=>la(e));return be(()=>{r(la(e))},[e.type,e.as]),be(()=>{n||!t.current||t.current instanceof HTMLButtonElement&&!t.current.hasAttribute("type")&&r("button")},[n,t]),n}let lh=Symbol();function ln(...e){let t=y.useRef(e);y.useEffect(()=>{t.current=e},[e]);let n=A(r=>{for(let l of t.current)l!=null&&(typeof l=="function"?l(r):l.current=r)});return e.every(r=>r==null||r?.[lh])?void 0:n}function ih(e){throw new Error("Unexpected object: "+e)}var Ee=(e=>(e[e.First=0]="First",e[e.Previous=1]="Previous",e[e.Next=2]="Next",e[e.Last=3]="Last",e[e.Specific=4]="Specific",e[e.Nothing=5]="Nothing",e))(Ee||{});function oh(e,t){let n=t.resolveItems();if(n.length<=0)return null;let r=t.resolveActiveIndex(),l=r??-1,i=(()=>{switch(e.focus){case 0:return n.findIndex(o=>!t.resolveDisabled(o));case 1:{let o=n.slice().reverse().findIndex((u,s,a)=>l!==-1&&a.length-s-1>=l?!1:!t.resolveDisabled(u));return o===-1?o:n.length-1-o}case 2:return n.findIndex((o,u)=>u<=l?!1:!t.resolveDisabled(o));case 3:{let o=n.slice().reverse().findIndex(u=>!t.resolveDisabled(u));return o===-1?o:n.length-1-o}case 4:return n.findIndex(o=>t.resolveId(o)===e.id);case 5:return null;default:ih(e)}})();return i===-1?r:i}function _d(...e){return e.filter(Boolean).join(" ")}var $l=(e=>(e[e.None=0]="None",e[e.RenderStrategy=1]="RenderStrategy",e[e.Static=2]="Static",e))($l||{}),st=(e=>(e[e.Unmount=0]="Unmount",e[e.Hidden=1]="Hidden",e))(st||{});function jt({ourProps:e,theirProps:t,slot:n,defaultTag:r,features:l,visible:i=!0,name:o}){let u=Ld(t,e);if(i)return Gr(u,n,r,o);let s=l??0;if(s&2){let{static:a=!1,...p}=u;if(a)return Gr(p,n,r,o)}if(s&1){let{unmount:a=!0,...p}=u;return ce(a?0:1,{[0](){return null},[1](){return Gr({...p,hidden:!0,style:{display:"none"}},n,r,o)}})}return Gr(u,n,r,o)}function Gr(e,t={},n,r){var l;let{as:i=n,children:o,refName:u="ref",...s}=Ri(e,["unmount","static"]),a=e.ref!==void 0?{[u]:e.ref}:{},p=typeof o=="function"?o(t):o;s.className&&typeof s.className=="function"&&(s.className=s.className(t));let f={};if(t){let m=!1,g=[];for(let[v,x]of Object.entries(t))typeof x=="boolean"&&(m=!0),x===!0&&g.push(v);m&&(f["data-headlessui-state"]=g.join(" "))}if(i===y.Fragment&&Object.keys(Fo(s)).length>0){if(!y.isValidElement(p)||Array.isArray(p)&&p.length>1)throw new Error(['Passing props on "Fragment"!',"",`The current component <${r} /> is rendering a "Fragment".`,"However we need to passthrough the following props:",Object.keys(s).map(v=>` - ${v}`).join(` + */function Io(){return Io=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function A0(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function U0(e,t){return e.button===0&&(!t||t==="_self")&&!A0(e)}const b0=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset"],V0=y.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:i,replace:o,state:u,target:s,to:a,preventScrollReset:p}=t,f=j0(t,b0),m=D0(a,{relative:l}),g=H0(a,{replace:o,state:u,target:s,preventScrollReset:p,relative:l});function v(x){r&&r(x),x.defaultPrevented||g(x)}return y.createElement("a",Io({},f,{href:m,onClick:i?r:v,ref:n,target:s}))});var ea;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmitImpl="useSubmitImpl",e.UseFetcher="useFetcher"})(ea||(ea={}));var ta;(function(e){e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(ta||(ta={}));function H0(e,t){let{target:n,replace:r,state:l,preventScrollReset:i,relative:o}=t===void 0?{}:t,u=M0(),s=zu(),a=Ed(e,{relative:o});return y.useCallback(p=>{if(U0(p,n)){p.preventDefault();let f=r!==void 0?r:Ys(s)===Ys(a);u(e,{replace:f,state:l,preventScrollReset:i,relative:o})}},[s,u,a,r,l,n,e,i,o])}const q=({size:e="medium",fullWidth:t=!1,testId:n,color:r,className:l,disabled:i=!1,...o})=>{let u="";if(i)u="bg-slate-50 dark:bg-black/50 text-slate-400 dark:text-slate-600 border border-slate-200 dark:border-slate-800 cursor-not-allowed ring-transparent focus:ring-slate-200";else switch(r){case"primary-on-violet-bg":u="bg-white text-violet-500 hover:bg-violet-50 border-2 border-white hover:border-violet-50 ring-violet-500 ring-offset-violet-500 focus:ring-white";break;case"secondary-on-violet-bg":u="bg-violet-500 text-white border-2 border-white hover:bg-violet-400 ring-violet-500 focus:ring-white ring-offset-violet-500";break;case"primary":u="bg-violet-700 dark:bg-violet-700 border border-violet-700 dark:border-violet-700 hover:border-violet-800 dark:hover:border-violet-700 text-white dark:hover:bg-violet-700 hover:bg-violet-800 ring-transparent focus:ring-violet-700 dark:ring-offset-slate-900";break;case"secondary":u="bg-violet-100 dark:bg-slate-800/80 border border-violet-100 dark:border-slate-700/70 hover:border-violet-200 dark:hover:bg-slate-700/80 text-violet-600 dark:text-white/80 hover:bg-violet-200 ring-transparent focus:ring-violet-300 dark:focus:ring-indigo-500/70 dark:ring-offset-slate-900";break;case"tertiary":u="bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 border dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 ring-transparent focus:ring-indigo-400/50 focus:border-indigo-200 dark:ring-offset-slate-900";break;case"warning":u="bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-300 border-red-100 dark:border-red-600/50 border hover:text-red-700 hover:bg-red-100 dark:hover:bg-red-500/20 dark:hover:text-red-200 ring-transparent focus:ring-red-500 focus:border-red-500 dark:ring-offset-slate-900";break}const s=o.type==="button"||o.type==="submit";let a="";switch(e){case"small":a="text-base px-4 py-1.5 font-semibold";break;case"medium":a="text-base px-5 py-3 sm:py-2.5";break;default:a="text-lg px-10 py-2.5";break}const p=W(u,"ring ring-offset-0 focus:ring-offset-2 rounded-xl font-bold transition duration-200 outline-none block active:scale-[0.98] leading-[1.25em] select-none",a,t?"w-full":"w-auto",!s&&"text-center",l);return s?k("button",{type:o.type,className:p,disabled:i,...n?{"data-test":n}:{},...o.type==="button"?{onClick:i?()=>{}:()=>o.onClick()}:{},children:o.children}):o.type==="external"?k("a",{className:p,...n?{"data-test":n}:{},...i?{onClick:f=>f.preventDefault()}:{href:o.href},children:o.children}):k(V0,{className:p,to:i?"#":o.to,...n?{"data-test":n}:{},onClick:i?f=>f.preventDefault():()=>{},children:o.children})},Cd=({children:e,htmlFor:t,className:n})=>k("label",{htmlFor:t,className:W("text-left text-slate-700 dark:text-slate-200 font-medium text-md mb-1",n),children:e});function B0(e,t){return t===1?e:`${e}s`}function W0(e){return Q0(new Date(e),"dateInput")}function Q0(e,t){return t==="short"?e.toLocaleDateString():t==="url"?[`${e.getMonth()+1}`.padStart(2,"0"),`${e.getDate()}`.padStart(2,"0"),`${e.getFullYear()}`].join("-"):t==="dateInput"?[`${e.getFullYear()}`,`${e.getMonth()+1}`.padStart(2,"0"),`${e.getDate()}`.padStart(2,"0")].join("-"):[e.toLocaleDateString("en-US",{weekday:"long"}),", ",e.toLocaleDateString("en-US",{month:t==="long"?"long":"short"})," ",e.getDate(),", ",e.getFullYear()].join("")}const _i=({label:e,optional:t,value:n,setValue:r,required:l=!1,autoFocus:i=!1,placeholder:o,className:u,disabled:s,name:a,testId:p,...f})=>{const[m,g]=y.useState(n),v=y.useId(),x=bt(f)?"input":"textarea";return L("div",{className:W("flex flex-col space-y-1 w-full",u),children:[(e||t)&&L("div",{className:"flex flex-row justify-between items-center",children:[e&&k(Cd,{htmlFor:v,children:e}),t&&k("span",{className:"text-violet-500/80 font-medium translate-y-px text-sm antialiased italic",children:"*optional"})]}),L("div",{className:"flex shadow-sm rounded-xl",children:[bt(f)&&f.prefix&&k("div",{className:"hidden xs:flex justify-center items-center p-3 bg-slate-50 dark:bg-slate-700/50 border border-r-0 dark:border-slate-700 rounded-l-xl",children:k("h3",{className:"text-slate-500 dark:text-slate-400",children:f.prefix})}),k(x,{id:v,type:f.type==="positiveInteger"?"number":f.type,value:m,required:!!l,autoFocus:i,placeholder:o,disabled:s,name:a,...p?{"data-test":p}:{},...f.type==="url"?{autoCapitalize:"none",autoCorrect:"off"}:{},...f.type==="password"?{minLength:4}:{},...f.type==="date"?{min:W0(new Date().toISOString())}:{},...bt(f)?{}:{rows:f.rows},onChange:N=>{const d=N.target.value;g(d),(f.type!=="positiveInteger"||K0(d))&&r(d)},className:W("py-3 px-4 flex-grow w-12","border border-slate-200 rounded-xl","transition-[border-color,ring-color] duration-150","text-slate-600 placeholder:text-slate-400/90 placeholder:antialiased","ring-0 ring-slate-200 outline-none focus:shadow-md focus:border-indigo-500 focus:ring-indigo-500 focus:ring-1","dark:bg-slate-700/20 dark:border-slate-700 dark:placeholder:text-slate-500 dark:text-white",!bt(f)&&f.noResize&&"resize-none",bt(f)&&f.unit&&"rounded-r-none",bt(f)&&f.prefix&&"xs:rounded-l-none")}),bt(f)&&f.unit&&k("div",{className:"flex justify-center items-center p-3 bg-slate-50 dark:bg-slate-700/50 border border-l-0 dark:border-slate-700 rounded-r-xl",children:k("h3",{className:"text-slate-500 dark:text-slate-400",children:f.unit})})]})]})};function K0(e){return e.match(/^[0-9]+$/)!==null&&Number.isInteger(Number(e))&&Number(e)>=0}function bt(e){return e.type!=="textarea"}var G0=Object.defineProperty,Y0=(e,t,n)=>t in e?G0(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Li=(e,t,n)=>(Y0(e,typeof t!="symbol"?t+"":t,n),n);let X0=class{constructor(){Li(this,"current",this.detect()),Li(this,"handoffState","pending"),Li(this,"currentId",0)}set(t){this.current!==t&&(this.handoffState="pending",this.currentId=0,this.current=t)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return this.current==="server"}get isClient(){return this.current==="client"}detect(){return typeof window>"u"||typeof document>"u"?"server":"client"}handoff(){this.handoffState==="pending"&&(this.handoffState="complete")}get isHandoffComplete(){return this.handoffState==="complete"}},zt=new X0,be=(e,t)=>{zt.isServer?y.useEffect(e,t):y.useLayoutEffect(e,t)};function ct(e){let t=y.useRef(e);return be(()=>{t.current=e},[e]),t}function Nd(e,t){let[n,r]=y.useState(e),l=ct(e);return be(()=>r(l.current),[l,r,...t]),n}function Z0(e){typeof queueMicrotask=="function"?queueMicrotask(e):Promise.resolve().then(e).catch(t=>setTimeout(()=>{throw t}))}function tn(){let e=[],t=[],n={enqueue(r){t.push(r)},addEventListener(r,l,i,o){return r.addEventListener(l,i,o),n.add(()=>r.removeEventListener(l,i,o))},requestAnimationFrame(...r){let l=requestAnimationFrame(...r);return n.add(()=>cancelAnimationFrame(l))},nextFrame(...r){return n.requestAnimationFrame(()=>n.requestAnimationFrame(...r))},setTimeout(...r){let l=setTimeout(...r);return n.add(()=>clearTimeout(l))},microTask(...r){let l={current:!0};return Z0(()=>{l.current&&r[0]()}),n.add(()=>{l.current=!1})},add(r){return e.push(r),()=>{let l=e.indexOf(r);if(l>=0){let[i]=e.splice(l,1);i()}}},dispose(){for(let r of e.splice(0))r()},async workQueue(){for(let r of t.splice(0))await r()},style(r,l,i){let o=r.style.getPropertyValue(l);return Object.assign(r.style,{[l]:i}),this.add(()=>{Object.assign(r.style,{[l]:o})})}};return n}function In(){let[e]=y.useState(tn);return y.useEffect(()=>()=>e.dispose(),[e]),e}let A=function(e){let t=ct(e);return ue.useCallback((...n)=>t.current(...n),[t])};function $u(){let[e,t]=y.useState(zt.isHandoffComplete);return e&&zt.isHandoffComplete===!1&&t(!1),y.useEffect(()=>{e!==!0&&t(!0)},[e]),y.useEffect(()=>zt.handoff(),[]),e}var na;let Xl=(na=ue.useId)!=null?na:function(){let e=$u(),[t,n]=ue.useState(e?()=>zt.nextId():null);return be(()=>{t===null&&n(zt.nextId())},[t]),t!=null?""+t:void 0};function ce(e,t,...n){if(e in t){let l=t[e];return typeof l=="function"?l(...n):l}let r=new Error(`Tried to handle "${e}" but there is no handler defined. Only defined handlers are: ${Object.keys(t).map(l=>`"${l}"`).join(", ")}.`);throw Error.captureStackTrace&&Error.captureStackTrace(r,ce),r}function Pd(e){return zt.isServer?null:e instanceof Node?e.ownerDocument:e!=null&&e.hasOwnProperty("current")&&e.current instanceof Node?e.current.ownerDocument:document}let ra=["[contentEditable=true]","[tabindex]","a[href]","area[href]","button:not([disabled])","iframe","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].map(e=>`${e}:not([tabindex='-1'])`).join(",");var J0=(e=>(e[e.First=1]="First",e[e.Previous=2]="Previous",e[e.Next=4]="Next",e[e.Last=8]="Last",e[e.WrapAround=16]="WrapAround",e[e.NoScroll=32]="NoScroll",e))(J0||{}),q0=(e=>(e[e.Error=0]="Error",e[e.Overflow=1]="Overflow",e[e.Success=2]="Success",e[e.Underflow=3]="Underflow",e))(q0||{}),eh=(e=>(e[e.Previous=-1]="Previous",e[e.Next=1]="Next",e))(eh||{}),Iu=(e=>(e[e.Strict=0]="Strict",e[e.Loose=1]="Loose",e))(Iu||{});function Od(e,t=0){var n;return e===((n=Pd(e))==null?void 0:n.body)?!1:ce(t,{[0](){return e.matches(ra)},[1](){let r=e;for(;r!==null;){if(r.matches(ra))return!0;r=r.parentElement}return!1}})}function th(e,t=n=>n){return e.slice().sort((n,r)=>{let l=t(n),i=t(r);if(l===null||i===null)return 0;let o=l.compareDocumentPosition(i);return o&Node.DOCUMENT_POSITION_FOLLOWING?-1:o&Node.DOCUMENT_POSITION_PRECEDING?1:0})}function Ti(e,t,n){let r=ct(t);y.useEffect(()=>{function l(i){r.current(i)}return document.addEventListener(e,l,n),()=>document.removeEventListener(e,l,n)},[e,n])}function nh(e,t,n=!0){let r=y.useRef(!1);y.useEffect(()=>{requestAnimationFrame(()=>{r.current=n})},[n]);function l(o,u){if(!r.current||o.defaultPrevented)return;let s=function p(f){return typeof f=="function"?p(f()):Array.isArray(f)||f instanceof Set?f:[f]}(e),a=u(o);if(a!==null&&a.getRootNode().contains(a)){for(let p of s){if(p===null)continue;let f=p instanceof HTMLElement?p:p.current;if(f!=null&&f.contains(a)||o.composed&&o.composedPath().includes(f))return}return!Od(a,Iu.Loose)&&a.tabIndex!==-1&&o.preventDefault(),t(o,a)}}let i=y.useRef(null);Ti("mousedown",o=>{var u,s;r.current&&(i.current=((s=(u=o.composedPath)==null?void 0:u.call(o))==null?void 0:s[0])||o.target)},!0),Ti("click",o=>{!i.current||(l(o,()=>i.current),i.current=null)},!0),Ti("blur",o=>l(o,()=>window.document.activeElement instanceof HTMLIFrameElement?window.document.activeElement:null),!0)}function la(e){var t;if(e.type)return e.type;let n=(t=e.as)!=null?t:"button";if(typeof n=="string"&&n.toLowerCase()==="button")return"button"}function rh(e,t){let[n,r]=y.useState(()=>la(e));return be(()=>{r(la(e))},[e.type,e.as]),be(()=>{n||!t.current||t.current instanceof HTMLButtonElement&&!t.current.hasAttribute("type")&&r("button")},[n,t]),n}let lh=Symbol();function ln(...e){let t=y.useRef(e);y.useEffect(()=>{t.current=e},[e]);let n=A(r=>{for(let l of t.current)l!=null&&(typeof l=="function"?l(r):l.current=r)});return e.every(r=>r==null||r?.[lh])?void 0:n}function ih(e){throw new Error("Unexpected object: "+e)}var Ee=(e=>(e[e.First=0]="First",e[e.Previous=1]="Previous",e[e.Next=2]="Next",e[e.Last=3]="Last",e[e.Specific=4]="Specific",e[e.Nothing=5]="Nothing",e))(Ee||{});function oh(e,t){let n=t.resolveItems();if(n.length<=0)return null;let r=t.resolveActiveIndex(),l=r??-1,i=(()=>{switch(e.focus){case 0:return n.findIndex(o=>!t.resolveDisabled(o));case 1:{let o=n.slice().reverse().findIndex((u,s,a)=>l!==-1&&a.length-s-1>=l?!1:!t.resolveDisabled(u));return o===-1?o:n.length-1-o}case 2:return n.findIndex((o,u)=>u<=l?!1:!t.resolveDisabled(o));case 3:{let o=n.slice().reverse().findIndex(u=>!t.resolveDisabled(u));return o===-1?o:n.length-1-o}case 4:return n.findIndex(o=>t.resolveId(o)===e.id);case 5:return null;default:ih(e)}})();return i===-1?r:i}function _d(...e){return e.filter(Boolean).join(" ")}var $l=(e=>(e[e.None=0]="None",e[e.RenderStrategy=1]="RenderStrategy",e[e.Static=2]="Static",e))($l||{}),st=(e=>(e[e.Unmount=0]="Unmount",e[e.Hidden=1]="Hidden",e))(st||{});function jt({ourProps:e,theirProps:t,slot:n,defaultTag:r,features:l,visible:i=!0,name:o}){let u=Ld(t,e);if(i)return Gr(u,n,r,o);let s=l??0;if(s&2){let{static:a=!1,...p}=u;if(a)return Gr(p,n,r,o)}if(s&1){let{unmount:a=!0,...p}=u;return ce(a?0:1,{[0](){return null},[1](){return Gr({...p,hidden:!0,style:{display:"none"}},n,r,o)}})}return Gr(u,n,r,o)}function Gr(e,t={},n,r){var l;let{as:i=n,children:o,refName:u="ref",...s}=Ri(e,["unmount","static"]),a=e.ref!==void 0?{[u]:e.ref}:{},p=typeof o=="function"?o(t):o;s.className&&typeof s.className=="function"&&(s.className=s.className(t));let f={};if(t){let m=!1,g=[];for(let[v,x]of Object.entries(t))typeof x=="boolean"&&(m=!0),x===!0&&g.push(v);m&&(f["data-headlessui-state"]=g.join(" "))}if(i===y.Fragment&&Object.keys(Fo(s)).length>0){if(!y.isValidElement(p)||Array.isArray(p)&&p.length>1)throw new Error(['Passing props on "Fragment"!',"",`The current component <${r} /> is rendering a "Fragment".`,"However we need to passthrough the following props:",Object.keys(s).map(v=>` - ${v}`).join(` `),"","You can apply a few solutions:",['Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',"Render a single element as the child so that we can forward the props onto that element."].map(v=>` - ${v}`).join(` `)].join(` -`));let m=_d((l=p.props)==null?void 0:l.className,s.className),g=m?{className:m}:{};return y.cloneElement(p,Object.assign({},Ld(p.props,Fo(Ri(s,["ref"]))),f,a,uh(p.ref,a.ref),g))}return y.createElement(i,Object.assign({},Ri(s,["ref"]),i!==y.Fragment&&a,i!==y.Fragment&&f),p)}function uh(...e){return{ref:e.every(t=>t==null)?void 0:t=>{for(let n of e)n!=null&&(typeof n=="function"?n(t):n.current=t)}}}function Ld(...e){if(e.length===0)return{};if(e.length===1)return e[0];let t={},n={};for(let r of e)for(let l in r)l.startsWith("on")&&typeof r[l]=="function"?(n[l]!=null||(n[l]=[]),n[l].push(r[l])):t[l]=r[l];if(t.disabled||t["aria-disabled"])return Object.assign(t,Object.fromEntries(Object.keys(n).map(r=>[r,void 0])));for(let r in n)Object.assign(t,{[r](l,...i){let o=n[r];for(let u of o){if((l instanceof Event||l?.nativeEvent instanceof Event)&&l.defaultPrevented)return;u(l,...i)}}});return t}function vt(e){var t;return Object.assign(y.forwardRef(e),{displayName:(t=e.displayName)!=null?t:e.name})}function Fo(e){let t=Object.assign({},e);for(let n in t)t[n]===void 0&&delete t[n];return t}function Ri(e,t=[]){let n=Object.assign({},e);for(let r of t)r in n&&delete n[r];return n}function sh(e){let t=e.parentElement,n=null;for(;t&&!(t instanceof HTMLFieldSetElement);)t instanceof HTMLLegendElement&&(n=t),t=t.parentElement;let r=t?.getAttribute("disabled")==="";return r&&ah(n)?!1:r}function ah(e){if(!e)return!1;let t=e.previousElementSibling;for(;t!==null;){if(t instanceof HTMLLegendElement)return!1;t=t.previousElementSibling}return!0}function Td(e={},t=null,n=[]){for(let[r,l]of Object.entries(e))zd(n,Rd(t,r),l);return n}function Rd(e,t){return e?e+"["+t+"]":t}function zd(e,t,n){if(Array.isArray(n))for(let[r,l]of n.entries())zd(e,Rd(t,r.toString()),l);else n instanceof Date?e.push([t,n.toISOString()]):typeof n=="boolean"?e.push([t,n?"1":"0"]):typeof n=="string"?e.push([t,n]):typeof n=="number"?e.push([t,`${n}`]):n==null?e.push([t,""]):Td(n,t,e)}let ch="div";var $d=(e=>(e[e.None=1]="None",e[e.Focusable=2]="Focusable",e[e.Hidden=4]="Hidden",e))($d||{});let dh=vt(function(e,t){let{features:n=1,...r}=e,l={ref:t,"aria-hidden":(n&2)===2?!0:void 0,style:{position:"fixed",top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0",...(n&4)===4&&(n&2)!==2&&{display:"none"}}};return jt({ourProps:l,theirProps:r,slot:{},defaultTag:ch,name:"Hidden"})}),Fu=y.createContext(null);Fu.displayName="OpenClosedContext";var _e=(e=>(e[e.Open=1]="Open",e[e.Closed=2]="Closed",e[e.Closing=4]="Closing",e[e.Opening=8]="Opening",e))(_e||{});function Du(){return y.useContext(Fu)}function Id({value:e,children:t}){return ue.createElement(Fu.Provider,{value:e},t)}var ie=(e=>(e.Space=" ",e.Enter="Enter",e.Escape="Escape",e.Backspace="Backspace",e.Delete="Delete",e.ArrowLeft="ArrowLeft",e.ArrowUp="ArrowUp",e.ArrowRight="ArrowRight",e.ArrowDown="ArrowDown",e.Home="Home",e.End="End",e.PageUp="PageUp",e.PageDown="PageDown",e.Tab="Tab",e))(ie||{});function fh(e,t,n){let[r,l]=y.useState(n),i=e!==void 0,o=y.useRef(i),u=y.useRef(!1),s=y.useRef(!1);return i&&!o.current&&!u.current?(u.current=!0,o.current=i,console.error("A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.")):!i&&o.current&&!s.current&&(s.current=!0,o.current=i,console.error("A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.")),[i?e:r,A(a=>(i||l(a),t?.(a)))]}function ia(e){return[e.screenX,e.screenY]}function ph(){let e=y.useRef([-1,-1]);return{wasMoved(t){let n=ia(t);return e.current[0]===n[0]&&e.current[1]===n[1]?!1:(e.current=n,!0)},update(t){e.current=ia(t)}}}function Fd(){let e=y.useRef(!1);return be(()=>(e.current=!0,()=>{e.current=!1}),[]),e}var hh=(e=>(e[e.Open=0]="Open",e[e.Closed=1]="Closed",e))(hh||{}),mh=(e=>(e[e.Single=0]="Single",e[e.Multi=1]="Multi",e))(mh||{}),vh=(e=>(e[e.Pointer=0]="Pointer",e[e.Other=1]="Other",e))(vh||{}),gh=(e=>(e[e.OpenListbox=0]="OpenListbox",e[e.CloseListbox=1]="CloseListbox",e[e.GoToOption=2]="GoToOption",e[e.Search=3]="Search",e[e.ClearSearch=4]="ClearSearch",e[e.RegisterOption=5]="RegisterOption",e[e.UnregisterOption=6]="UnregisterOption",e[e.RegisterLabel=7]="RegisterLabel",e))(gh||{});function zi(e,t=n=>n){let n=e.activeOptionIndex!==null?e.options[e.activeOptionIndex]:null,r=th(t(e.options.slice()),i=>i.dataRef.current.domRef.current),l=n?r.indexOf(n):null;return l===-1&&(l=null),{options:r,activeOptionIndex:l}}let yh={[1](e){return e.dataRef.current.disabled||e.listboxState===1?e:{...e,activeOptionIndex:null,listboxState:1}},[0](e){if(e.dataRef.current.disabled||e.listboxState===0)return e;let t=e.activeOptionIndex,{isSelected:n}=e.dataRef.current,r=e.options.findIndex(l=>n(l.dataRef.current.value));return r!==-1&&(t=r),{...e,listboxState:0,activeOptionIndex:t}},[2](e,t){var n;if(e.dataRef.current.disabled||e.listboxState===1)return e;let r=zi(e),l=oh(t,{resolveItems:()=>r.options,resolveActiveIndex:()=>r.activeOptionIndex,resolveId:i=>i.id,resolveDisabled:i=>i.dataRef.current.disabled});return{...e,...r,searchQuery:"",activeOptionIndex:l,activationTrigger:(n=t.trigger)!=null?n:1}},[3]:(e,t)=>{if(e.dataRef.current.disabled||e.listboxState===1)return e;let n=e.searchQuery!==""?0:1,r=e.searchQuery+t.value.toLowerCase(),l=(e.activeOptionIndex!==null?e.options.slice(e.activeOptionIndex+n).concat(e.options.slice(0,e.activeOptionIndex+n)):e.options).find(o=>{var u;return!o.dataRef.current.disabled&&((u=o.dataRef.current.textValue)==null?void 0:u.startsWith(r))}),i=l?e.options.indexOf(l):-1;return i===-1||i===e.activeOptionIndex?{...e,searchQuery:r}:{...e,searchQuery:r,activeOptionIndex:i,activationTrigger:1}},[4](e){return e.dataRef.current.disabled||e.listboxState===1||e.searchQuery===""?e:{...e,searchQuery:""}},[5]:(e,t)=>{let n={id:t.id,dataRef:t.dataRef},r=zi(e,l=>[...l,n]);return e.activeOptionIndex===null&&e.dataRef.current.isSelected(t.dataRef.current.value)&&(r.activeOptionIndex=r.options.indexOf(n)),{...e,...r}},[6]:(e,t)=>{let n=zi(e,r=>{let l=r.findIndex(i=>i.id===t.id);return l!==-1&&r.splice(l,1),r});return{...e,...n,activationTrigger:1}},[7]:(e,t)=>({...e,labelId:t.id})},Mu=y.createContext(null);Mu.displayName="ListboxActionsContext";function _r(e){let t=y.useContext(Mu);if(t===null){let n=new Error(`<${e} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(n,_r),n}return t}let ju=y.createContext(null);ju.displayName="ListboxDataContext";function Lr(e){let t=y.useContext(ju);if(t===null){let n=new Error(`<${e} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(n,Lr),n}return t}function kh(e,t){return ce(t.type,yh,e,t)}let wh=y.Fragment,xh=vt(function(e,t){let{value:n,defaultValue:r,name:l,onChange:i,by:o=(D,j)=>D===j,disabled:u=!1,horizontal:s=!1,multiple:a=!1,...p}=e;const f=s?"horizontal":"vertical";let m=ln(t),[g=a?[]:void 0,v]=fh(n,i,r),[x,N]=y.useReducer(kh,{dataRef:y.createRef(),listboxState:1,options:[],searchQuery:"",labelId:null,activeOptionIndex:null,activationTrigger:1}),d=y.useRef({static:!1,hold:!1}),c=y.useRef(null),h=y.useRef(null),w=y.useRef(null),E=A(typeof o=="string"?(D,j)=>{let te=o;return D?.[te]===j?.[te]}:o),P=y.useCallback(D=>ce(C.mode,{[1]:()=>g.some(j=>E(j,D)),[0]:()=>E(g,D)}),[g]),C=y.useMemo(()=>({...x,value:g,disabled:u,mode:a?1:0,orientation:f,compare:E,isSelected:P,optionsPropsRef:d,labelRef:c,buttonRef:h,optionsRef:w}),[g,u,a,x]);be(()=>{x.dataRef.current=C},[C]),nh([C.buttonRef,C.optionsRef],(D,j)=>{var te;N({type:1}),Od(j,Iu.Loose)||(D.preventDefault(),(te=C.buttonRef.current)==null||te.focus())},C.listboxState===0);let T=y.useMemo(()=>({open:C.listboxState===0,disabled:u,value:g}),[C,u,g]),b=A(D=>{let j=C.options.find(te=>te.id===D);!j||Be(j.dataRef.current.value)}),$=A(()=>{if(C.activeOptionIndex!==null){let{dataRef:D,id:j}=C.options[C.activeOptionIndex];Be(D.current.value),N({type:2,focus:Ee.Specific,id:j})}}),le=A(()=>N({type:0})),Ve=A(()=>N({type:1})),He=A((D,j,te)=>D===Ee.Specific?N({type:2,focus:Ee.Specific,id:j,trigger:te}):N({type:2,focus:D,trigger:te})),on=A((D,j)=>(N({type:5,id:D,dataRef:j}),()=>N({type:6,id:D}))),rt=A(D=>(N({type:7,id:D}),()=>N({type:7,id:null}))),Be=A(D=>ce(C.mode,{[0](){return v?.(D)},[1](){let j=C.value.slice(),te=j.findIndex(Ie=>E(Ie,D));return te===-1?j.push(D):j.splice(te,1),v?.(j)}})),At=A(D=>N({type:3,value:D})),O=A(()=>N({type:4})),R=y.useMemo(()=>({onChange:Be,registerOption:on,registerLabel:rt,goToOption:He,closeListbox:Ve,openListbox:le,selectActiveOption:$,selectOption:b,search:At,clearSearch:O}),[]),z={ref:m},F=y.useRef(null),J=In();return y.useEffect(()=>{!F.current||r!==void 0&&J.addEventListener(F.current,"reset",()=>{Be(r)})},[F,Be]),ue.createElement(Mu.Provider,{value:R},ue.createElement(ju.Provider,{value:C},ue.createElement(Id,{value:ce(C.listboxState,{[0]:_e.Open,[1]:_e.Closed})},l!=null&&g!=null&&Td({[l]:g}).map(([D,j],te)=>ue.createElement(dh,{features:$d.Hidden,ref:te===0?Ie=>{var Ut;F.current=(Ut=Ie?.closest("form"))!=null?Ut:null}:void 0,...Fo({key:D,as:"input",type:"hidden",hidden:!0,readOnly:!0,name:D,value:j})})),jt({ourProps:z,theirProps:p,slot:T,defaultTag:wh,name:"Listbox"}))))}),Sh="button",Eh=vt(function(e,t){var n;let r=Xl(),{id:l=`headlessui-listbox-button-${r}`,...i}=e,o=Lr("Listbox.Button"),u=_r("Listbox.Button"),s=ln(o.buttonRef,t),a=In(),p=A(N=>{switch(N.key){case ie.Space:case ie.Enter:case ie.ArrowDown:N.preventDefault(),u.openListbox(),a.nextFrame(()=>{o.value||u.goToOption(Ee.First)});break;case ie.ArrowUp:N.preventDefault(),u.openListbox(),a.nextFrame(()=>{o.value||u.goToOption(Ee.Last)});break}}),f=A(N=>{switch(N.key){case ie.Space:N.preventDefault();break}}),m=A(N=>{if(sh(N.currentTarget))return N.preventDefault();o.listboxState===0?(u.closeListbox(),a.nextFrame(()=>{var d;return(d=o.buttonRef.current)==null?void 0:d.focus({preventScroll:!0})})):(N.preventDefault(),u.openListbox())}),g=Nd(()=>{if(o.labelId)return[o.labelId,l].join(" ")},[o.labelId,l]),v=y.useMemo(()=>({open:o.listboxState===0,disabled:o.disabled,value:o.value}),[o]),x={ref:s,id:l,type:rh(e,o.buttonRef),"aria-haspopup":"listbox","aria-controls":(n=o.optionsRef.current)==null?void 0:n.id,"aria-expanded":o.disabled?void 0:o.listboxState===0,"aria-labelledby":g,disabled:o.disabled,onKeyDown:p,onKeyUp:f,onClick:m};return jt({ourProps:x,theirProps:i,slot:v,defaultTag:Sh,name:"Listbox.Button"})}),Ch="label",Nh=vt(function(e,t){let n=Xl(),{id:r=`headlessui-listbox-label-${n}`,...l}=e,i=Lr("Listbox.Label"),o=_r("Listbox.Label"),u=ln(i.labelRef,t);be(()=>o.registerLabel(r),[r]);let s=A(()=>{var p;return(p=i.buttonRef.current)==null?void 0:p.focus({preventScroll:!0})}),a=y.useMemo(()=>({open:i.listboxState===0,disabled:i.disabled}),[i]);return jt({ourProps:{ref:u,id:r,onClick:s},theirProps:l,slot:a,defaultTag:Ch,name:"Listbox.Label"})}),Ph="ul",Oh=$l.RenderStrategy|$l.Static,_h=vt(function(e,t){var n;let r=Xl(),{id:l=`headlessui-listbox-options-${r}`,...i}=e,o=Lr("Listbox.Options"),u=_r("Listbox.Options"),s=ln(o.optionsRef,t),a=In(),p=In(),f=Du(),m=(()=>f!==null?(f&_e.Open)===_e.Open:o.listboxState===0)();y.useEffect(()=>{var d;let c=o.optionsRef.current;!c||o.listboxState===0&&c!==((d=Pd(c))==null?void 0:d.activeElement)&&c.focus({preventScroll:!0})},[o.listboxState,o.optionsRef]);let g=A(d=>{switch(p.dispose(),d.key){case ie.Space:if(o.searchQuery!=="")return d.preventDefault(),d.stopPropagation(),u.search(d.key);case ie.Enter:if(d.preventDefault(),d.stopPropagation(),o.activeOptionIndex!==null){let{dataRef:c}=o.options[o.activeOptionIndex];u.onChange(c.current.value)}o.mode===0&&(u.closeListbox(),tn().nextFrame(()=>{var c;return(c=o.buttonRef.current)==null?void 0:c.focus({preventScroll:!0})}));break;case ce(o.orientation,{vertical:ie.ArrowDown,horizontal:ie.ArrowRight}):return d.preventDefault(),d.stopPropagation(),u.goToOption(Ee.Next);case ce(o.orientation,{vertical:ie.ArrowUp,horizontal:ie.ArrowLeft}):return d.preventDefault(),d.stopPropagation(),u.goToOption(Ee.Previous);case ie.Home:case ie.PageUp:return d.preventDefault(),d.stopPropagation(),u.goToOption(Ee.First);case ie.End:case ie.PageDown:return d.preventDefault(),d.stopPropagation(),u.goToOption(Ee.Last);case ie.Escape:return d.preventDefault(),d.stopPropagation(),u.closeListbox(),a.nextFrame(()=>{var c;return(c=o.buttonRef.current)==null?void 0:c.focus({preventScroll:!0})});case ie.Tab:d.preventDefault(),d.stopPropagation();break;default:d.key.length===1&&(u.search(d.key),p.setTimeout(()=>u.clearSearch(),350));break}}),v=Nd(()=>{var d,c,h;return(h=(d=o.labelRef.current)==null?void 0:d.id)!=null?h:(c=o.buttonRef.current)==null?void 0:c.id},[o.labelRef.current,o.buttonRef.current]),x=y.useMemo(()=>({open:o.listboxState===0}),[o]),N={"aria-activedescendant":o.activeOptionIndex===null||(n=o.options[o.activeOptionIndex])==null?void 0:n.id,"aria-multiselectable":o.mode===1?!0:void 0,"aria-labelledby":v,"aria-orientation":o.orientation,id:l,onKeyDown:g,role:"listbox",tabIndex:0,ref:s};return jt({ourProps:N,theirProps:i,slot:x,defaultTag:Ph,features:Oh,visible:m,name:"Listbox.Options"})}),Lh="li",Th=vt(function(e,t){let n=Xl(),{id:r=`headlessui-listbox-option-${n}`,disabled:l=!1,value:i,...o}=e,u=Lr("Listbox.Option"),s=_r("Listbox.Option"),a=u.activeOptionIndex!==null?u.options[u.activeOptionIndex].id===r:!1,p=u.isSelected(i),f=y.useRef(null),m=ct({disabled:l,value:i,domRef:f,get textValue(){var E,P;return(P=(E=f.current)==null?void 0:E.textContent)==null?void 0:P.toLowerCase()}}),g=ln(t,f);be(()=>{if(u.listboxState!==0||!a||u.activationTrigger===0)return;let E=tn();return E.requestAnimationFrame(()=>{var P,C;(C=(P=f.current)==null?void 0:P.scrollIntoView)==null||C.call(P,{block:"nearest"})}),E.dispose},[f,a,u.listboxState,u.activationTrigger,u.activeOptionIndex]),be(()=>s.registerOption(r,m),[m,r]);let v=A(E=>{if(l)return E.preventDefault();s.onChange(i),u.mode===0&&(s.closeListbox(),tn().nextFrame(()=>{var P;return(P=u.buttonRef.current)==null?void 0:P.focus({preventScroll:!0})}))}),x=A(()=>{if(l)return s.goToOption(Ee.Nothing);s.goToOption(Ee.Specific,r)}),N=ph(),d=A(E=>N.update(E)),c=A(E=>{!N.wasMoved(E)||l||a||s.goToOption(Ee.Specific,r,0)}),h=A(E=>{!N.wasMoved(E)||l||!a||s.goToOption(Ee.Nothing)}),w=y.useMemo(()=>({active:a,selected:p,disabled:l}),[a,p,l]);return jt({ourProps:{id:r,ref:g,role:"option",tabIndex:l===!0?void 0:-1,"aria-disabled":l===!0?!0:void 0,"aria-selected":p,disabled:void 0,onClick:v,onFocus:x,onPointerEnter:d,onMouseEnter:d,onPointerMove:c,onMouseMove:c,onPointerLeave:h,onMouseLeave:h},theirProps:o,slot:w,defaultTag:Lh,name:"Listbox.Option"})}),Yr=Object.assign(xh,{Button:Eh,Label:Nh,Options:_h,Option:Th});function Rh(e=0){let[t,n]=y.useState(e),r=y.useCallback(u=>n(s=>s|u),[t]),l=y.useCallback(u=>Boolean(t&u),[t]),i=y.useCallback(u=>n(s=>s&~u),[n]),o=y.useCallback(u=>n(s=>s^u),[n]);return{flags:t,addFlag:r,hasFlag:l,removeFlag:i,toggleFlag:o}}function zh(e){let t={called:!1};return(...n)=>{if(!t.called)return t.called=!0,e(...n)}}function $i(e,...t){e&&t.length>0&&e.classList.add(...t)}function Ii(e,...t){e&&t.length>0&&e.classList.remove(...t)}function $h(e,t){let n=tn();if(!e)return n.dispose;let{transitionDuration:r,transitionDelay:l}=getComputedStyle(e),[i,o]=[r,l].map(u=>{let[s=0]=u.split(",").filter(Boolean).map(a=>a.includes("ms")?parseFloat(a):parseFloat(a)*1e3).sort((a,p)=>p-a);return s});if(i+o!==0){let u=n.addEventListener(e,"transitionend",s=>{s.target===s.currentTarget&&(t(),u())})}else t();return n.add(()=>t()),n.dispose}function Ih(e,t,n,r){let l=n?"enter":"leave",i=tn(),o=r!==void 0?zh(r):()=>{};l==="enter"&&(e.removeAttribute("hidden"),e.style.display="");let u=ce(l,{enter:()=>t.enter,leave:()=>t.leave}),s=ce(l,{enter:()=>t.enterTo,leave:()=>t.leaveTo}),a=ce(l,{enter:()=>t.enterFrom,leave:()=>t.leaveFrom});return Ii(e,...t.enter,...t.enterTo,...t.enterFrom,...t.leave,...t.leaveFrom,...t.leaveTo,...t.entered),$i(e,...u,...a),i.nextFrame(()=>{Ii(e,...a),$i(e,...s),$h(e,()=>(Ii(e,...u),$i(e,...t.entered),o()))}),i.dispose}function Fh({container:e,direction:t,classes:n,onStart:r,onStop:l}){let i=Fd(),o=In(),u=ct(t);be(()=>{let s=tn();o.add(s.dispose);let a=e.current;if(a&&u.current!=="idle"&&i.current)return s.dispose(),r.current(u.current),s.add(Ih(a,n.current,u.current==="enter",()=>{s.dispose(),l.current(u.current)})),s.dispose},[t])}function Vt(e=""){return e.split(" ").filter(t=>t.trim().length>1)}let Zl=y.createContext(null);Zl.displayName="TransitionContext";var Dh=(e=>(e.Visible="visible",e.Hidden="hidden",e))(Dh||{});function Mh(){let e=y.useContext(Zl);if(e===null)throw new Error("A is used but it is missing a parent or .");return e}function jh(){let e=y.useContext(Jl);if(e===null)throw new Error("A is used but it is missing a parent or .");return e}let Jl=y.createContext(null);Jl.displayName="NestingContext";function ql(e){return"children"in e?ql(e.children):e.current.filter(({el:t})=>t.current!==null).filter(({state:t})=>t==="visible").length>0}function Dd(e,t){let n=ct(e),r=y.useRef([]),l=Fd(),i=In(),o=A((g,v=st.Hidden)=>{let x=r.current.findIndex(({el:N})=>N===g);x!==-1&&(ce(v,{[st.Unmount](){r.current.splice(x,1)},[st.Hidden](){r.current[x].state="hidden"}}),i.microTask(()=>{var N;!ql(r)&&l.current&&((N=n.current)==null||N.call(n))}))}),u=A(g=>{let v=r.current.find(({el:x})=>x===g);return v?v.state!=="visible"&&(v.state="visible"):r.current.push({el:g,state:"visible"}),()=>o(g,st.Unmount)}),s=y.useRef([]),a=y.useRef(Promise.resolve()),p=y.useRef({enter:[],leave:[],idle:[]}),f=A((g,v,x)=>{s.current.splice(0),t&&(t.chains.current[v]=t.chains.current[v].filter(([N])=>N!==g)),t?.chains.current[v].push([g,new Promise(N=>{s.current.push(N)})]),t?.chains.current[v].push([g,new Promise(N=>{Promise.all(p.current[v].map(([d,c])=>c)).then(()=>N())})]),v==="enter"?a.current=a.current.then(()=>t?.wait.current).then(()=>x(v)):x(v)}),m=A((g,v,x)=>{Promise.all(p.current[v].splice(0).map(([N,d])=>d)).then(()=>{var N;(N=s.current.shift())==null||N()}).then(()=>x(v))});return y.useMemo(()=>({children:r,register:u,unregister:o,onStart:f,onStop:m,wait:a,chains:p}),[u,o,r,f,m,p,a])}function Ah(){}let Uh=["beforeEnter","afterEnter","beforeLeave","afterLeave"];function oa(e){var t;let n={};for(let r of Uh)n[r]=(t=e[r])!=null?t:Ah;return n}function bh(e){let t=y.useRef(oa(e));return y.useEffect(()=>{t.current=oa(e)},[e]),t}let Vh="div",Md=$l.RenderStrategy,jd=vt(function(e,t){let{beforeEnter:n,afterEnter:r,beforeLeave:l,afterLeave:i,enter:o,enterFrom:u,enterTo:s,entered:a,leave:p,leaveFrom:f,leaveTo:m,...g}=e,v=y.useRef(null),x=ln(v,t),N=g.unmount?st.Unmount:st.Hidden,{show:d,appear:c,initial:h}=Mh(),[w,E]=y.useState(d?"visible":"hidden"),P=jh(),{register:C,unregister:T}=P,b=y.useRef(null);y.useEffect(()=>C(v),[C,v]),y.useEffect(()=>{if(N===st.Hidden&&v.current){if(d&&w!=="visible"){E("visible");return}return ce(w,{hidden:()=>T(v),visible:()=>C(v)})}},[w,v,C,T,d,N]);let $=ct({enter:Vt(o),enterFrom:Vt(u),enterTo:Vt(s),entered:Vt(a),leave:Vt(p),leaveFrom:Vt(f),leaveTo:Vt(m)}),le=bh({beforeEnter:n,afterEnter:r,beforeLeave:l,afterLeave:i}),Ve=$u();y.useEffect(()=>{if(Ve&&w==="visible"&&v.current===null)throw new Error("Did you forget to passthrough the `ref` to the actual DOM node?")},[v,w,Ve]);let He=h&&!c,on=(()=>!Ve||He||b.current===d?"idle":d?"enter":"leave")(),rt=Rh(0),Be=A(F=>ce(F,{enter:()=>{rt.addFlag(_e.Opening),le.current.beforeEnter()},leave:()=>{rt.addFlag(_e.Closing),le.current.beforeLeave()},idle:()=>{}})),At=A(F=>ce(F,{enter:()=>{rt.removeFlag(_e.Opening),le.current.afterEnter()},leave:()=>{rt.removeFlag(_e.Closing),le.current.afterLeave()},idle:()=>{}})),O=Dd(()=>{E("hidden"),T(v)},P);Fh({container:v,classes:$,direction:on,onStart:ct(F=>{O.onStart(v,F,Be)}),onStop:ct(F=>{O.onStop(v,F,At),F==="leave"&&!ql(O)&&(E("hidden"),T(v))})}),y.useEffect(()=>{!He||(N===st.Hidden?b.current=null:b.current=d)},[d,He,w]);let R=g,z={ref:x};return c&&d&&zt.isServer&&(R={...R,className:_d(g.className,...$.current.enter,...$.current.enterFrom)}),ue.createElement(Jl.Provider,{value:O},ue.createElement(Id,{value:ce(w,{visible:_e.Open,hidden:_e.Closed})|rt.flags},jt({ourProps:z,theirProps:R,defaultTag:Vh,features:Md,visible:w==="visible",name:"Transition.Child"})))}),Do=vt(function(e,t){let{show:n,appear:r=!1,unmount:l,...i}=e,o=y.useRef(null),u=ln(o,t);$u();let s=Du();if(n===void 0&&s!==null&&(n=(s&_e.Open)===_e.Open),![!0,!1].includes(n))throw new Error("A is used but it is missing a `show={true | false}` prop.");let[a,p]=y.useState(n?"visible":"hidden"),f=Dd(()=>{p("hidden")}),[m,g]=y.useState(!0),v=y.useRef([n]);be(()=>{m!==!1&&v.current[v.current.length-1]!==n&&(v.current.push(n),g(!1))},[v,n]);let x=y.useMemo(()=>({show:n,appear:r,initial:m}),[n,r,m]);y.useEffect(()=>{if(n)p("visible");else if(!ql(f))p("hidden");else{let d=o.current;if(!d)return;let c=d.getBoundingClientRect();c.x===0&&c.y===0&&c.width===0&&c.height===0&&p("hidden")}},[n,f]);let N={unmount:l};return ue.createElement(Jl.Provider,{value:f},ue.createElement(Zl.Provider,{value:x},jt({ourProps:{...N,as:y.Fragment,children:ue.createElement(jd,{ref:u,...N,...i})},theirProps:{},defaultTag:y.Fragment,features:Md,visible:a==="visible",name:"Transition"})))}),Hh=vt(function(e,t){let n=y.useContext(Zl)!==null,r=Du()!==null;return ue.createElement(ue.Fragment,null,!n&&r?ue.createElement(Do,{ref:t,...e}):ue.createElement(jd,{ref:t,...e}))}),Bh=Object.assign(Do,{Child:Hh,Root:Do});function Wh({options:e,selectedOption:t,setSelected:n,label:r,size:l="large",testId:i,disabled:o}){let u="";switch(l){case"small":u="py-1 bg-white dark:bg-slate-900 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-500 dark:text-slate-400 dark:ring-offset-slate-900";break;case"medium":u="py-2 bg-white text-slate-400 hover:bg-slate-100";break;case"large":u="py-3 bg-violet-800 text-white hover:bg-violet-900";break}return k(Yr,{value:t,onChange:n,children:({open:s})=>L("div",{"data-test":i,className:W("relative",o&&"opacity-60 cursor-not-allowed"),children:[r&&k(Cd,{className:"w-full inline-block pb-px",children:r}),L("div",{className:"relative",children:[k("div",{className:"rounded-lg w-full",children:L("div",{className:"relative z-0 inline-flex rounded-lg w-full border dark:border-slate-700",children:[k("div",{className:"relative flex flex-grow items-center bg-white dark:bg-slate-900 pl-3 pr-4 border border-transparent rounded-l-lg text-slate-700 dark:text-slate-300",children:k("p",{className:"ml-2.5 font-medium",children:(e.find(a=>a.value===t)??e[0])?.display??"make a selection..."})}),L(Yr.Button,{className:W("relative inline-flex items-center px-4 rounded-l-none rounded-r-lg text-sm font-medium focus:outline-none focus:z-10 focus:ring-2 focus:ring-offset-2 focus:ring-offset-slate-50 focus:ring-indigo-500 transition-[outline,background-color] duration-100",u,o&&"cursor-not-allowed pointer-events-none"),children:[k("span",{className:"sr-only",children:"Change published status"}),k("i",{className:`fa fa-chevron-down text-xl text-opacity-90 transition-transform duration-100 ${s?"-rotate-180":"rotate-0"}`,"aria-hidden":"true"})]})]})}),k(Bh,{show:s,as:y.Fragment,leave:"transition-[opacity,transform] ease-in duration-100",leaveFrom:"opacity-100",leaveTo:"opacity-0 -translate-y-1",enter:"transition-[opacity,transform] ease-in duration-100",enterFrom:"opacity-0 -translate-y-1",enterTo:"opacity-100",children:k(Yr.Options,{className:"origin-top-right absolute z-20 right-0 mt-2 p-2 w-72 rounded-xl shadow-lg overflow-hidden bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 focus:outline-none flex flex-col",children:e.map(({value:a,display:p})=>k(Yr.Option,{className:({active:f})=>W(f?"bg-violet-100 text-slate-900 dark:text-slate-200":"text-slate-900 dark:text-slate-300","cursor-pointer select-none relative rounded-lg transition-[background-color] duration-75",l==="small"?"py-2 px-3":"p-3.5 text-md",l==="small"&&f&&"bg-slate-50 dark:bg-slate-700/50"),value:a,children:({selected:f})=>k("div",{className:"flex flex-col",children:L("div",{className:"flex justify-between",children:[k("p",{className:f?"font-semibold":"font-normal",children:p}),f?k("span",{className:"text-violet-700",children:k("i",{className:"fa fa-check h-5 w-5","aria-hidden":"true"})}):null]})})},a))})})]})]})})}const Qh=({type:e,className:t,children:n,size:r})=>{let l="";switch(e){case"green":case"ok":l="bg-green-50 dark:bg-green-500/10 border-green-200 dark:border-green-500/50 text-green-600 dark:text-green-300";break;case"red":case"error":l="bg-red-50 dark:bg-red-500/10 border-red-200 dark:border-red-500/50 text-red-600 dark:text-red-300";break;case"yellow":case"warning":l="bg-yellow-50 dark:bg-yellow-500/10 border-yellow-200 dark:border-yellow-500/50 text-yellow-600 dark:text-yellow-300";break;case"blue":case"info":l="bg-blue-50 dark:bg-blue-500/10 border-blue-200 dark:border-blue-500/50 text-blue-600 dark:text-blue-300";break}return k("div",{className:W("max-w-fit border rounded-full flex justify-center items-center",{"text-xs px-[12px] py-[2px]":r==="small","text-base px-6 py-0.5":r==="large","text-sm px-[14px] py-[2.5px]":r==="medium"||!r},l,t),children:n})},Kh=({short:e,onRecheck:t,onDisconnect:n})=>{const[r,l]=y.useState(!1),[i,o]=y.useState(!1);return k("div",{className:W("min-h-screen flex flex-col justify-center items-center bg-white dark:bg-slate-900",e?"p-4":"p-8"),children:L("div",{className:W("border border-red-100 dark:border-red-500/40 rounded-2xl h-full flex flex-col bg-red-50/50 dark:bg-red-500/20",e?"p-6":"p-8"),children:[L("div",{className:"flex-grow flex flex-col space-y-4 text-slate-900 dark:text-slate-100",children:[L("h1",{className:"text-2xl font-medium",children:["Your Gertrude account is ",k("strong",{children:"no longer active."})]}),!e&&k("p",{children:"The internet filter will continue protecting this computer according to the rules set before the account went inactive, but no changes or suspensions can be made until the account is restored."}),L("p",{children:["To ",k("strong",{children:"restore the account,"})," login to the Gertrude parent site and resolve the payment issue, then click the ",k("strong",{children:"Recheck"})," ","button below."]}),!e&&L("p",{children:["If you no longer wish to use Gertrude, click the ",k("strong",{children:"Disconnect"})," ","button below, then uninstall the app."]}),L("p",{children:["Contact us at at"," ",k("a",{href:"https://gertrude.app/contact",className:"font-semibold text-slate-800 dark:text-slate-200 border-b-2 pb-1 border-transparent dark:border-transparent hover:border-slate-600 dark:hover:border-slate-100 hover:pb-0.5 duration-200 transition-[padding-bottom,border-color]",children:"https://gertrude.app/contact"})," ","to get help."]})]}),L("div",{className:"flex items-center mt-6 space-x-4 bg-white dark:bg-slate-900 p-4 rounded-xl self-stretch justify-between border border-red-100 dark:border-red-500/50",children:[L(q,{type:"button",onClick:()=>{l(!0),setTimeout(()=>l(!1),3e3),t()},color:"secondary",disabled:r,children:[k("i",{className:W("fa-solid fa-sync mr-3",r&&"animate-spin")}),r?"Rechecking...":"Recheck"]}),L(q,{type:"button",onClick:()=>{o(!0),setTimeout(()=>o(!1),6e3),n()},color:"warning",disabled:i,children:[k("i",{className:W("fa-solid mr-3",i?"fa-sync animate-spin":"fa-plug")}),i?"Disconnecting...":"Disconnect"]})]})]})})},Gh=({screen:e,setScreen:t})=>{const[n,r]=y.useState(0);return L("nav",{className:W("border-slate-200 dark:border-slate-800 border-r p-2 font-bold flex flex-col items-stretch space-y-1 bg-white dark:bg-slate-900 fixed h-full z-20 top-0"),children:[k(Xr,{isActive:e==="healthCheck",onClick:()=>t("healthCheck"),icon:"heart-pulse"}),k(Xr,{isActive:e==="actions",onClick:()=>t("actions"),icon:"arrow-pointer"}),k(Xr,{isActive:e==="exemptUsers",onClick:()=>t("exemptUsers"),icon:"users"}),k(Xr,{isActive:e==="advanced",onClick:()=>{n<10?r(n+1):t("advanced")},icon:"flask",className:n<10?"cursor-default opacity-0":""})]})},Xr=({onClick:e,isActive:t,className:n,icon:r})=>k("button",{onClick:e,className:W("transition-colors duration-100 w-12 h-12 flex justify-center items-center rounded-lg",t?"bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400":"text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-600 dark:hover:text-slate-400",n),children:k("i",{className:`fa-solid fa-${r} text-2xl`})}),Yh=({filterRunning:e,installedAppVersion:t,availableAppUpdate:n,dangerZoneModal:r,userName:l,emit:i,quitting:o,releaseChannel:u,dispatch:s})=>{const a=Xh(n);return L("div",{className:"p-4 h-full flex flex-col justify-between relative",children:[k("div",{className:W("w-full h-full left-0 top-0 bg-slate-100 dark:bg-slate-900 z-10 transition-[backdrop-filter,background-color] duration-300 flex justify-center items-center fixed",r!=="hidden"?"bg-opacity-30 dark:bg-opacity-90 dark:backdrop-blur-sm backdrop-blur-md pointer-events-auto":"bg-opacity-0 dark:bg-opacity-0 backdrop-blur-none pointer-events-none"),onClick:()=>s({type:"dangerZoneModalDismissed"}),children:L("div",{className:W("bg-white dark:bg-slate-800 shadow-lg shadow-slate-300/50 dark:shadow-black/20 rounded-2xl transition-[transform,opacity] duration-300",r!=="hidden"?"pointer-events-auto":"pointer-events-none scale-75 opacity-0"),onClick:p=>p.stopPropagation(),children:[L("div",{className:"p-8",children:[L("h3",{className:"font-bold text-xl text-slate-900 dark:text-slate-200 ",children:["Are you sure you want to",r==="quitApp"?" quit Gertrude?":" stop the filter?"]}),k("p",{className:"max-w-md text-sm text-slate-500 dark:text-slate-400 mt-4",children:r==="quitApp"?"Quitting the app stops all screenshot and keystroke monitoring. This is usually only necessary when uninstalling or troubleshooting.":'Stopping the filter gives all users on this computer unrestricted internet access. If you want to temporarily suspend the filter, use the "Suspend filter" button in the main menubar dropdown instead.'})]}),L("div",{className:"p-4 bg-slate-50 dark:bg-slate-900/50 rounded-b-2xl flex justify-between space-x-4",children:[k(q,{type:"button",onClick:()=>s({type:"dangerZoneModalDismissed"}),color:"tertiary",className:"flex-grow",children:"Cancel"}),k(q,{type:"button",onClick:()=>i({case:r==="quitApp"?"confirmQuitAppClicked":"confirmStopFilterClicked"}),color:"warning",className:"flex-grow",disabled:r==="quitApp"&&o,children:o?"Quitting...":`I understand, ${r==="quitApp"?"quit the app":"stop the filter"}`})]})]})}),L("div",{className:"flex flex-col flex-grow",children:[L("div",{className:"border border-slate-200 dark:border-slate-800 rounded-2xl relative flex flex-col justify-between mb-3.5",children:[k(Qh,{type:a.badgeColor,className:"absolute right-2 top-2 w-36 !max-w-none",children:a.badgeText}),L("div",{className:"p-4 pt-3",children:[L("h2",{className:"text-lg font-semibold text-slate-600 dark:text-slate-300",children:["Currently running Gertrude version"," ",k("span",{className:"font-bold *font-mono text-violet-700 dark:text-violet-400",children:t})]}),k("p",{className:"text-slate-500 mt-2",children:a.versionMessage})]}),L("div",{className:"p-3 bg-slate-50 dark:bg-slate-800/50 rounded-b-2xl flex justify-end items-center",children:[L("p",{className:W("text-slate-500 opacity-80 italic ml-3 flex-grow",u==="stable"&&"hidden"),children:[k("i",{className:"fas fa-flask mr-2"}),"Release channel: ",k("b",{children:u})]}),L(q,{type:"button",size:"small",onClick:()=>i({case:"updateAppNowClicked"}),color:"tertiary",disabled:!n,children:[k("i",{className:"fas fa-sync-alt mr-2"}),"Update now"]})]})]}),l!==void 0&&L("div",{className:"border border-slate-200 dark:border-slate-800 rounded-2xl relative flex flex-col justify-between",children:[L("div",{className:"p-4 pt-3",children:[L("h2",{className:"text-lg font-semibold text-slate-600 dark:text-slate-300",children:["Connected to child:"," ",k("span",{className:"font-bold *font-mono text-violet-700 dark:text-violet-400",children:l})]}),k("p",{className:"text-slate-500 mt-2",children:"Disconnect if you want to connect a different child."})]}),k("div",{className:"p-3 bg-slate-50 dark:bg-slate-800/50 rounded-b-2xl flex justify-end",children:L(q,{type:"button",size:"small",onClick:()=>i({case:"disconnectUserClicked"}),color:"tertiary",children:[k("i",{className:"fa fa-scissors mr-2"}),"Disconnect"]})})]})]}),L("div",{className:"border border-red-200/70 dark:border-red-500/20 rounded-2xl bg-red-50/20 dark:bg-red-500/5",children:[L("div",{className:"p-4 pt-3",children:[k("h2",{className:"text-xl font-bold text-red-700 dark:text-red-400",children:"Danger zone"}),k("p",{className:"mt-2 text-red-700/70 dark:text-red-400/50",children:"These actions disable Gertrude‘s protections. Use only for troubleshooting or uninstalling."})]}),L("div",{className:"flex justify-end space-x-4 bg-red-50/30 dark:bg-red-500/5 rounded-b-2xl p-3",children:[e&&k(q,{type:"button",size:"small",onClick:()=>s({type:"dangerZoneStopFilterClicked"}),color:"warning",children:"Stop filter"}),k(q,{type:"button",size:"small",onClick:()=>s({type:"dangerZoneQuitAppClicked"}),color:"warning",children:"Quit app"})]})]})]})};function Xh(e){return e?e.required?{badgeColor:"red",badgeText:"Update required",versionMessage:L(ma,{children:["Update to required version ",k("b",{children:e.semver})," as soon as possible."]})}:{badgeColor:"yellow",badgeText:"Update available",versionMessage:`Gertrude version ${e.semver} is available for download.`}:{badgeColor:"green",badgeText:"Up to date",versionMessage:"We'll let you know when the next update is available."}}const Zh=e=>{let t,n;switch(e.state){case"ok":t="bg-green-500",n="fa-solid fa-check translate-x-[0.5px]";break;case"fail":t="bg-red-500",n="fa-solid fa-times translate-x-[0.5px]";break;case"warn":t="bg-yellow-400",n="fa-solid fa-minus";break;case"checking":t="bg-purple-500 dark:bg-purple-800",n="fa-solid fa-sync animate-spin dark:text-slate-100";break;case"unexpected":t="bg-gray-500/90",n="fa-solid fa-exclamation translate-x-[0.5px]";break}const r=Jh(e),l=qh(e);return L("div",{className:"flex items-center p-2 rounded-xl bg-slate-50 dark:bg-slate-800/30",children:[k("div",{className:W("w-6 h-6 rounded-full flex justify-center items-center",t),children:k("i",{className:W("text-white dark:text-slate-900",n)})}),L("div",{className:"flex-grow ml-4",children:[k("h3",{className:"font-medium text-slate-800 dark:text-slate-200",children:e.title}),l&&k("p",{className:"text-slate-500 dark:text-slate-400",dangerouslySetInnerHTML:{__html:l}})]}),r&&L(q,{type:"button",onClick:()=>e.emit(r.action),color:"tertiary",size:"small",children:[k("i",{className:`fa-solid fa-${r.icon} mr-2`}),r.label]})]})};function Jh(e){if(e.state==="warn"||e.state==="fail")return e.button}function qh(e){switch(e.state){case"ok":return e.message;case"fail":return e.message;case"warn":return e.message;case"unexpected":return e.message??"Unexpected check error, please try again";default:return}}function em(e){return Object.entries(e)}function tm(e){return e!=null}class nm{constructor(t,n,r,l){this.data=t,this.installedAppVersion=n,this.screenshotMonitoringEnabled=r,this.keystrokeMonitoringEnabled=l}get items(){return[this.appVersion,...this.filterItems,this.screenRecordingPermission,this.keystrokeRecordingPermission,this.notificationsPermission,this.macOsUserType,this.accountStatus].filter(tm)}get failingChecksCount(){return this.items.filter(({state:t})=>t==="fail").length}get isChecking(){return this.items.some(({state:t})=>t==="checking")}get appVersion(){const{latestAppVersion:t}=this.data;return t===void 0?{title:"App Version",state:"checking"}:Kr(t)?{title:"App Version",state:"unexpected",message:g0(t)}:this.installedAppVersion===t.value||this.installedAppVersion>t.value?{title:"App Version",state:"ok",message:`You're up to date (${this.installedAppVersion})`}:{title:"App Version",state:"warn",message:`Update available (${t.value})`,button:{icon:"sync",label:"Update",action:"upgradeAppClicked"}}}get screenRecordingPermission(){if(this.screenshotMonitoringEnabled)return this.data.screenRecordingPermissionOk===void 0?{title:"Screen recording permission",state:"checking"}:this.data.screenRecordingPermissionOk?{title:"Screen recording permission",state:"ok"}:{title:"Screen recording permission",state:"fail",message:"Gertrude can't take screenshots until you give permission",button:{icon:"cog",label:"Fix permission",action:"fixScreenRecordingPermissionClicked"}}}get keystrokeRecordingPermission(){if(this.keystrokeMonitoringEnabled)return this.data.keystrokeRecordingPermissionOk===void 0?{title:"Keystroke recording permission",state:"checking"}:this.data.keystrokeRecordingPermissionOk?{title:"Keystroke recording permission",state:"ok"}:{title:"Keystroke recording permission",state:"fail",message:"Gertrude can't monitor keystrokes until you give permission",button:{icon:"cog",label:"Fix permission",action:"fixKeystrokeRecordingPermissionClicked"}}}get macOsUserType(){return this.data.macOsUserType===void 0?{title:"macOS user account type",state:"checking"}:Kr(this.data.macOsUserType)?{title:"macOS user account type",state:"unexpected"}:this.data.macOsUserType.value!=="standard"?{title:"Mac user has admin privileges",state:"fail",message:"Admin users can disable Gertrude if they have the password",button:{icon:"user",label:"Remove admin privilege",action:"removeUserAdminPrivilegeClicked"}}:{title:"macOS user account type",state:"ok"}}get notificationsPermission(){switch(this.data.notificationsSetting){case"alert":return{title:"Notification settings",state:"ok"};case"banner":return{title:"Notification settings",state:"warn",message:'Set to "banner", recommended setting is "alert"',button:{icon:"cog",label:"Fix setting",action:"fixNotificationPermissionClicked"}};case"none":return{title:"Notification settings",state:"fail",message:"Notifications disabled, child will miss critical updates",button:{icon:"cog",label:"Fix setting",action:"fixNotificationPermissionClicked"}};default:return{title:"Notification settings",state:"checking"}}}get accountStatus(){if(this.data.accountStatus===void 0)return{title:"Gertrude account status",state:"checking"};if(Kr(this.data.accountStatus))return{title:"Gertrude account status",state:"unexpected"};switch(this.data.accountStatus.value){case"active":return{title:"Gertrude account status",state:"ok"};case"needsAttention":return{title:"Gertrude account status",state:"warn",message:"Needs attention: log in to the Gertrude parents website for more details"};default:return{title:"Gertrude account status",state:"fail",message:"Log in to the Gertrude parents website to resolve"}}}get filterItems(){const{filterStatus:t,latestAppVersion:n}=this.data;if(t===void 0||n===void 0||t.case==="installing"||t.case==="communicationBroken"&&t.repairing)return[{title:"Filter status",state:"checking"}];if(t.case==="disabled")return[{title:"Filter status",state:"warn",message:"Filter has been disabled",button:{icon:"cog",label:"Enable filter",action:"enableFilterClicked"}}];if(t.case==="notInstalled")return[{title:"Filter status",state:"warn",message:"Filter has not been installed",button:{icon:"cog",label:"Install filter",action:"installFilterClicked"}}];if(t.case==="installTimeout")return[{title:"Filter status",state:"unexpected",message:"Installation did not complete, try again"}];if(t.case==="unexpected")return[{title:"Filter status",state:"unexpected",message:"Unexpected error: try rebooting the computer"}];if(t.case==="communicationBroken")return[{title:"Filter to app communication broken",state:"fail",message:"If repair and recheck fails, restart the computer to resolve",button:{icon:"sync",label:"Attempt repair",action:"repairFilterCommunicationClicked"}}];const r=[],{version:l,numUserKeys:i}=t;return this.shouldShowFilterOutOfDateItem(n,l)&&r.push({title:"Filter version",state:"fail",message:`Filter version out of date (${l})`,button:{icon:"sync",label:"Reinstall filter",action:"repairOutOfDateFilterClicked"}}),i>0?r.push({title:"Filter rules",state:"ok",message:`Looks good, ${i} keys loaded`}):r.push({title:"Filter rules",state:"warn",message:"No keys loaded, try refreshing rules",button:{icon:"sync",label:"Refresh rules",action:"zeroKeysRefreshRulesClicked"}}),r}shouldShowFilterOutOfDateItem(t,n){return Kr(t)?n!==this.installedAppVersion:this.installedAppVersion!==t.value?!1:n!==this.installedAppVersion}}const rm=({installedAppVersion:e,screenshotMonitoringEnabled:t,keystrokeMonitoringEnabled:n,emit:r,...l})=>{const i=new nm(l,e,t,n);return L("div",{className:"h-full overflow-y-auto relative",children:[L("header",{className:"flex items-center justify-between border-b p-4 border-slate-200 dark:border-slate-800 sticky bg-white dark:bg-slate-900 top-0 z-10",children:[L("div",{children:[k("h2",{className:"text-2xl font-bold text-slate-800 dark:text-slate-100",children:"Health check"}),k("span",{className:W("text-slate-600 dark:text-slate-400",i.isChecking&&"italic opacity-50"),children:i.isChecking?"Checking...":i.failingChecksCount?`${i.failingChecksCount} ${B0("failing check",i.failingChecksCount)}!`:"Everything looks good!"})]}),L(q,{type:"button",onClick:()=>r("recheckClicked"),disabled:i.isChecking,color:"secondary",size:"small",children:[k("i",{className:W("fa-solid fa-sync mr-2",i.isChecking&&"animate-spin")}),i.isChecking?"Checking...":"Recheck"]})]}),k("ul",{className:"flex flex-col space-y-2 p-4",children:i.items.map(o=>k(Zh,{...o,emit:r},o.title))})]})},lm=({title:e,children:t,button:n,className:r})=>L("div",{className:W("flex flex-col items-center justify-center p-4 rounded-2xl border border-red-200 dark:border-red-700/50 bg-red-50/30 dark:bg-red-600/5 flex-grow",r),children:[k("span",{className:"font-bold text-lg text-slate-700 dark:text-white/80 mb-2",children:e}),t&&k("span",{className:"text-red-500 mb-4 dark:text-red-400",children:t}),n&&L(q,{type:"button",onClick:n.action,color:"warning",size:"small",children:[k("i",{className:`fa-solid ${n.icon} mr-2`}),n.text]})]}),im=({users:e,emit:t})=>e?e.case==="error"?k("div",{className:"p-6",children:k(lm,{title:"Unexpected error",button:{text:"Check health",icon:"fa-heart-pulse",action:()=>t({case:"gotoScreenClicked",screen:"healthCheck"})},children:"Check health, or contact support if the problem persists."})}):L("div",{className:"flex flex-col h-full",children:[k("header",{className:"flex items-center justify-between border-b p-4 border-slate-200 sticky bg-white dark:border-slate-800 dark:bg-slate-900 top-0",children:k("h2",{className:"text-2xl font-bold text-slate-800 dark:text-slate-100",children:"Exempt users"})}),L("main",{className:"p-4 flex-grow flex flex-col relative",children:[L("div",{className:"mr-4",children:[L("p",{className:"text-slate-500 dark:text-slate-400",children:["Gertrude's network filter has to make decisions about whether to allow or deny network requests from every user on this computer. For maximum internet safety, it defaults to blocking all requests for users that it doesn't have rules for. If this computer has another user or users who should have unrestricted internet access (like a parent's admin account on a shared computer), you can make that user"," ",k("strong",{className:"text-slate-700 dark:text-slate-200",children:"exempt from filtering"})," ","by selecting the user name below."]}),L("p",{className:"text-slate-500 dark:text-slate-400 mt-4",children:[k("strong",{className:"text-slate-700 dark:text-slate-200",children:"Please note:"})," ","any user that is exempt from filtering should have a password enabled that is unknown to any individual subject to filtering, or else they would be able to log in to that user at any time and also have unrestricted internet access."]})]}),k("ul",{className:"mt-4 space-y-2 flex-grow",children:e.value.map(n=>k(om,{name:n.name,isExempt:n.isExempt,onToggle:()=>t({case:"setUserExemption",userId:n.id,enabled:!n.isExempt})},n.id))}),k("div",{className:"flex justify-end mt-4",children:L(q,{type:"button",onClick:()=>t({case:"administrateOSUserAccountsClicked"}),color:"secondary",size:"medium",className:"",children:["Administrate user accounts",k("i",{className:"fa-solid fa-arrow-right ml-2"})]})})]})]}):null,om=({name:e,isExempt:t,onToggle:n})=>L("div",{onClick:n,className:W("flex items-center justify-start rounded-xl p-2 pl-4",t&&"bg-red-50 dark:bg-red-500/10"),children:[k("button",{className:W("w-5 h-5 rounded-full border-slate-300 dark:border-slate-700 border mr-4 flex justify-center items-center hover:scale-105 transition-[transform,border-color,border,background-color] duration-100",t&&"bg-red-500 !border-red-500 dark:border-red-500"),children:k("i",{className:"fa-solid fa-check text-white dark:text-slate-900 text-xs"})}),L("div",{className:"flex items-center space-x-2 grow",children:[k("h3",{className:"font-bold dark:text-white grow",children:e}),k("span",{className:"text-red-500 dark:text-red-400 pr-2",children:t?"exempt from filtering - unrestricted internet access":""})]})]}),um=({emit:e,pairqlEndpointDefault:t,pairqlEndpointOverride:n,websocketEndpointDefault:r,websocketEndpointOverride:l,appcastEndpointDefault:i,appcastEndpointOverride:o,appVersions:u})=>{const[s,a]=y.useState(n??""),[p,f]=y.useState(l??""),[m,g]=y.useState(o??""),[v,x]=y.useState(""),N=u?em(u).map(([d,c])=>({display:c,value:d})).sort((d,c)=>c.value.localeCompare(d.value)):null;return L("div",{className:"flex flex-col items-stretch h-full p-6 space-y-5",children:[L("div",{className:"pb-8 flex justify-between items-start",children:[N?L("div",{className:"flex space-x-2 items-end",children:[k(q,{size:"small",disabled:v==="",onClick:()=>e({case:"forceUpdateToSpecificVersionClicked",version:v}),type:"button",color:"tertiary",children:"Force update to:"}),k(Wh,{size:"small",options:[{display:"choose version...",value:""},...N],selectedOption:v,setSelected:x})]}):k("div",{className:"text-gray-400 italic",children:"Loading app versions..."}),k(q,{className:"h-12",onClick:()=>e({case:"deleteAllDeviceStorageClicked"}),type:"button",color:"warning",children:"Purge all device storage"})]}),L("div",{className:"flex items-end gap-x-2",children:[k(_i,{label:"API PairQL endpoint override:",type:"url",value:s,placeholder:t,setValue:a}),k(q,{className:"h-12",disabled:s.trim()==="",onClick:()=>e({case:"pairqlEndpointSet",url:s.trim()}),type:"button",color:"secondary",children:"Set"}),k(q,{className:"h-12",disabled:n===void 0,onClick:()=>e({case:"pairqlEndpointSet",url:void 0}),type:"button",color:"secondary",children:"Clear"})]}),L("div",{className:"flex items-end gap-x-2",children:[k(_i,{label:"Websocket endpoint override:",type:"url",value:p,placeholder:r,setValue:f}),k(q,{className:"h-12",disabled:p.trim()==="",onClick:()=>e({case:"websocketEndpointSet",url:p.trim()}),type:"button",color:"secondary",children:"Set"}),k(q,{className:"h-12",disabled:l===void 0,onClick:()=>e({case:"websocketEndpointSet",url:void 0}),type:"button",color:"secondary",children:"Clear"})]}),L("div",{className:"flex items-end gap-x-2",children:[k(_i,{label:"Sparkle Appcast endpoint override:",type:"url",value:m,placeholder:i,setValue:g}),k(q,{className:"h-12",disabled:m.trim()==="",onClick:()=>e({case:"appcastEndpointSet",url:m.trim()}),type:"button",color:"secondary",children:"Set"}),k(q,{className:"h-12",disabled:o===void 0,onClick:()=>e({case:"appcastEndpointSet",url:void 0}),type:"button",color:"secondary",children:"Clear"})]})]})};class sm extends y0{appState(){return{windowOpen:!0,screen:"healthCheck",filterState:{case:"off"},installedAppVersion:"0.0.0",healthCheck:{},releaseChannel:"stable",quitting:!1}}viewState(){return{filterSuspensionDurationInSeconds:String(60*5),dangerZoneModal:"hidden"}}initializer(){return{...this.appState(),...this.viewState()}}reducer(t,n){switch(n.type){case"filterSuspensionDurationInSecondsChanged":return{...t,filterSuspensionDurationInSeconds:n.value};case"receivedUpdatedAppState":return{...t,...n.appState};case"dangerZoneStopFilterClicked":return{...t,dangerZoneModal:"stopFilter"};case"dangerZoneQuitAppClicked":return{...t,dangerZoneModal:"quitApp"};case"dangerZoneModalDismissed":return{...t,dangerZoneModal:"hidden"};case"appEventEmitted":return n.event.case==="confirmStopFilterClicked"?{...t,dangerZoneModal:"hidden"}:t}}}const am=new sm,cm=({healthCheck:e,filterState:t,user:n,installedAppVersion:r,availableAppUpdate:l,releaseChannel:i,exemptableUsers:o,screen:u,advanced:s,quitting:a,dangerZoneModal:p,emit:f,dispatch:m})=>{let g;switch(u){case"healthCheck":g=k(rm,{...e,installedAppVersion:r,screenshotMonitoringEnabled:n?.keystrokeMonitoringEnabled??!1,keystrokeMonitoringEnabled:n?.screenshotMonitoringEnabled??!1,emit:v=>f({case:"healthCheck",action:v})});break;case"actions":g=k(Yh,{releaseChannel:i,emit:f,dispatch:m,filterRunning:t.case==="on",installedAppVersion:r,availableAppUpdate:l,dangerZoneModal:p,userName:n?.name,quitting:a});break;case"exemptUsers":g=k(im,{emit:f,users:o});break;case"advanced":g=s?k(um,{...s,emit:v=>f({case:"advanced",action:v})}):k(ma,{children:"Loading..."});break}return v0(e.accountStatus)==="inactive"?k(Kh,{onRecheck:()=>f({case:"inactiveAccountRecheckClicked"}),onDisconnect:()=>f({case:"inactiveAccountDisconnectAppClicked"})}):k("div",{className:"flex flex-col h-screen",children:L("div",{className:W("flex flex-grow relative"),children:[k(Gh,{screen:u,setScreen:v=>f({case:"gotoScreenClicked",screen:v})}),k("main",{className:"flex-grow bg-white dark:bg-slate-900 ml-16",children:g})]})})},dm=k0(am,cm);Di.createRoot(document.getElementById("app")).render(k(dm,{})); +`));let m=_d((l=p.props)==null?void 0:l.className,s.className),g=m?{className:m}:{};return y.cloneElement(p,Object.assign({},Ld(p.props,Fo(Ri(s,["ref"]))),f,a,uh(p.ref,a.ref),g))}return y.createElement(i,Object.assign({},Ri(s,["ref"]),i!==y.Fragment&&a,i!==y.Fragment&&f),p)}function uh(...e){return{ref:e.every(t=>t==null)?void 0:t=>{for(let n of e)n!=null&&(typeof n=="function"?n(t):n.current=t)}}}function Ld(...e){if(e.length===0)return{};if(e.length===1)return e[0];let t={},n={};for(let r of e)for(let l in r)l.startsWith("on")&&typeof r[l]=="function"?(n[l]!=null||(n[l]=[]),n[l].push(r[l])):t[l]=r[l];if(t.disabled||t["aria-disabled"])return Object.assign(t,Object.fromEntries(Object.keys(n).map(r=>[r,void 0])));for(let r in n)Object.assign(t,{[r](l,...i){let o=n[r];for(let u of o){if((l instanceof Event||l?.nativeEvent instanceof Event)&&l.defaultPrevented)return;u(l,...i)}}});return t}function vt(e){var t;return Object.assign(y.forwardRef(e),{displayName:(t=e.displayName)!=null?t:e.name})}function Fo(e){let t=Object.assign({},e);for(let n in t)t[n]===void 0&&delete t[n];return t}function Ri(e,t=[]){let n=Object.assign({},e);for(let r of t)r in n&&delete n[r];return n}function sh(e){let t=e.parentElement,n=null;for(;t&&!(t instanceof HTMLFieldSetElement);)t instanceof HTMLLegendElement&&(n=t),t=t.parentElement;let r=t?.getAttribute("disabled")==="";return r&&ah(n)?!1:r}function ah(e){if(!e)return!1;let t=e.previousElementSibling;for(;t!==null;){if(t instanceof HTMLLegendElement)return!1;t=t.previousElementSibling}return!0}function Td(e={},t=null,n=[]){for(let[r,l]of Object.entries(e))zd(n,Rd(t,r),l);return n}function Rd(e,t){return e?e+"["+t+"]":t}function zd(e,t,n){if(Array.isArray(n))for(let[r,l]of n.entries())zd(e,Rd(t,r.toString()),l);else n instanceof Date?e.push([t,n.toISOString()]):typeof n=="boolean"?e.push([t,n?"1":"0"]):typeof n=="string"?e.push([t,n]):typeof n=="number"?e.push([t,`${n}`]):n==null?e.push([t,""]):Td(n,t,e)}let ch="div";var $d=(e=>(e[e.None=1]="None",e[e.Focusable=2]="Focusable",e[e.Hidden=4]="Hidden",e))($d||{});let dh=vt(function(e,t){let{features:n=1,...r}=e,l={ref:t,"aria-hidden":(n&2)===2?!0:void 0,style:{position:"fixed",top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0",...(n&4)===4&&(n&2)!==2&&{display:"none"}}};return jt({ourProps:l,theirProps:r,slot:{},defaultTag:ch,name:"Hidden"})}),Fu=y.createContext(null);Fu.displayName="OpenClosedContext";var _e=(e=>(e[e.Open=1]="Open",e[e.Closed=2]="Closed",e[e.Closing=4]="Closing",e[e.Opening=8]="Opening",e))(_e||{});function Du(){return y.useContext(Fu)}function Id({value:e,children:t}){return ue.createElement(Fu.Provider,{value:e},t)}var ie=(e=>(e.Space=" ",e.Enter="Enter",e.Escape="Escape",e.Backspace="Backspace",e.Delete="Delete",e.ArrowLeft="ArrowLeft",e.ArrowUp="ArrowUp",e.ArrowRight="ArrowRight",e.ArrowDown="ArrowDown",e.Home="Home",e.End="End",e.PageUp="PageUp",e.PageDown="PageDown",e.Tab="Tab",e))(ie||{});function fh(e,t,n){let[r,l]=y.useState(n),i=e!==void 0,o=y.useRef(i),u=y.useRef(!1),s=y.useRef(!1);return i&&!o.current&&!u.current?(u.current=!0,o.current=i,console.error("A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.")):!i&&o.current&&!s.current&&(s.current=!0,o.current=i,console.error("A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.")),[i?e:r,A(a=>(i||l(a),t?.(a)))]}function ia(e){return[e.screenX,e.screenY]}function ph(){let e=y.useRef([-1,-1]);return{wasMoved(t){let n=ia(t);return e.current[0]===n[0]&&e.current[1]===n[1]?!1:(e.current=n,!0)},update(t){e.current=ia(t)}}}function Fd(){let e=y.useRef(!1);return be(()=>(e.current=!0,()=>{e.current=!1}),[]),e}var hh=(e=>(e[e.Open=0]="Open",e[e.Closed=1]="Closed",e))(hh||{}),mh=(e=>(e[e.Single=0]="Single",e[e.Multi=1]="Multi",e))(mh||{}),vh=(e=>(e[e.Pointer=0]="Pointer",e[e.Other=1]="Other",e))(vh||{}),gh=(e=>(e[e.OpenListbox=0]="OpenListbox",e[e.CloseListbox=1]="CloseListbox",e[e.GoToOption=2]="GoToOption",e[e.Search=3]="Search",e[e.ClearSearch=4]="ClearSearch",e[e.RegisterOption=5]="RegisterOption",e[e.UnregisterOption=6]="UnregisterOption",e[e.RegisterLabel=7]="RegisterLabel",e))(gh||{});function zi(e,t=n=>n){let n=e.activeOptionIndex!==null?e.options[e.activeOptionIndex]:null,r=th(t(e.options.slice()),i=>i.dataRef.current.domRef.current),l=n?r.indexOf(n):null;return l===-1&&(l=null),{options:r,activeOptionIndex:l}}let yh={[1](e){return e.dataRef.current.disabled||e.listboxState===1?e:{...e,activeOptionIndex:null,listboxState:1}},[0](e){if(e.dataRef.current.disabled||e.listboxState===0)return e;let t=e.activeOptionIndex,{isSelected:n}=e.dataRef.current,r=e.options.findIndex(l=>n(l.dataRef.current.value));return r!==-1&&(t=r),{...e,listboxState:0,activeOptionIndex:t}},[2](e,t){var n;if(e.dataRef.current.disabled||e.listboxState===1)return e;let r=zi(e),l=oh(t,{resolveItems:()=>r.options,resolveActiveIndex:()=>r.activeOptionIndex,resolveId:i=>i.id,resolveDisabled:i=>i.dataRef.current.disabled});return{...e,...r,searchQuery:"",activeOptionIndex:l,activationTrigger:(n=t.trigger)!=null?n:1}},[3]:(e,t)=>{if(e.dataRef.current.disabled||e.listboxState===1)return e;let n=e.searchQuery!==""?0:1,r=e.searchQuery+t.value.toLowerCase(),l=(e.activeOptionIndex!==null?e.options.slice(e.activeOptionIndex+n).concat(e.options.slice(0,e.activeOptionIndex+n)):e.options).find(o=>{var u;return!o.dataRef.current.disabled&&((u=o.dataRef.current.textValue)==null?void 0:u.startsWith(r))}),i=l?e.options.indexOf(l):-1;return i===-1||i===e.activeOptionIndex?{...e,searchQuery:r}:{...e,searchQuery:r,activeOptionIndex:i,activationTrigger:1}},[4](e){return e.dataRef.current.disabled||e.listboxState===1||e.searchQuery===""?e:{...e,searchQuery:""}},[5]:(e,t)=>{let n={id:t.id,dataRef:t.dataRef},r=zi(e,l=>[...l,n]);return e.activeOptionIndex===null&&e.dataRef.current.isSelected(t.dataRef.current.value)&&(r.activeOptionIndex=r.options.indexOf(n)),{...e,...r}},[6]:(e,t)=>{let n=zi(e,r=>{let l=r.findIndex(i=>i.id===t.id);return l!==-1&&r.splice(l,1),r});return{...e,...n,activationTrigger:1}},[7]:(e,t)=>({...e,labelId:t.id})},Mu=y.createContext(null);Mu.displayName="ListboxActionsContext";function _r(e){let t=y.useContext(Mu);if(t===null){let n=new Error(`<${e} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(n,_r),n}return t}let ju=y.createContext(null);ju.displayName="ListboxDataContext";function Lr(e){let t=y.useContext(ju);if(t===null){let n=new Error(`<${e} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(n,Lr),n}return t}function kh(e,t){return ce(t.type,yh,e,t)}let wh=y.Fragment,xh=vt(function(e,t){let{value:n,defaultValue:r,name:l,onChange:i,by:o=(D,j)=>D===j,disabled:u=!1,horizontal:s=!1,multiple:a=!1,...p}=e;const f=s?"horizontal":"vertical";let m=ln(t),[g=a?[]:void 0,v]=fh(n,i,r),[x,N]=y.useReducer(kh,{dataRef:y.createRef(),listboxState:1,options:[],searchQuery:"",labelId:null,activeOptionIndex:null,activationTrigger:1}),d=y.useRef({static:!1,hold:!1}),c=y.useRef(null),h=y.useRef(null),w=y.useRef(null),E=A(typeof o=="string"?(D,j)=>{let te=o;return D?.[te]===j?.[te]}:o),P=y.useCallback(D=>ce(C.mode,{[1]:()=>g.some(j=>E(j,D)),[0]:()=>E(g,D)}),[g]),C=y.useMemo(()=>({...x,value:g,disabled:u,mode:a?1:0,orientation:f,compare:E,isSelected:P,optionsPropsRef:d,labelRef:c,buttonRef:h,optionsRef:w}),[g,u,a,x]);be(()=>{x.dataRef.current=C},[C]),nh([C.buttonRef,C.optionsRef],(D,j)=>{var te;N({type:1}),Od(j,Iu.Loose)||(D.preventDefault(),(te=C.buttonRef.current)==null||te.focus())},C.listboxState===0);let T=y.useMemo(()=>({open:C.listboxState===0,disabled:u,value:g}),[C,u,g]),b=A(D=>{let j=C.options.find(te=>te.id===D);!j||Be(j.dataRef.current.value)}),$=A(()=>{if(C.activeOptionIndex!==null){let{dataRef:D,id:j}=C.options[C.activeOptionIndex];Be(D.current.value),N({type:2,focus:Ee.Specific,id:j})}}),le=A(()=>N({type:0})),Ve=A(()=>N({type:1})),He=A((D,j,te)=>D===Ee.Specific?N({type:2,focus:Ee.Specific,id:j,trigger:te}):N({type:2,focus:D,trigger:te})),on=A((D,j)=>(N({type:5,id:D,dataRef:j}),()=>N({type:6,id:D}))),rt=A(D=>(N({type:7,id:D}),()=>N({type:7,id:null}))),Be=A(D=>ce(C.mode,{[0](){return v?.(D)},[1](){let j=C.value.slice(),te=j.findIndex(Ie=>E(Ie,D));return te===-1?j.push(D):j.splice(te,1),v?.(j)}})),At=A(D=>N({type:3,value:D})),O=A(()=>N({type:4})),R=y.useMemo(()=>({onChange:Be,registerOption:on,registerLabel:rt,goToOption:He,closeListbox:Ve,openListbox:le,selectActiveOption:$,selectOption:b,search:At,clearSearch:O}),[]),z={ref:m},F=y.useRef(null),J=In();return y.useEffect(()=>{!F.current||r!==void 0&&J.addEventListener(F.current,"reset",()=>{Be(r)})},[F,Be]),ue.createElement(Mu.Provider,{value:R},ue.createElement(ju.Provider,{value:C},ue.createElement(Id,{value:ce(C.listboxState,{[0]:_e.Open,[1]:_e.Closed})},l!=null&&g!=null&&Td({[l]:g}).map(([D,j],te)=>ue.createElement(dh,{features:$d.Hidden,ref:te===0?Ie=>{var Ut;F.current=(Ut=Ie?.closest("form"))!=null?Ut:null}:void 0,...Fo({key:D,as:"input",type:"hidden",hidden:!0,readOnly:!0,name:D,value:j})})),jt({ourProps:z,theirProps:p,slot:T,defaultTag:wh,name:"Listbox"}))))}),Sh="button",Eh=vt(function(e,t){var n;let r=Xl(),{id:l=`headlessui-listbox-button-${r}`,...i}=e,o=Lr("Listbox.Button"),u=_r("Listbox.Button"),s=ln(o.buttonRef,t),a=In(),p=A(N=>{switch(N.key){case ie.Space:case ie.Enter:case ie.ArrowDown:N.preventDefault(),u.openListbox(),a.nextFrame(()=>{o.value||u.goToOption(Ee.First)});break;case ie.ArrowUp:N.preventDefault(),u.openListbox(),a.nextFrame(()=>{o.value||u.goToOption(Ee.Last)});break}}),f=A(N=>{switch(N.key){case ie.Space:N.preventDefault();break}}),m=A(N=>{if(sh(N.currentTarget))return N.preventDefault();o.listboxState===0?(u.closeListbox(),a.nextFrame(()=>{var d;return(d=o.buttonRef.current)==null?void 0:d.focus({preventScroll:!0})})):(N.preventDefault(),u.openListbox())}),g=Nd(()=>{if(o.labelId)return[o.labelId,l].join(" ")},[o.labelId,l]),v=y.useMemo(()=>({open:o.listboxState===0,disabled:o.disabled,value:o.value}),[o]),x={ref:s,id:l,type:rh(e,o.buttonRef),"aria-haspopup":"listbox","aria-controls":(n=o.optionsRef.current)==null?void 0:n.id,"aria-expanded":o.disabled?void 0:o.listboxState===0,"aria-labelledby":g,disabled:o.disabled,onKeyDown:p,onKeyUp:f,onClick:m};return jt({ourProps:x,theirProps:i,slot:v,defaultTag:Sh,name:"Listbox.Button"})}),Ch="label",Nh=vt(function(e,t){let n=Xl(),{id:r=`headlessui-listbox-label-${n}`,...l}=e,i=Lr("Listbox.Label"),o=_r("Listbox.Label"),u=ln(i.labelRef,t);be(()=>o.registerLabel(r),[r]);let s=A(()=>{var p;return(p=i.buttonRef.current)==null?void 0:p.focus({preventScroll:!0})}),a=y.useMemo(()=>({open:i.listboxState===0,disabled:i.disabled}),[i]);return jt({ourProps:{ref:u,id:r,onClick:s},theirProps:l,slot:a,defaultTag:Ch,name:"Listbox.Label"})}),Ph="ul",Oh=$l.RenderStrategy|$l.Static,_h=vt(function(e,t){var n;let r=Xl(),{id:l=`headlessui-listbox-options-${r}`,...i}=e,o=Lr("Listbox.Options"),u=_r("Listbox.Options"),s=ln(o.optionsRef,t),a=In(),p=In(),f=Du(),m=(()=>f!==null?(f&_e.Open)===_e.Open:o.listboxState===0)();y.useEffect(()=>{var d;let c=o.optionsRef.current;!c||o.listboxState===0&&c!==((d=Pd(c))==null?void 0:d.activeElement)&&c.focus({preventScroll:!0})},[o.listboxState,o.optionsRef]);let g=A(d=>{switch(p.dispose(),d.key){case ie.Space:if(o.searchQuery!=="")return d.preventDefault(),d.stopPropagation(),u.search(d.key);case ie.Enter:if(d.preventDefault(),d.stopPropagation(),o.activeOptionIndex!==null){let{dataRef:c}=o.options[o.activeOptionIndex];u.onChange(c.current.value)}o.mode===0&&(u.closeListbox(),tn().nextFrame(()=>{var c;return(c=o.buttonRef.current)==null?void 0:c.focus({preventScroll:!0})}));break;case ce(o.orientation,{vertical:ie.ArrowDown,horizontal:ie.ArrowRight}):return d.preventDefault(),d.stopPropagation(),u.goToOption(Ee.Next);case ce(o.orientation,{vertical:ie.ArrowUp,horizontal:ie.ArrowLeft}):return d.preventDefault(),d.stopPropagation(),u.goToOption(Ee.Previous);case ie.Home:case ie.PageUp:return d.preventDefault(),d.stopPropagation(),u.goToOption(Ee.First);case ie.End:case ie.PageDown:return d.preventDefault(),d.stopPropagation(),u.goToOption(Ee.Last);case ie.Escape:return d.preventDefault(),d.stopPropagation(),u.closeListbox(),a.nextFrame(()=>{var c;return(c=o.buttonRef.current)==null?void 0:c.focus({preventScroll:!0})});case ie.Tab:d.preventDefault(),d.stopPropagation();break;default:d.key.length===1&&(u.search(d.key),p.setTimeout(()=>u.clearSearch(),350));break}}),v=Nd(()=>{var d,c,h;return(h=(d=o.labelRef.current)==null?void 0:d.id)!=null?h:(c=o.buttonRef.current)==null?void 0:c.id},[o.labelRef.current,o.buttonRef.current]),x=y.useMemo(()=>({open:o.listboxState===0}),[o]),N={"aria-activedescendant":o.activeOptionIndex===null||(n=o.options[o.activeOptionIndex])==null?void 0:n.id,"aria-multiselectable":o.mode===1?!0:void 0,"aria-labelledby":v,"aria-orientation":o.orientation,id:l,onKeyDown:g,role:"listbox",tabIndex:0,ref:s};return jt({ourProps:N,theirProps:i,slot:x,defaultTag:Ph,features:Oh,visible:m,name:"Listbox.Options"})}),Lh="li",Th=vt(function(e,t){let n=Xl(),{id:r=`headlessui-listbox-option-${n}`,disabled:l=!1,value:i,...o}=e,u=Lr("Listbox.Option"),s=_r("Listbox.Option"),a=u.activeOptionIndex!==null?u.options[u.activeOptionIndex].id===r:!1,p=u.isSelected(i),f=y.useRef(null),m=ct({disabled:l,value:i,domRef:f,get textValue(){var E,P;return(P=(E=f.current)==null?void 0:E.textContent)==null?void 0:P.toLowerCase()}}),g=ln(t,f);be(()=>{if(u.listboxState!==0||!a||u.activationTrigger===0)return;let E=tn();return E.requestAnimationFrame(()=>{var P,C;(C=(P=f.current)==null?void 0:P.scrollIntoView)==null||C.call(P,{block:"nearest"})}),E.dispose},[f,a,u.listboxState,u.activationTrigger,u.activeOptionIndex]),be(()=>s.registerOption(r,m),[m,r]);let v=A(E=>{if(l)return E.preventDefault();s.onChange(i),u.mode===0&&(s.closeListbox(),tn().nextFrame(()=>{var P;return(P=u.buttonRef.current)==null?void 0:P.focus({preventScroll:!0})}))}),x=A(()=>{if(l)return s.goToOption(Ee.Nothing);s.goToOption(Ee.Specific,r)}),N=ph(),d=A(E=>N.update(E)),c=A(E=>{!N.wasMoved(E)||l||a||s.goToOption(Ee.Specific,r,0)}),h=A(E=>{!N.wasMoved(E)||l||!a||s.goToOption(Ee.Nothing)}),w=y.useMemo(()=>({active:a,selected:p,disabled:l}),[a,p,l]);return jt({ourProps:{id:r,ref:g,role:"option",tabIndex:l===!0?void 0:-1,"aria-disabled":l===!0?!0:void 0,"aria-selected":p,disabled:void 0,onClick:v,onFocus:x,onPointerEnter:d,onMouseEnter:d,onPointerMove:c,onMouseMove:c,onPointerLeave:h,onMouseLeave:h},theirProps:o,slot:w,defaultTag:Lh,name:"Listbox.Option"})}),Yr=Object.assign(xh,{Button:Eh,Label:Nh,Options:_h,Option:Th});function Rh(e=0){let[t,n]=y.useState(e),r=y.useCallback(u=>n(s=>s|u),[t]),l=y.useCallback(u=>Boolean(t&u),[t]),i=y.useCallback(u=>n(s=>s&~u),[n]),o=y.useCallback(u=>n(s=>s^u),[n]);return{flags:t,addFlag:r,hasFlag:l,removeFlag:i,toggleFlag:o}}function zh(e){let t={called:!1};return(...n)=>{if(!t.called)return t.called=!0,e(...n)}}function $i(e,...t){e&&t.length>0&&e.classList.add(...t)}function Ii(e,...t){e&&t.length>0&&e.classList.remove(...t)}function $h(e,t){let n=tn();if(!e)return n.dispose;let{transitionDuration:r,transitionDelay:l}=getComputedStyle(e),[i,o]=[r,l].map(u=>{let[s=0]=u.split(",").filter(Boolean).map(a=>a.includes("ms")?parseFloat(a):parseFloat(a)*1e3).sort((a,p)=>p-a);return s});if(i+o!==0){let u=n.addEventListener(e,"transitionend",s=>{s.target===s.currentTarget&&(t(),u())})}else t();return n.add(()=>t()),n.dispose}function Ih(e,t,n,r){let l=n?"enter":"leave",i=tn(),o=r!==void 0?zh(r):()=>{};l==="enter"&&(e.removeAttribute("hidden"),e.style.display="");let u=ce(l,{enter:()=>t.enter,leave:()=>t.leave}),s=ce(l,{enter:()=>t.enterTo,leave:()=>t.leaveTo}),a=ce(l,{enter:()=>t.enterFrom,leave:()=>t.leaveFrom});return Ii(e,...t.enter,...t.enterTo,...t.enterFrom,...t.leave,...t.leaveFrom,...t.leaveTo,...t.entered),$i(e,...u,...a),i.nextFrame(()=>{Ii(e,...a),$i(e,...s),$h(e,()=>(Ii(e,...u),$i(e,...t.entered),o()))}),i.dispose}function Fh({container:e,direction:t,classes:n,onStart:r,onStop:l}){let i=Fd(),o=In(),u=ct(t);be(()=>{let s=tn();o.add(s.dispose);let a=e.current;if(a&&u.current!=="idle"&&i.current)return s.dispose(),r.current(u.current),s.add(Ih(a,n.current,u.current==="enter",()=>{s.dispose(),l.current(u.current)})),s.dispose},[t])}function Vt(e=""){return e.split(" ").filter(t=>t.trim().length>1)}let Zl=y.createContext(null);Zl.displayName="TransitionContext";var Dh=(e=>(e.Visible="visible",e.Hidden="hidden",e))(Dh||{});function Mh(){let e=y.useContext(Zl);if(e===null)throw new Error("A is used but it is missing a parent or .");return e}function jh(){let e=y.useContext(Jl);if(e===null)throw new Error("A is used but it is missing a parent or .");return e}let Jl=y.createContext(null);Jl.displayName="NestingContext";function ql(e){return"children"in e?ql(e.children):e.current.filter(({el:t})=>t.current!==null).filter(({state:t})=>t==="visible").length>0}function Dd(e,t){let n=ct(e),r=y.useRef([]),l=Fd(),i=In(),o=A((g,v=st.Hidden)=>{let x=r.current.findIndex(({el:N})=>N===g);x!==-1&&(ce(v,{[st.Unmount](){r.current.splice(x,1)},[st.Hidden](){r.current[x].state="hidden"}}),i.microTask(()=>{var N;!ql(r)&&l.current&&((N=n.current)==null||N.call(n))}))}),u=A(g=>{let v=r.current.find(({el:x})=>x===g);return v?v.state!=="visible"&&(v.state="visible"):r.current.push({el:g,state:"visible"}),()=>o(g,st.Unmount)}),s=y.useRef([]),a=y.useRef(Promise.resolve()),p=y.useRef({enter:[],leave:[],idle:[]}),f=A((g,v,x)=>{s.current.splice(0),t&&(t.chains.current[v]=t.chains.current[v].filter(([N])=>N!==g)),t?.chains.current[v].push([g,new Promise(N=>{s.current.push(N)})]),t?.chains.current[v].push([g,new Promise(N=>{Promise.all(p.current[v].map(([d,c])=>c)).then(()=>N())})]),v==="enter"?a.current=a.current.then(()=>t?.wait.current).then(()=>x(v)):x(v)}),m=A((g,v,x)=>{Promise.all(p.current[v].splice(0).map(([N,d])=>d)).then(()=>{var N;(N=s.current.shift())==null||N()}).then(()=>x(v))});return y.useMemo(()=>({children:r,register:u,unregister:o,onStart:f,onStop:m,wait:a,chains:p}),[u,o,r,f,m,p,a])}function Ah(){}let Uh=["beforeEnter","afterEnter","beforeLeave","afterLeave"];function oa(e){var t;let n={};for(let r of Uh)n[r]=(t=e[r])!=null?t:Ah;return n}function bh(e){let t=y.useRef(oa(e));return y.useEffect(()=>{t.current=oa(e)},[e]),t}let Vh="div",Md=$l.RenderStrategy,jd=vt(function(e,t){let{beforeEnter:n,afterEnter:r,beforeLeave:l,afterLeave:i,enter:o,enterFrom:u,enterTo:s,entered:a,leave:p,leaveFrom:f,leaveTo:m,...g}=e,v=y.useRef(null),x=ln(v,t),N=g.unmount?st.Unmount:st.Hidden,{show:d,appear:c,initial:h}=Mh(),[w,E]=y.useState(d?"visible":"hidden"),P=jh(),{register:C,unregister:T}=P,b=y.useRef(null);y.useEffect(()=>C(v),[C,v]),y.useEffect(()=>{if(N===st.Hidden&&v.current){if(d&&w!=="visible"){E("visible");return}return ce(w,{hidden:()=>T(v),visible:()=>C(v)})}},[w,v,C,T,d,N]);let $=ct({enter:Vt(o),enterFrom:Vt(u),enterTo:Vt(s),entered:Vt(a),leave:Vt(p),leaveFrom:Vt(f),leaveTo:Vt(m)}),le=bh({beforeEnter:n,afterEnter:r,beforeLeave:l,afterLeave:i}),Ve=$u();y.useEffect(()=>{if(Ve&&w==="visible"&&v.current===null)throw new Error("Did you forget to passthrough the `ref` to the actual DOM node?")},[v,w,Ve]);let He=h&&!c,on=(()=>!Ve||He||b.current===d?"idle":d?"enter":"leave")(),rt=Rh(0),Be=A(F=>ce(F,{enter:()=>{rt.addFlag(_e.Opening),le.current.beforeEnter()},leave:()=>{rt.addFlag(_e.Closing),le.current.beforeLeave()},idle:()=>{}})),At=A(F=>ce(F,{enter:()=>{rt.removeFlag(_e.Opening),le.current.afterEnter()},leave:()=>{rt.removeFlag(_e.Closing),le.current.afterLeave()},idle:()=>{}})),O=Dd(()=>{E("hidden"),T(v)},P);Fh({container:v,classes:$,direction:on,onStart:ct(F=>{O.onStart(v,F,Be)}),onStop:ct(F=>{O.onStop(v,F,At),F==="leave"&&!ql(O)&&(E("hidden"),T(v))})}),y.useEffect(()=>{!He||(N===st.Hidden?b.current=null:b.current=d)},[d,He,w]);let R=g,z={ref:x};return c&&d&&zt.isServer&&(R={...R,className:_d(g.className,...$.current.enter,...$.current.enterFrom)}),ue.createElement(Jl.Provider,{value:O},ue.createElement(Id,{value:ce(w,{visible:_e.Open,hidden:_e.Closed})|rt.flags},jt({ourProps:z,theirProps:R,defaultTag:Vh,features:Md,visible:w==="visible",name:"Transition.Child"})))}),Do=vt(function(e,t){let{show:n,appear:r=!1,unmount:l,...i}=e,o=y.useRef(null),u=ln(o,t);$u();let s=Du();if(n===void 0&&s!==null&&(n=(s&_e.Open)===_e.Open),![!0,!1].includes(n))throw new Error("A is used but it is missing a `show={true | false}` prop.");let[a,p]=y.useState(n?"visible":"hidden"),f=Dd(()=>{p("hidden")}),[m,g]=y.useState(!0),v=y.useRef([n]);be(()=>{m!==!1&&v.current[v.current.length-1]!==n&&(v.current.push(n),g(!1))},[v,n]);let x=y.useMemo(()=>({show:n,appear:r,initial:m}),[n,r,m]);y.useEffect(()=>{if(n)p("visible");else if(!ql(f))p("hidden");else{let d=o.current;if(!d)return;let c=d.getBoundingClientRect();c.x===0&&c.y===0&&c.width===0&&c.height===0&&p("hidden")}},[n,f]);let N={unmount:l};return ue.createElement(Jl.Provider,{value:f},ue.createElement(Zl.Provider,{value:x},jt({ourProps:{...N,as:y.Fragment,children:ue.createElement(jd,{ref:u,...N,...i})},theirProps:{},defaultTag:y.Fragment,features:Md,visible:a==="visible",name:"Transition"})))}),Hh=vt(function(e,t){let n=y.useContext(Zl)!==null,r=Du()!==null;return ue.createElement(ue.Fragment,null,!n&&r?ue.createElement(Do,{ref:t,...e}):ue.createElement(jd,{ref:t,...e}))}),Bh=Object.assign(Do,{Child:Hh,Root:Do});function Wh({options:e,selectedOption:t,setSelected:n,label:r,size:l="large",testId:i,disabled:o}){let u="";switch(l){case"small":u="py-1 bg-white dark:bg-slate-900 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-500 dark:text-slate-400 dark:ring-offset-slate-900";break;case"medium":u="py-2 bg-white text-slate-400 hover:bg-slate-100";break;case"large":u="!py-2 bg-slate-white text-slate-300 hover:bg-slate-100 m-1 !rounded-lg focus:!ring-offset-0";break}return k(Yr,{value:t,onChange:n,children:({open:s})=>L("div",{"data-test":i,className:W("relative",o&&"opacity-60 cursor-not-allowed"),children:[r&&k(Cd,{className:"w-full inline-block pb-px",children:r}),L("div",{className:"relative",children:[k("div",{className:"rounded-xl w-full",children:L("div",{className:"relative z-0 inline-flex rounded-xl w-full border border-slate-200 dark:border-slate-700",children:[k("div",{className:"relative flex flex-grow items-center bg-white dark:bg-slate-900 pl-3 pr-4 border border-transparent rounded-l-xl text-slate-700 dark:text-slate-300",children:k("p",{className:"ml-2.5 font-medium",children:(e.find(a=>a.value===t)??e[0])?.display??"make a selection..."})}),L(Yr.Button,{className:W("relative inline-flex items-center px-4 rounded-l-none rounded-r-xl text-sm font-medium focus:outline-none focus:z-10 focus:ring-2 focus:ring-offset-2 focus:ring-offset-slate-50 focus:ring-indigo-500 transition-[outline,background-color] duration-100",u,o&&"cursor-not-allowed pointer-events-none"),children:[k("span",{className:"sr-only",children:"Change published status"}),k("i",{className:`fa fa-chevron-down text-xl text-opacity-90 transition-transform duration-100 ${s?"-rotate-180":"rotate-0"}`,"aria-hidden":"true"})]})]})}),k(Bh,{show:s,as:y.Fragment,leave:"transition-[opacity,transform] ease-in duration-100",leaveFrom:"opacity-100",leaveTo:"opacity-0 -translate-y-1",enter:"transition-[opacity,transform] ease-in duration-100",enterFrom:"opacity-0 -translate-y-1",enterTo:"opacity-100",children:k(Yr.Options,{className:"origin-top-right absolute z-20 right-0 mt-2 p-2 w-72 rounded-xl shadow-lg overflow-hidden bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 focus:outline-none flex flex-col",children:e.map(({value:a,display:p})=>k(Yr.Option,{className:({active:f})=>W(f?"bg-violet-100 text-slate-900 dark:text-slate-200":"text-slate-900 dark:text-slate-300","cursor-pointer select-none relative rounded-xl transition-[background-color] duration-75",l==="small"?"py-2 px-3":"p-3.5 text-md",l==="small"&&f&&"bg-slate-50 dark:bg-slate-700/50"),value:a,children:({selected:f})=>k("div",{className:"flex flex-col",children:L("div",{className:"flex justify-between",children:[k("p",{className:f?"font-semibold":"font-normal",children:p}),f?k("span",{className:"text-violet-700",children:k("i",{className:"fa fa-check h-5 w-5","aria-hidden":"true"})}):null]})})},a))})})]})]})})}const Qh=({type:e,className:t,children:n,size:r})=>{let l="";switch(e){case"green":case"ok":l="bg-green-50 dark:bg-green-500/10 border-green-200 dark:border-green-500/50 text-green-600 dark:text-green-300";break;case"red":case"error":l="bg-red-50 dark:bg-red-500/10 border-red-200 dark:border-red-500/50 text-red-600 dark:text-red-300";break;case"yellow":case"warning":l="bg-yellow-50 dark:bg-yellow-500/10 border-yellow-200 dark:border-yellow-500/50 text-yellow-600 dark:text-yellow-300";break;case"blue":case"info":l="bg-blue-50 dark:bg-blue-500/10 border-blue-200 dark:border-blue-500/50 text-blue-600 dark:text-blue-300";break}return k("div",{className:W("max-w-fit border rounded-full flex justify-center items-center",{"text-xs px-[12px] py-[2px]":r==="small","text-base px-6 py-0.5":r==="large","text-sm px-[14px] py-[2.5px]":r==="medium"||!r},l,t),children:n})},Kh=({short:e,onRecheck:t,onDisconnect:n})=>{const[r,l]=y.useState(!1),[i,o]=y.useState(!1);return k("div",{className:W("min-h-screen flex flex-col justify-center items-center bg-white dark:bg-slate-900",e?"p-4":"p-8"),children:L("div",{className:W("border border-red-100 dark:border-red-500/40 rounded-2xl h-full flex flex-col bg-red-50/50 dark:bg-red-500/20",e?"p-6":"p-8"),children:[L("div",{className:"flex-grow flex flex-col space-y-4 text-slate-900 dark:text-slate-100",children:[L("h1",{className:"text-2xl font-medium",children:["Your Gertrude account is ",k("strong",{children:"no longer active."})]}),!e&&k("p",{children:"The internet filter will continue protecting this computer according to the rules set before the account went inactive, but no changes or suspensions can be made until the account is restored."}),L("p",{children:["To ",k("strong",{children:"restore the account,"})," login to the Gertrude parent site and resolve the payment issue, then click the ",k("strong",{children:"Recheck"})," ","button below."]}),!e&&L("p",{children:["If you no longer wish to use Gertrude, click the ",k("strong",{children:"Disconnect"})," ","button below, then uninstall the app."]}),L("p",{children:["Contact us at at"," ",k("a",{href:"https://gertrude.app/contact",className:"font-semibold text-slate-800 dark:text-slate-200 border-b-2 pb-1 border-transparent dark:border-transparent hover:border-slate-600 dark:hover:border-slate-100 hover:pb-0.5 duration-200 transition-[padding-bottom,border-color]",children:"https://gertrude.app/contact"})," ","to get help."]})]}),L("div",{className:"flex items-center mt-6 space-x-4 bg-white dark:bg-slate-900 p-4 rounded-xl self-stretch justify-between border border-red-100 dark:border-red-500/50",children:[L(q,{type:"button",onClick:()=>{l(!0),setTimeout(()=>l(!1),3e3),t()},color:"secondary",disabled:r,children:[k("i",{className:W("fa-solid fa-sync mr-3",r&&"animate-spin")}),r?"Rechecking...":"Recheck"]}),L(q,{type:"button",onClick:()=>{o(!0),setTimeout(()=>o(!1),6e3),n()},color:"warning",disabled:i,children:[k("i",{className:W("fa-solid mr-3",i?"fa-sync animate-spin":"fa-plug")}),i?"Disconnecting...":"Disconnect"]})]})]})})},Gh=({screen:e,setScreen:t})=>{const[n,r]=y.useState(0);return L("nav",{className:W("border-slate-200 dark:border-slate-800 border-r p-2 font-bold flex flex-col items-stretch space-y-1 bg-white dark:bg-slate-900 fixed h-full z-20 top-0"),children:[k(Xr,{isActive:e==="healthCheck",onClick:()=>t("healthCheck"),icon:"heart-pulse"}),k(Xr,{isActive:e==="actions",onClick:()=>t("actions"),icon:"arrow-pointer"}),k(Xr,{isActive:e==="exemptUsers",onClick:()=>t("exemptUsers"),icon:"users"}),k(Xr,{isActive:e==="advanced",onClick:()=>{n<10?r(n+1):t("advanced")},icon:"flask",className:n<10?"cursor-default opacity-0":""})]})},Xr=({onClick:e,isActive:t,className:n,icon:r})=>k("button",{onClick:e,className:W("transition-colors duration-100 w-12 h-12 flex justify-center items-center rounded-lg",t?"bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400":"text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800/50 hover:text-slate-600 dark:hover:text-slate-400",n),children:k("i",{className:`fa-solid fa-${r} text-2xl`})}),Yh=({filterRunning:e,installedAppVersion:t,availableAppUpdate:n,dangerZoneModal:r,userName:l,emit:i,quitting:o,releaseChannel:u,dispatch:s})=>{const a=Xh(n);return L("div",{className:"p-4 h-full flex flex-col justify-between relative",children:[k("div",{className:W("w-full h-full left-0 top-0 bg-slate-100 dark:bg-slate-900 z-10 transition-[backdrop-filter,background-color] duration-300 flex justify-center items-center fixed",r!=="hidden"?"bg-opacity-30 dark:bg-opacity-90 dark:backdrop-blur-sm backdrop-blur-md pointer-events-auto":"bg-opacity-0 dark:bg-opacity-0 backdrop-blur-none pointer-events-none"),onClick:()=>s({type:"dangerZoneModalDismissed"}),children:L("div",{className:W("bg-white dark:bg-slate-800 shadow-lg shadow-slate-300/50 dark:shadow-black/20 rounded-2xl transition-[transform,opacity] duration-300",r!=="hidden"?"pointer-events-auto":"pointer-events-none scale-75 opacity-0"),onClick:p=>p.stopPropagation(),children:[L("div",{className:"p-8",children:[L("h3",{className:"font-bold text-xl text-slate-900 dark:text-slate-200 ",children:["Are you sure you want to",r==="quitApp"?" quit Gertrude?":" stop the filter?"]}),k("p",{className:"max-w-md text-sm text-slate-500 dark:text-slate-400 mt-4",children:r==="quitApp"?"Quitting the app stops all screenshot and keystroke monitoring. This is usually only necessary when uninstalling or troubleshooting.":'Stopping the filter gives all users on this computer unrestricted internet access. If you want to temporarily suspend the filter, use the "Suspend filter" button in the main menubar dropdown instead.'})]}),L("div",{className:"p-4 bg-slate-50 dark:bg-slate-900/50 rounded-b-2xl flex justify-between space-x-4",children:[k(q,{type:"button",onClick:()=>s({type:"dangerZoneModalDismissed"}),color:"tertiary",className:"flex-grow",children:"Cancel"}),k(q,{type:"button",onClick:()=>i({case:r==="quitApp"?"confirmQuitAppClicked":"confirmStopFilterClicked"}),color:"warning",className:"flex-grow",disabled:r==="quitApp"&&o,children:o?"Quitting...":`I understand, ${r==="quitApp"?"quit the app":"stop the filter"}`})]})]})}),L("div",{className:"flex flex-col flex-grow",children:[L("div",{className:"border border-slate-200 dark:border-slate-800 rounded-2xl relative flex flex-col justify-between mb-3.5",children:[k(Qh,{type:a.badgeColor,className:"absolute right-2 top-2 w-36 !max-w-none",children:a.badgeText}),L("div",{className:"p-4 pt-3",children:[L("h2",{className:"text-lg font-semibold text-slate-600 dark:text-slate-300",children:["Currently running Gertrude version"," ",k("span",{className:"font-bold *font-mono text-violet-700 dark:text-violet-400",children:t})]}),k("p",{className:"text-slate-500 mt-2",children:a.versionMessage})]}),L("div",{className:"p-3 bg-slate-50 dark:bg-slate-800/50 rounded-b-2xl flex justify-end items-center",children:[L("p",{className:W("text-slate-500 opacity-80 italic ml-3 flex-grow",u==="stable"&&"hidden"),children:[k("i",{className:"fas fa-flask mr-2"}),"Release channel: ",k("b",{children:u})]}),L(q,{type:"button",size:"small",onClick:()=>i({case:"updateAppNowClicked"}),color:"tertiary",disabled:!n,children:[k("i",{className:"fas fa-sync-alt mr-2"}),"Update now"]})]})]}),l!==void 0&&L("div",{className:"border border-slate-200 dark:border-slate-800 rounded-2xl relative flex flex-col justify-between",children:[L("div",{className:"p-4 pt-3",children:[L("h2",{className:"text-lg font-semibold text-slate-600 dark:text-slate-300",children:["Connected to child:"," ",k("span",{className:"font-bold *font-mono text-violet-700 dark:text-violet-400",children:l})]}),k("p",{className:"text-slate-500 mt-2",children:"Disconnect if you want to connect a different child."})]}),k("div",{className:"p-3 bg-slate-50 dark:bg-slate-800/50 rounded-b-2xl flex justify-end",children:L(q,{type:"button",size:"small",onClick:()=>i({case:"disconnectUserClicked"}),color:"tertiary",children:[k("i",{className:"fa fa-scissors mr-2"}),"Disconnect"]})})]})]}),L("div",{className:"border border-red-200/70 dark:border-red-500/20 rounded-2xl bg-red-50/20 dark:bg-red-500/5",children:[L("div",{className:"p-4 pt-3",children:[k("h2",{className:"text-xl font-bold text-red-700 dark:text-red-400",children:"Danger zone"}),k("p",{className:"mt-2 text-red-700/70 dark:text-red-400/50",children:"These actions disable Gertrude‘s protections. Use only for troubleshooting or uninstalling."})]}),L("div",{className:"flex justify-end space-x-4 bg-red-50/30 dark:bg-red-500/5 rounded-b-2xl p-3",children:[e&&k(q,{type:"button",size:"small",onClick:()=>s({type:"dangerZoneStopFilterClicked"}),color:"warning",children:"Stop filter"}),k(q,{type:"button",size:"small",onClick:()=>s({type:"dangerZoneQuitAppClicked"}),color:"warning",children:"Quit app"})]})]})]})};function Xh(e){return e?e.required?{badgeColor:"red",badgeText:"Update required",versionMessage:L(ma,{children:["Update to required version ",k("b",{children:e.semver})," as soon as possible."]})}:{badgeColor:"yellow",badgeText:"Update available",versionMessage:`Gertrude version ${e.semver} is available for download.`}:{badgeColor:"green",badgeText:"Up to date",versionMessage:"We'll let you know when the next update is available."}}const Zh=e=>{let t,n;switch(e.state){case"ok":t="bg-green-500",n="fa-solid fa-check translate-x-[0.5px]";break;case"fail":t="bg-red-500",n="fa-solid fa-times translate-x-[0.5px]";break;case"warn":t="bg-yellow-400",n="fa-solid fa-minus";break;case"checking":t="bg-purple-500 dark:bg-purple-800",n="fa-solid fa-sync animate-spin dark:text-slate-100";break;case"unexpected":t="bg-gray-500/90",n="fa-solid fa-exclamation translate-x-[0.5px]";break}const r=Jh(e),l=qh(e);return L("div",{className:"flex items-center p-2 rounded-xl bg-slate-50 dark:bg-slate-800/30",children:[k("div",{className:W("w-6 h-6 rounded-full flex justify-center items-center",t),children:k("i",{className:W("text-white dark:text-slate-900",n)})}),L("div",{className:"flex-grow ml-4",children:[k("h3",{className:"font-medium text-slate-800 dark:text-slate-200",children:e.title}),l&&k("p",{className:"text-slate-500 dark:text-slate-400",dangerouslySetInnerHTML:{__html:l}})]}),r&&L(q,{type:"button",onClick:()=>e.emit(r.action),color:"tertiary",size:"small",children:[k("i",{className:`fa-solid fa-${r.icon} mr-2`}),r.label]})]})};function Jh(e){if(e.state==="warn"||e.state==="fail")return e.button}function qh(e){switch(e.state){case"ok":return e.message;case"fail":return e.message;case"warn":return e.message;case"unexpected":return e.message??"Unexpected check error, please try again";default:return}}function em(e){return Object.entries(e)}function tm(e){return e!=null}class nm{constructor(t,n,r,l){this.data=t,this.installedAppVersion=n,this.screenshotMonitoringEnabled=r,this.keystrokeMonitoringEnabled=l}get items(){return[this.appVersion,...this.filterItems,this.screenRecordingPermission,this.keystrokeRecordingPermission,this.notificationsPermission,this.macOsUserType,this.accountStatus].filter(tm)}get failingChecksCount(){return this.items.filter(({state:t})=>t==="fail").length}get isChecking(){return this.items.some(({state:t})=>t==="checking")}get appVersion(){const{latestAppVersion:t}=this.data;return t===void 0?{title:"App Version",state:"checking"}:Kr(t)?{title:"App Version",state:"unexpected",message:g0(t)}:this.installedAppVersion===t.value||this.installedAppVersion>t.value?{title:"App Version",state:"ok",message:`You're up to date (${this.installedAppVersion})`}:{title:"App Version",state:"warn",message:`Update available (${t.value})`,button:{icon:"sync",label:"Update",action:"upgradeAppClicked"}}}get screenRecordingPermission(){if(this.screenshotMonitoringEnabled)return this.data.screenRecordingPermissionOk===void 0?{title:"Screen recording permission",state:"checking"}:this.data.screenRecordingPermissionOk?{title:"Screen recording permission",state:"ok"}:{title:"Screen recording permission",state:"fail",message:"Gertrude can't take screenshots until you give permission",button:{icon:"cog",label:"Fix permission",action:"fixScreenRecordingPermissionClicked"}}}get keystrokeRecordingPermission(){if(this.keystrokeMonitoringEnabled)return this.data.keystrokeRecordingPermissionOk===void 0?{title:"Keystroke recording permission",state:"checking"}:this.data.keystrokeRecordingPermissionOk?{title:"Keystroke recording permission",state:"ok"}:{title:"Keystroke recording permission",state:"fail",message:"Gertrude can't monitor keystrokes until you give permission",button:{icon:"cog",label:"Fix permission",action:"fixKeystrokeRecordingPermissionClicked"}}}get macOsUserType(){return this.data.macOsUserType===void 0?{title:"macOS user account type",state:"checking"}:Kr(this.data.macOsUserType)?{title:"macOS user account type",state:"unexpected"}:this.data.macOsUserType.value!=="standard"?{title:"Mac user has admin privileges",state:"fail",message:"Admin users can disable Gertrude if they have the password",button:{icon:"user",label:"Remove admin privilege",action:"removeUserAdminPrivilegeClicked"}}:{title:"macOS user account type",state:"ok"}}get notificationsPermission(){switch(this.data.notificationsSetting){case"alert":return{title:"Notification settings",state:"ok"};case"banner":return{title:"Notification settings",state:"warn",message:'Set to "banner", recommended setting is "alert"',button:{icon:"cog",label:"Fix setting",action:"fixNotificationPermissionClicked"}};case"none":return{title:"Notification settings",state:"fail",message:"Notifications disabled, child will miss critical updates",button:{icon:"cog",label:"Fix setting",action:"fixNotificationPermissionClicked"}};default:return{title:"Notification settings",state:"checking"}}}get accountStatus(){if(this.data.accountStatus===void 0)return{title:"Gertrude account status",state:"checking"};if(Kr(this.data.accountStatus))return{title:"Gertrude account status",state:"unexpected"};switch(this.data.accountStatus.value){case"active":return{title:"Gertrude account status",state:"ok"};case"needsAttention":return{title:"Gertrude account status",state:"warn",message:"Needs attention: log in to the Gertrude parents website for more details"};default:return{title:"Gertrude account status",state:"fail",message:"Log in to the Gertrude parents website to resolve"}}}get filterItems(){const{filterStatus:t,latestAppVersion:n}=this.data;if(t===void 0||n===void 0||t.case==="installing"||t.case==="communicationBroken"&&t.repairing)return[{title:"Filter status",state:"checking"}];if(t.case==="disabled")return[{title:"Filter status",state:"warn",message:"Filter has been disabled",button:{icon:"cog",label:"Enable filter",action:"enableFilterClicked"}}];if(t.case==="notInstalled")return[{title:"Filter status",state:"warn",message:"Filter has not been installed",button:{icon:"cog",label:"Install filter",action:"installFilterClicked"}}];if(t.case==="installTimeout")return[{title:"Filter status",state:"unexpected",message:"Installation did not complete, try again"}];if(t.case==="unexpected")return[{title:"Filter status",state:"unexpected",message:"Unexpected error: try rebooting the computer"}];if(t.case==="communicationBroken")return[{title:"Filter to app communication broken",state:"fail",message:"If repair and recheck fails, restart the computer to resolve",button:{icon:"sync",label:"Attempt repair",action:"repairFilterCommunicationClicked"}}];const r=[],{version:l,numUserKeys:i}=t;return this.shouldShowFilterOutOfDateItem(n,l)&&r.push({title:"Filter version",state:"fail",message:`Filter version out of date (${l})`,button:{icon:"sync",label:"Reinstall filter",action:"repairOutOfDateFilterClicked"}}),i>0?r.push({title:"Filter rules",state:"ok",message:`Looks good, ${i} keys loaded`}):r.push({title:"Filter rules",state:"warn",message:"No keys loaded, try refreshing rules",button:{icon:"sync",label:"Refresh rules",action:"zeroKeysRefreshRulesClicked"}}),r}shouldShowFilterOutOfDateItem(t,n){return Kr(t)?n!==this.installedAppVersion:this.installedAppVersion!==t.value?!1:n!==this.installedAppVersion}}const rm=({installedAppVersion:e,screenshotMonitoringEnabled:t,keystrokeMonitoringEnabled:n,emit:r,...l})=>{const i=new nm(l,e,t,n);return L("div",{className:"h-full overflow-y-auto relative",children:[L("header",{className:"flex items-center justify-between border-b p-4 border-slate-200 dark:border-slate-800 sticky bg-white dark:bg-slate-900 top-0 z-10",children:[L("div",{children:[k("h2",{className:"text-2xl font-bold text-slate-800 dark:text-slate-100",children:"Health check"}),k("span",{className:W("text-slate-600 dark:text-slate-400",i.isChecking&&"italic opacity-50"),children:i.isChecking?"Checking...":i.failingChecksCount?`${i.failingChecksCount} ${B0("failing check",i.failingChecksCount)}!`:"Everything looks good!"})]}),L(q,{type:"button",onClick:()=>r("recheckClicked"),disabled:i.isChecking,color:"secondary",size:"small",children:[k("i",{className:W("fa-solid fa-sync mr-2",i.isChecking&&"animate-spin")}),i.isChecking?"Checking...":"Recheck"]})]}),k("ul",{className:"flex flex-col space-y-2 p-4",children:i.items.map(o=>k(Zh,{...o,emit:r},o.title))})]})},lm=({title:e,children:t,button:n,className:r})=>L("div",{className:W("flex flex-col items-center justify-center p-4 rounded-2xl border border-red-200 dark:border-red-700/50 bg-red-50/30 dark:bg-red-600/5 flex-grow",r),children:[k("span",{className:"font-bold text-lg text-slate-700 dark:text-white/80 mb-2",children:e}),t&&k("span",{className:"text-red-500 mb-4 dark:text-red-400",children:t}),n&&L(q,{type:"button",onClick:n.action,color:"warning",size:"small",children:[k("i",{className:`fa-solid ${n.icon} mr-2`}),n.text]})]}),im=({users:e,emit:t})=>e?e.case==="error"?k("div",{className:"p-6",children:k(lm,{title:"Unexpected error",button:{text:"Check health",icon:"fa-heart-pulse",action:()=>t({case:"gotoScreenClicked",screen:"healthCheck"})},children:"Check health, or contact support if the problem persists."})}):L("div",{className:"flex flex-col h-full",children:[k("header",{className:"flex items-center justify-between border-b p-4 border-slate-200 sticky bg-white dark:border-slate-800 dark:bg-slate-900 top-0",children:k("h2",{className:"text-2xl font-bold text-slate-800 dark:text-slate-100",children:"Exempt users"})}),L("main",{className:"p-4 flex-grow flex flex-col relative",children:[L("div",{className:"mr-4",children:[L("p",{className:"text-slate-500 dark:text-slate-400",children:["Gertrude’s network filter has to make decisions about whether to allow or deny network requests from every user on this computer. For maximum internet safety, it defaults to blocking all requests for users that it doesn’t have rules for. If this computer has another user or users who should have unrestricted internet access (like a parent’s admin account on a shared computer), you can make that user"," ",k("strong",{className:"text-slate-700 dark:text-slate-200",children:"exempt from filtering"})," ","by selecting the user name below."]}),L("p",{className:"text-slate-500 dark:text-slate-400 mt-4",children:[k("strong",{className:"text-slate-700 dark:text-slate-200",children:"Please note:"})," ","any user that is exempt from filtering should have a password enabled that is unknown to any individual subject to filtering, or else they would be able to log in to that user at any time and also have unrestricted internet access."]})]}),k("ul",{className:"mt-4 space-y-2 flex-grow",children:e.value.map(n=>k(om,{name:n.name,isExempt:n.isExempt,onToggle:()=>t({case:"setUserExemption",userId:n.id,enabled:!n.isExempt})},n.id))}),k("div",{className:"flex justify-end mt-4",children:L(q,{type:"button",onClick:()=>t({case:"administrateOSUserAccountsClicked"}),color:"secondary",size:"medium",className:"",children:["Administrate user accounts",k("i",{className:"fa-solid fa-arrow-right ml-2"})]})})]})]}):null,om=({name:e,isExempt:t,onToggle:n})=>L("div",{onClick:n,className:W("flex items-center justify-start rounded-xl p-2 pl-4",t&&"bg-red-50 dark:bg-red-500/10"),children:[k("button",{className:W("w-5 h-5 rounded-full border-slate-300 dark:border-slate-700 border mr-4 flex justify-center items-center hover:scale-105 transition-[transform,border-color,border,background-color] duration-100",t&&"bg-red-500 !border-red-500 dark:border-red-500"),children:k("i",{className:"fa-solid fa-check text-white dark:text-slate-900 text-xs"})}),L("div",{className:"flex items-center space-x-2 grow",children:[k("h3",{className:"font-bold dark:text-white grow",children:e}),k("span",{className:"text-red-500 dark:text-red-400 pr-2",children:t?"exempt from filtering - unrestricted internet access":""})]})]}),um=({emit:e,pairqlEndpointDefault:t,pairqlEndpointOverride:n,websocketEndpointDefault:r,websocketEndpointOverride:l,appcastEndpointDefault:i,appcastEndpointOverride:o,appVersions:u})=>{const[s,a]=y.useState(n??""),[p,f]=y.useState(l??""),[m,g]=y.useState(o??""),[v,x]=y.useState(""),N=u?em(u).map(([d,c])=>({display:c,value:d})).sort((d,c)=>c.value.localeCompare(d.value)):null;return L("div",{className:"flex flex-col items-stretch h-full p-6 space-y-5",children:[L("div",{className:"pb-8 flex justify-between items-start",children:[N?L("div",{className:"flex space-x-2 items-end",children:[k(q,{size:"small",disabled:v==="",onClick:()=>e({case:"forceUpdateToSpecificVersionClicked",version:v}),type:"button",color:"tertiary",children:"Force update to:"}),k(Wh,{size:"small",options:[{display:"choose version...",value:""},...N],selectedOption:v,setSelected:x})]}):k("div",{className:"text-gray-400 italic",children:"Loading app versions..."}),k(q,{className:"h-12",onClick:()=>e({case:"deleteAllDeviceStorageClicked"}),type:"button",color:"warning",children:"Purge all device storage"})]}),L("div",{className:"flex items-end gap-x-2",children:[k(_i,{label:"API PairQL endpoint override:",type:"url",value:s,placeholder:t,setValue:a}),k(q,{className:"h-12",disabled:s.trim()==="",onClick:()=>e({case:"pairqlEndpointSet",url:s.trim()}),type:"button",color:"secondary",children:"Set"}),k(q,{className:"h-12",disabled:n===void 0,onClick:()=>e({case:"pairqlEndpointSet",url:void 0}),type:"button",color:"secondary",children:"Clear"})]}),L("div",{className:"flex items-end gap-x-2",children:[k(_i,{label:"Websocket endpoint override:",type:"url",value:p,placeholder:r,setValue:f}),k(q,{className:"h-12",disabled:p.trim()==="",onClick:()=>e({case:"websocketEndpointSet",url:p.trim()}),type:"button",color:"secondary",children:"Set"}),k(q,{className:"h-12",disabled:l===void 0,onClick:()=>e({case:"websocketEndpointSet",url:void 0}),type:"button",color:"secondary",children:"Clear"})]}),L("div",{className:"flex items-end gap-x-2",children:[k(_i,{label:"Sparkle Appcast endpoint override:",type:"url",value:m,placeholder:i,setValue:g}),k(q,{className:"h-12",disabled:m.trim()==="",onClick:()=>e({case:"appcastEndpointSet",url:m.trim()}),type:"button",color:"secondary",children:"Set"}),k(q,{className:"h-12",disabled:o===void 0,onClick:()=>e({case:"appcastEndpointSet",url:void 0}),type:"button",color:"secondary",children:"Clear"})]})]})};class sm extends y0{appState(){return{windowOpen:!0,screen:"healthCheck",filterState:{case:"off"},installedAppVersion:"0.0.0",healthCheck:{},releaseChannel:"stable",quitting:!1}}viewState(){return{filterSuspensionDurationInSeconds:String(60*5),dangerZoneModal:"hidden"}}initializer(){return{...this.appState(),...this.viewState()}}reducer(t,n){switch(n.type){case"filterSuspensionDurationInSecondsChanged":return{...t,filterSuspensionDurationInSeconds:n.value};case"receivedUpdatedAppState":return{...t,...n.appState};case"dangerZoneStopFilterClicked":return{...t,dangerZoneModal:"stopFilter"};case"dangerZoneQuitAppClicked":return{...t,dangerZoneModal:"quitApp"};case"dangerZoneModalDismissed":return{...t,dangerZoneModal:"hidden"};case"appEventEmitted":return n.event.case==="confirmStopFilterClicked"?{...t,dangerZoneModal:"hidden"}:t}}}const am=new sm,cm=({healthCheck:e,filterState:t,user:n,installedAppVersion:r,availableAppUpdate:l,releaseChannel:i,exemptableUsers:o,screen:u,advanced:s,quitting:a,dangerZoneModal:p,emit:f,dispatch:m})=>{let g;switch(u){case"healthCheck":g=k(rm,{...e,installedAppVersion:r,screenshotMonitoringEnabled:n?.keystrokeMonitoringEnabled??!1,keystrokeMonitoringEnabled:n?.screenshotMonitoringEnabled??!1,emit:v=>f({case:"healthCheck",action:v})});break;case"actions":g=k(Yh,{releaseChannel:i,emit:f,dispatch:m,filterRunning:t.case==="on",installedAppVersion:r,availableAppUpdate:l,dangerZoneModal:p,userName:n?.name,quitting:a});break;case"exemptUsers":g=k(im,{emit:f,users:o});break;case"advanced":g=s?k(um,{...s,emit:v=>f({case:"advanced",action:v})}):k(ma,{children:"Loading..."});break}return v0(e.accountStatus)==="inactive"?k(Kh,{onRecheck:()=>f({case:"inactiveAccountRecheckClicked"}),onDisconnect:()=>f({case:"inactiveAccountDisconnectAppClicked"})}):k("div",{className:"flex flex-col h-screen",children:L("div",{className:W("flex flex-grow relative"),children:[k(Gh,{screen:u,setScreen:v=>f({case:"gotoScreenClicked",screen:v})}),k("main",{className:"flex-grow bg-white dark:bg-slate-900 ml-16",children:g})]})})},dm=k0(am,cm);Di.createRoot(document.getElementById("app")).render(k(dm,{})); diff --git a/macapp/Xcode/Gertrude/WebViews/Administrate/style.css b/macapp/Xcode/Gertrude/WebViews/Administrate/style.css index 107b289b..70033ee5 100644 --- a/macapp/Xcode/Gertrude/WebViews/Administrate/style.css +++ b/macapp/Xcode/Gertrude/WebViews/Administrate/style.css @@ -1 +1 @@ -*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;font-family: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-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}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;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.-left-full{left:-100%}.-right-6{right:-1.5rem}.bottom-0{bottom:0px}.left-0{left:0px}.left-4{left:1rem}.left-full{left:100%}.right-0{right:0px}.right-1{right:.25rem}.right-1\.5{right:.375rem}.right-2{right:.5rem}.top-0{top:0px}.top-1{top:.25rem}.top-2{top:.5rem}.top-4{top:1rem}.top-8{top:2rem}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.-m-2{margin:-.5rem}.mx-3{margin-left:.75rem;margin-right:.75rem}.mx-6{margin-left:1.5rem;margin-right:1.5rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-2\.5{margin-top:.625rem;margin-bottom:.625rem}.-mt-0{margin-top:-0px}.-mt-0\.5{margin-top:-.125rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-20{margin-bottom:5rem}.mb-3{margin-bottom:.75rem}.mb-3\.5{margin-bottom:.875rem}.mb-4{margin-bottom:1rem}.mb-8{margin-bottom:2rem}.ml-16{margin-left:4rem}.ml-2{margin-left:.5rem}.ml-2\.5{margin-left:.625rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-4{margin-right:1rem}.mt-0{margin-top:0}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-20{height:5rem}.h-32{height:8rem}.h-4{height:1rem}.h-44{height:11rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-\[300px\]{height:300px}.h-full{height:100%}.h-screen{height:100vh}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-32{width:8rem}.w-36{width:9rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[200px\]{width:200px}.w-\[400px\]{width:400px}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.w-screen{width:100vw}.min-w-\[180px\]{min-width:180px}.\!max-w-none{max-width:none!important}.max-w-\[550px\]{max-width:550px}.max-w-fit{max-width:fit-content}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-shrink-0,.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.origin-top-right{transform-origin:top right}.-translate-y-1{--tw-translate-y: -.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-4{--tw-translate-x: 1rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\[0\.5px\]{--tw-translate-x: .5px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0\.5{--tw-translate-y: .125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-1{--tw-translate-y: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-px{--tw-translate-y: 1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-180{--tw-rotate: -180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-0{--tw-rotate: 0deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-50{--tw-scale-x: .5;--tw-scale-y: .5;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-75{--tw-scale-x: .75;--tw-scale-y: .75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.animate-\[loader-bounce_1\.5s_0\.1s_ease-out_infinite\]{animation:loader-bounce 1.5s .1s ease-out infinite}.animate-\[loader-bounce_1\.5s_0\.2s_ease-out_infinite\]{animation:loader-bounce 1.5s .2s ease-out infinite}@keyframes loader-bounce{0%{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:0}15%{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:0}30%{height:60px;opacity:.8;width:10px;margin-left:4px;margin-right:4px;margin-top:-4px}50%{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:38px}65%{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:38px}80%{height:60px;opacity:.8;width:10px;margin-left:4px;margin-right:4px;margin-top:0}to{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:0}}.animate-\[loader-bounce_1\.5s_0\.3s_ease-out_infinite\]{animation:loader-bounce 1.5s .3s ease-out infinite}@keyframes ping{75%,to{transform:scale(2);opacity:0}}.animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;user-select:none}.resize-none{resize:none}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-x-2{column-gap:.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-1\.5>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.375rem * var(--tw-space-x-reverse));margin-left:calc(.375rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-5>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.25rem * var(--tw-space-x-reverse));margin-left:calc(1.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.5rem * var(--tw-space-x-reverse));margin-left:calc(1.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.self-stretch{align-self:stretch}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-ellipsis,.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.rounded-2xl{border-radius:1rem}.rounded-\[30px\]{border-radius:30px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-b-2xl{border-bottom-right-radius:1rem;border-bottom-left-radius:1rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-b-xl{border-bottom-right-radius:.75rem;border-bottom-left-radius:.75rem}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-l-lg{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-l-xl{border-top-left-radius:.75rem;border-bottom-left-radius:.75rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-r-lg{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.rounded-r-xl{border-top-right-radius:.75rem;border-bottom-right-radius:.75rem}.rounded-t-xl{border-top-left-radius:.75rem;border-top-right-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-\[0\.5px\]{border-width:.5px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-0{border-left-width:0px}.border-r{border-right-width:1px}.border-r-0{border-right-width:0px}.border-t{border-top-width:1px}.border-none{border-style:none}.\!border-red-500{--tw-border-opacity: 1 !important;border-color:rgb(239 68 68 / var(--tw-border-opacity))!important}.\!border-violet-200{--tw-border-opacity: 1 !important;border-color:rgb(221 214 254 / var(--tw-border-opacity))!important}.border-blue-200{--tw-border-opacity: 1;border-color:rgb(191 219 254 / var(--tw-border-opacity))}.border-fuchsia-500{--tw-border-opacity: 1;border-color:rgb(217 70 239 / var(--tw-border-opacity))}.border-green-200{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity))}.border-indigo-500{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}.border-red-100{--tw-border-opacity: 1;border-color:rgb(254 226 226 / var(--tw-border-opacity))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity))}.border-red-200\/70{border-color:#fecacab3}.border-red-500\/20{border-color:#ef444433}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity))}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.border-slate-900{--tw-border-opacity: 1;border-color:rgb(15 23 42 / var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-violet-100{--tw-border-opacity: 1;border-color:rgb(237 233 254 / var(--tw-border-opacity))}.border-violet-700{--tw-border-opacity: 1;border-color:rgb(109 40 217 / var(--tw-border-opacity))}.border-white{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity))}.border-white\/30{border-color:#ffffff4d}.border-yellow-200{--tw-border-opacity: 1;border-color:rgb(254 240 138 / var(--tw-border-opacity))}.border-yellow-500\/20{border-color:#eab30833}.\!bg-violet-100{--tw-bg-opacity: 1 !important;background-color:rgb(237 233 254 / var(--tw-bg-opacity))!important}.bg-black\/5{background-color:#0000000d}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity))}.bg-fuchsia-500{--tw-bg-opacity: 1;background-color:rgb(217 70 239 / var(--tw-bg-opacity))}.bg-gray-500\/90{background-color:#6b7280e6}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-indigo-300\/40{background-color:#a5b4fc66}.bg-indigo-500{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.bg-purple-500{--tw-bg-opacity: 1;background-color:rgb(168 85 247 / var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity))}.bg-red-50\/20{background-color:#fef2f233}.bg-red-50\/30{background-color:#fef2f24d}.bg-red-50\/50{background-color:#fef2f280}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-red-500\/20{background-color:#ef444433}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}.bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.bg-slate-50\/30{background-color:#f8fafc4d}.bg-slate-50\/50{background-color:#f8fafc80}.bg-slate-500\/70{background-color:#64748bb3}.bg-violet-100{--tw-bg-opacity: 1;background-color:rgb(237 233 254 / var(--tw-bg-opacity))}.bg-violet-500{--tw-bg-opacity: 1;background-color:rgb(139 92 246 / var(--tw-bg-opacity))}.bg-violet-500\/90{background-color:#8b5cf6e6}.bg-violet-700{--tw-bg-opacity: 1;background-color:rgb(109 40 217 / var(--tw-bg-opacity))}.bg-violet-800{--tw-bg-opacity: 1;background-color:rgb(91 33 182 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-white\/20{background-color:#fff3}.bg-white\/30{background-color:#ffffff4d}.bg-white\/80{background-color:#fffc}.bg-white\/90{background-color:#ffffffe6}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity))}.bg-yellow-50{--tw-bg-opacity: 1;background-color:rgb(254 252 232 / var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity))}.bg-opacity-0{--tw-bg-opacity: 0}.bg-opacity-30{--tw-bg-opacity: .3}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-gradient-to-tr{background-image:linear-gradient(to top right,var(--tw-gradient-stops))}.from-\[\#ffffff00\]{--tw-gradient-from: #ffffff00 var(--tw-gradient-from-position);--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-indigo-400{--tw-gradient-from: #818cf8 var(--tw-gradient-from-position);--tw-gradient-to: rgb(129 140 248 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-indigo-500{--tw-gradient-from: #6366f1 var(--tw-gradient-from-position);--tw-gradient-to: rgb(99 102 241 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-indigo-600{--tw-gradient-from: #4f46e5 var(--tw-gradient-from-position);--tw-gradient-to: rgb(79 70 229 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-fuchsia-400{--tw-gradient-to: #e879f9 var(--tw-gradient-to-position)}.to-fuchsia-500{--tw-gradient-to: #d946ef var(--tw-gradient-to-position)}.to-fuchsia-600{--tw-gradient-to: #c026d3 var(--tw-gradient-to-position)}.bg-clip-text{background-clip:text}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-3\.5{padding:.875rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-12{padding-left:3rem;padding-right:3rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-7{padding-left:1.75rem;padding-right:1.75rem}.px-8{padding-left:2rem;padding-right:2rem}.px-\[12px\]{padding-left:12px;padding-right:12px}.px-\[14px\]{padding-left:14px;padding-right:14px}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-\[2\.5px\]{padding-top:2.5px;padding-bottom:2.5px}.py-\[2px\]{padding-top:2px;padding-bottom:2px}.pb-1{padding-bottom:.25rem}.pb-8{padding-bottom:2rem}.pb-px{padding-bottom:1px}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.font-inter{font-family:Inter,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-lato{font-family:lato,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{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-6xl{font-size:3.75rem;line-height:1}.text-\[13px\]{font-size:13px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-5{line-height:1.25rem}.leading-tight{line-height:1.25}.tracking-wider{letter-spacing:.05em}.\!text-violet-700{--tw-text-opacity: 1 !important;color:rgb(109 40 217 / var(--tw-text-opacity))!important}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-black\/10{color:#0000001a}.text-black\/20{color:#0003}.text-black\/30{color:#0000004d}.text-black\/50{color:#00000080}.text-black\/70{color:#000000b3}.text-black\/80{color:#000c}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.text-fuchsia-800{--tw-text-opacity: 1;color:rgb(134 25 143 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-600\/80{color:#4b5563cc}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity: 1;color:rgb(79 70 229 / var(--tw-text-opacity))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity))}.text-red-700\/70{color:#b91c1cb3}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-400\/80{color:#94a3b8cc}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-transparent{color:transparent}.text-violet-500{--tw-text-opacity: 1;color:rgb(139 92 246 / var(--tw-text-opacity))}.text-violet-500\/80{color:#8b5cf6cc}.text-violet-600{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity))}.text-violet-700{--tw-text-opacity: 1;color:rgb(109 40 217 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-yellow-200{--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity))}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity))}.text-opacity-90{--tw-text-opacity: .9}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/5{--tw-shadow-color: rgb(0 0 0 / .05);--tw-shadow: var(--tw-shadow-colored)}.shadow-slate-300\/30{--tw-shadow-color: rgb(203 213 225 / .3);--tw-shadow: var(--tw-shadow-colored)}.shadow-slate-300\/50{--tw-shadow-color: rgb(203 213 225 / .5);--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-slate-200{--tw-ring-opacity: 1;--tw-ring-color: rgb(226 232 240 / var(--tw-ring-opacity))}.ring-transparent{--tw-ring-color: transparent}.ring-violet-500{--tw-ring-opacity: 1;--tw-ring-color: rgb(139 92 246 / var(--tw-ring-opacity))}.ring-offset-0{--tw-ring-offset-width: 0px}.ring-offset-violet-500{--tw-ring-offset-color: #8b5cf6}.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-md{--tw-backdrop-blur: blur(12px);-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: blur(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-blur-xl{--tw-backdrop-blur: blur(24px);-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,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[backdrop-filter\,background-color\]{transition-property:background-color,-webkit-backdrop-filter;transition-property:backdrop-filter,background-color;transition-property:backdrop-filter,background-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,box-shadow\,transform\]{transition-property:background-color,box-shadow,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,box-shadow\]{transition-property:background-color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,opacity\,transform\]{transition-property:background-color,opacity,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,transform\,box-shadow\]{transition-property:background-color,transform,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\]{transition-property:background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[border-color\,background-color\]{transition-property:border-color,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[border-color\,ring-color\]{transition-property:border-color,ring-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[box-shadow\,transform\,background-color\]{transition-property:box-shadow,transform,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[color\,transform\,opacity\]{transition-property:color,transform,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[height\]{transition-property:height;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[left\,opacity\]{transition-property:left,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[opacity\,display\,background-color\]{transition-property:opacity,display,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[opacity\,transform\]{transition-property:opacity,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[outline\,background-color\]{transition-property:outline,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[padding-bottom\,border-color\]{transition-property:padding-bottom,border-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[transform\,background-color\,box-shadow\]{transition-property:transform,background-color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[transform\,border-color\,border\,background-color\]{transition-property:transform,border-color,border,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[transform\,opacity\]{transition-property:transform,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-75{transition-duration:75ms}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.\[-webkit-background-clip\:text\;\],.\[-webkit-background-clip\:text\]{-webkit-background-clip:text}*{touch-callout:none;user-select:none;-webkit-touch-callout:none;-webkit-user-select:none}input,textarea{touch-callout:default;user-select:auto;-webkit-touch-callout:default;-webkit-user-select:auto}.dark *::-webkit-scrollbar,body.dark::-webkit-scrollbar{width:12px}.dark *::-webkit-scrollbar-track,body.dark::-webkit-scrollbar-track{background-color:#0f172a}.dark *::-webkit-scrollbar-thumb,body.dark::-webkit-scrollbar-thumb{background-color:#1e293b;border-radius:6px}.dark *::-webkit-scrollbar-thumb:hover,body.dark::-webkit-scrollbar-thumb{background-color:#334155}.placeholder\:text-slate-400::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.placeholder\:text-slate-400\/90::placeholder{color:#94a3b8e6}.placeholder\:antialiased::placeholder{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.before\:\[content\:\'-_\'\]:before{content:"- "}.after\:\[content\:\'_-\'\]:after{content:" -"}.even\:bg-slate-50:nth-child(even){--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.even\:text-slate-50:nth-child(even){--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity))}.hover\:-translate-y-0:hover{--tw-translate-y: -0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:-translate-y-0\.5:hover{--tw-translate-y: -.125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:-translate-y-\[1px\]:hover{--tw-translate-y: -1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-slate-400:hover{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity))}.hover\:border-slate-600:hover{--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}.hover\:border-violet-200:hover{--tw-border-opacity: 1;border-color:rgb(221 214 254 / var(--tw-border-opacity))}.hover\:border-violet-50:hover{--tw-border-opacity: 1;border-color:rgb(245 243 255 / var(--tw-border-opacity))}.hover\:border-violet-800:hover{--tw-border-opacity: 1;border-color:rgb(91 33 182 / var(--tw-border-opacity))}.hover\:\!bg-violet-200:hover{--tw-bg-opacity: 1 !important;background-color:rgb(221 214 254 / var(--tw-bg-opacity))!important}.hover\:bg-red-100:hover{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity))}.hover\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.hover\:bg-slate-200:hover{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}.hover\:bg-slate-300:hover{--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity))}.hover\:bg-slate-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.hover\:bg-slate-600\/70:hover{background-color:#475569b3}.hover\:bg-violet-200:hover{--tw-bg-opacity: 1;background-color:rgb(221 214 254 / var(--tw-bg-opacity))}.hover\:bg-violet-400:hover{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity))}.hover\:bg-violet-50:hover{--tw-bg-opacity: 1;background-color:rgb(245 243 255 / var(--tw-bg-opacity))}.hover\:bg-violet-600\/90:hover{background-color:#7c3aede6}.hover\:bg-violet-800:hover{--tw-bg-opacity: 1;background-color:rgb(91 33 182 / var(--tw-bg-opacity))}.hover\:bg-violet-900:hover{--tw-bg-opacity: 1;background-color:rgb(76 29 149 / var(--tw-bg-opacity))}.hover\:bg-white\/100:hover{background-color:#fff}.hover\:bg-white\/20:hover{background-color:#fff3}.hover\:bg-white\/30:hover{background-color:#ffffff4d}.hover\:bg-white\/80:hover{background-color:#fffc}.hover\:from-indigo-600:hover{--tw-gradient-from: #4f46e5 var(--tw-gradient-from-position);--tw-gradient-to: rgb(79 70 229 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:to-fuchsia-600:hover{--tw-gradient-to: #c026d3 var(--tw-gradient-to-position)}.hover\:pb-0:hover{padding-bottom:0}.hover\:pb-0\.5:hover{padding-bottom:.125rem}.hover\:text-black:hover{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.hover\:text-black\/30:hover{color:#0000004d}.hover\:text-black\/40:hover{color:#0006}.hover\:text-red-700:hover{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity))}.hover\:text-slate-600:hover{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.hover\:shadow-lg:hover{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:z-10:focus{z-index:10}.focus\:border-indigo-200:focus{--tw-border-opacity: 1;border-color:rgb(199 210 254 / var(--tw-border-opacity))}.focus\:border-indigo-500:focus{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}.focus\:border-red-500:focus{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity))}.focus\:bg-slate-50:focus{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.focus\:shadow-md:focus{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-indigo-400\/50:focus{--tw-ring-color: rgb(129 140 248 / .5)}.focus\:ring-indigo-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity))}.focus\:ring-slate-200:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(226 232 240 / var(--tw-ring-opacity))}.focus\:ring-violet-300:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(196 181 253 / var(--tw-ring-opacity))}.focus\:ring-violet-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(139 92 246 / var(--tw-ring-opacity))}.focus\:ring-violet-700:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(109 40 217 / var(--tw-ring-opacity))}.focus\:ring-white:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-slate-50:focus{--tw-ring-offset-color: #f8fafc}.active\:translate-y-0:active{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-95:active{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:shadow:active{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.group:nth-child(odd) .group-odd\:bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.group:nth-child(odd) .group-odd\:to-white{--tw-gradient-to: #fff var(--tw-gradient-to-position)}.group:nth-child(even) .group-even\:bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.group:nth-child(even) .group-even\:to-slate-50{--tw-gradient-to: #f8fafc var(--tw-gradient-to-position)}.group:hover .group-hover\:bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.group:hover .group-hover\:to-slate-100{--tw-gradient-to: #f1f5f9 var(--tw-gradient-to-position)}:is(.dark .dark\:border){border-width:1px}:is(.dark .dark\:\!border-violet-600){--tw-border-opacity: 1 !important;border-color:rgb(124 58 237 / var(--tw-border-opacity))!important}:is(.dark .dark\:border-blue-500\/50){border-color:#3b82f680}:is(.dark .dark\:border-fuchsia-400){--tw-border-opacity: 1;border-color:rgb(232 121 249 / var(--tw-border-opacity))}:is(.dark .dark\:border-green-500\/50){border-color:#22c55e80}:is(.dark .dark\:border-green-500\/70){border-color:#22c55eb3}:is(.dark .dark\:border-indigo-400){--tw-border-opacity: 1;border-color:rgb(129 140 248 / var(--tw-border-opacity))}:is(.dark .dark\:border-red-500){--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity))}:is(.dark .dark\:border-red-500\/20){border-color:#ef444433}:is(.dark .dark\:border-red-500\/40){border-color:#ef444466}:is(.dark .dark\:border-red-500\/50){border-color:#ef444480}:is(.dark .dark\:border-red-500\/70){border-color:#ef4444b3}:is(.dark .dark\:border-red-600\/50){border-color:#dc262680}:is(.dark .dark\:border-red-700\/50){border-color:#b91c1c80}:is(.dark .dark\:border-slate-500){--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity))}:is(.dark .dark\:border-slate-600){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}:is(.dark .dark\:border-slate-700){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}:is(.dark .dark\:border-slate-700\/50){border-color:#33415580}:is(.dark .dark\:border-slate-700\/70){border-color:#334155b3}:is(.dark .dark\:border-slate-800){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}:is(.dark .dark\:border-transparent){border-color:transparent}:is(.dark .dark\:border-violet-700){--tw-border-opacity: 1;border-color:rgb(109 40 217 / var(--tw-border-opacity))}:is(.dark .dark\:border-white\/20){border-color:#fff3}:is(.dark .dark\:border-yellow-500\/50){border-color:#eab30880}:is(.dark .dark\:border-yellow-500\/70){border-color:#eab308b3}:is(.dark .dark\:\!bg-violet-700\/60){background-color:#6d28d999!important}:is(.dark .dark\:bg-black\/20){background-color:#0003}:is(.dark .dark\:bg-black\/50){background-color:#00000080}:is(.dark .dark\:bg-blue-500\/10){background-color:#3b82f61a}:is(.dark .dark\:bg-fuchsia-500\/20){background-color:#d946ef33}:is(.dark .dark\:bg-green-500\/10){background-color:#22c55e1a}:is(.dark .dark\:bg-green-500\/30){background-color:#22c55e4d}:is(.dark .dark\:bg-indigo-500\/20){background-color:#6366f133}:is(.dark .dark\:bg-indigo-600\/50){background-color:#4f46e580}:is(.dark .dark\:bg-purple-800){--tw-bg-opacity: 1;background-color:rgb(107 33 168 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-red-500\/10){background-color:#ef44441a}:is(.dark .dark\:bg-red-500\/20){background-color:#ef444433}:is(.dark .dark\:bg-red-500\/30){background-color:#ef44444d}:is(.dark .dark\:bg-red-500\/5){background-color:#ef44440d}:is(.dark .dark\:bg-red-600\/5){background-color:#dc26260d}:is(.dark .dark\:bg-slate-200){--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-slate-700){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-slate-700\/20){background-color:#33415533}:is(.dark .dark\:bg-slate-700\/50){background-color:#33415580}:is(.dark .dark\:bg-slate-800){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-slate-800\/30){background-color:#1e293b4d}:is(.dark .dark\:bg-slate-800\/50){background-color:#1e293b80}:is(.dark .dark\:bg-slate-800\/60){background-color:#1e293b99}:is(.dark .dark\:bg-slate-800\/80){background-color:#1e293bcc}:is(.dark .dark\:bg-slate-900){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-slate-900\/50){background-color:#0f172a80}:is(.dark .dark\:bg-violet-600){--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-violet-700){--tw-bg-opacity: 1;background-color:rgb(109 40 217 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-violet-700\/90){background-color:#6d28d9e6}:is(.dark .dark\:bg-white\/20){background-color:#fff3}:is(.dark .dark\:bg-white\/5){background-color:#ffffff0d}:is(.dark .dark\:bg-white\/80){background-color:#fffc}:is(.dark .dark\:bg-yellow-500\/10){background-color:#eab3081a}:is(.dark .dark\:bg-yellow-500\/30){background-color:#eab3084d}:is(.dark .dark\:bg-opacity-0){--tw-bg-opacity: 0}:is(.dark .dark\:bg-opacity-90){--tw-bg-opacity: .9}:is(.dark .dark\:from-\[\#0f172a00\]){--tw-gradient-from: #0f172a00 var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}:is(.dark .dark\:\!text-violet-100){--tw-text-opacity: 1 !important;color:rgb(237 233 254 / var(--tw-text-opacity))!important}:is(.dark .dark\:text-blue-300){--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity))}:is(.dark .dark\:text-fuchsia-200){--tw-text-opacity: 1;color:rgb(245 208 254 / var(--tw-text-opacity))}:is(.dark .dark\:text-green-300){--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity))}:is(.dark .dark\:text-indigo-200){--tw-text-opacity: 1;color:rgb(199 210 254 / var(--tw-text-opacity))}:is(.dark .dark\:text-indigo-300){--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-100){--tw-text-opacity: 1;color:rgb(254 226 226 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-200){--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-300){--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-400){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-400\/50){color:#f8717180}:is(.dark .dark\:text-slate-100){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-200){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-300){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-400){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-500){--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-600){--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-700){--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-900){--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}:is(.dark .dark\:text-violet-400){--tw-text-opacity: 1;color:rgb(167 139 250 / var(--tw-text-opacity))}:is(.dark .dark\:text-white){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}:is(.dark .dark\:text-white\/10){color:#ffffff1a}:is(.dark .dark\:text-white\/20){color:#fff3}:is(.dark .dark\:text-white\/30){color:#ffffff4d}:is(.dark .dark\:text-white\/50){color:#ffffff80}:is(.dark .dark\:text-white\/60){color:#fff9}:is(.dark .dark\:text-white\/70){color:#ffffffb3}:is(.dark .dark\:text-white\/80){color:#fffc}:is(.dark .dark\:text-yellow-100){--tw-text-opacity: 1;color:rgb(254 249 195 / var(--tw-text-opacity))}:is(.dark .dark\:text-yellow-200){--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity))}:is(.dark .dark\:text-yellow-300){--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity))}:is(.dark .dark\:opacity-90){opacity:.9}:is(.dark .dark\:shadow-lg){--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}:is(.dark .dark\:shadow-black\/20){--tw-shadow-color: rgb(0 0 0 / .2);--tw-shadow: var(--tw-shadow-colored)}:is(.dark .dark\:shadow-black\/30){--tw-shadow-color: rgb(0 0 0 / .3);--tw-shadow: var(--tw-shadow-colored)}:is(.dark .dark\:shadow-black\/50){--tw-shadow-color: rgb(0 0 0 / .5);--tw-shadow: var(--tw-shadow-colored)}:is(.dark .dark\:ring-offset-slate-900){--tw-ring-offset-color: #0f172a}:is(.dark .dark\:backdrop-blur-sm){--tw-backdrop-blur: blur(4px);-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)}:is(.dark .dark\:\[filter\:brightness\(600\%\)\]){filter:brightness(600%)}:is(.dark .dark\:placeholder\:text-slate-500)::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}:is(.dark .dark\:even\:bg-\[\#141A2F\]:nth-child(even)){--tw-bg-opacity: 1;background-color:rgb(20 26 47 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:border-slate-100:hover){--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:border-slate-500:hover){--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:border-violet-700:hover){--tw-border-opacity: 1;border-color:rgb(109 40 217 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:\!bg-violet-700:hover){--tw-bg-opacity: 1 !important;background-color:rgb(109 40 217 / var(--tw-bg-opacity))!important}:is(.dark .dark\:hover\:bg-red-500\/20:hover){background-color:#ef444433}:is(.dark .dark\:hover\:bg-slate-600:hover){--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-slate-700\/80:hover){background-color:#334155cc}:is(.dark .dark\:hover\:bg-slate-800:hover){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-slate-800\/50:hover){background-color:#1e293b80}:is(.dark .dark\:hover\:bg-violet-700:hover){--tw-bg-opacity: 1;background-color:rgb(109 40 217 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-violet-800\/90:hover){background-color:#5b21b6e6}:is(.dark .dark\:hover\:bg-white:hover){--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-white\/10:hover){background-color:#ffffff1a}:is(.dark .dark\:hover\:bg-white\/30:hover){background-color:#ffffff4d}:is(.dark .hover\:dark\:bg-slate-900\/80):hover{background-color:#0f172acc}:is(.dark .dark\:hover\:text-red-200:hover){--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-slate-400:hover){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-white:hover){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}:is(.dark .hover\:dark\:text-white\/30):hover{color:#ffffff4d}:is(.dark .hover\:dark\:text-white\/40):hover{color:#fff6}:is(.dark .dark\:focus\:bg-slate-800:focus){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:focus\:ring-indigo-500\/70:focus){--tw-ring-color: rgb(99 102 241 / .7)}:is(.dark .group:nth-child(odd) .dark\:group-odd\:bg-slate-900){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}:is(.dark .group:nth-child(odd) .dark\:group-odd\:to-slate-900){--tw-gradient-to: #0f172a var(--tw-gradient-to-position)}:is(.dark .group:nth-child(even) .dark\:group-even\:bg-\[\#141A2E\]){--tw-bg-opacity: 1;background-color:rgb(20 26 46 / var(--tw-bg-opacity))}:is(.dark .group:nth-child(even) .dark\:group-even\:to-\[\#141A2E\]){--tw-gradient-to: #141A2E var(--tw-gradient-to-position)}:is(.dark .group:hover .dark\:group-hover\:bg-slate-800){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .group:hover .dark\:group-hover\:to-slate-800){--tw-gradient-to: #1e293b var(--tw-gradient-to-position)}@media (min-width: 500px){.xs\:flex{display:flex}.xs\:rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}}@media (min-width: 640px){.sm\:py-2{padding-top:.5rem;padding-bottom:.5rem}.sm\:py-2\.5{padding-top:.625rem;padding-bottom:.625rem}} +*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;tab-size:4;font-family: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-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}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;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::-webkit-backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.-bottom-72{bottom:-18rem}.-bottom-80{bottom:-20rem}.-bottom-96{bottom:-24rem}.-left-6{left:-1.5rem}.-left-96{left:-24rem}.-left-full{left:-100%}.-right-6{right:-1.5rem}.-right-80{right:-20rem}.-top-12{top:-3rem}.-top-\[200vh\]{top:-200vh}.bottom-0{bottom:0px}.bottom-6{bottom:1.5rem}.left-0{left:0px}.left-4{left:1rem}.left-\[-100\%\]{left:-100%}.left-\[50vw\]{left:50vw}.left-full{left:100%}.right-0{right:0px}.right-1{right:.25rem}.right-1\.5{right:.375rem}.right-2{right:.5rem}.right-4{right:1rem}.right-6{right:1.5rem}.top-0{top:0px}.top-1{top:.25rem}.top-2{top:.5rem}.top-4{top:1rem}.top-8{top:2rem}.top-\[50\%\]{top:50%}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.-m-2{margin:-.5rem}.m-1{margin:.25rem}.m-4{margin:1rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-3{margin-left:.75rem;margin-right:.75rem}.mx-6{margin-left:1.5rem;margin-right:1.5rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-2\.5{margin-top:.625rem;margin-bottom:.625rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-8{margin-top:2rem;margin-bottom:2rem}.\!ml-0{margin-left:0!important}.-mt-0{margin-top:-0px}.-mt-0\.5{margin-top:-.125rem}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-2{margin-bottom:.5rem}.mb-20{margin-bottom:5rem}.mb-3{margin-bottom:.75rem}.mb-3\.5{margin-bottom:.875rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-16{margin-left:4rem}.ml-2{margin-left:.5rem}.ml-2\.5{margin-left:.625rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-4{margin-right:1rem}.mt-0{margin-top:0}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-152{height:38rem}.h-2{height:.5rem}.h-20{height:5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-32{height:8rem}.h-4{height:1rem}.h-44{height:11rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-\[200px\]{height:200px}.h-\[200vh\]{height:200vh}.h-\[300px\]{height:300px}.h-\[3px\]{height:3px}.h-full{height:100%}.h-screen{height:100vh}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-152{width:38rem}.w-16{width:4rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-32{width:8rem}.w-36{width:9rem}.w-4{width:1rem}.w-44{width:11rem}.w-5{width:1.25rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-\[200px\]{width:200px}.w-\[270px\]{width:270px}.w-\[400px\]{width:400px}.w-\[580px\]{width:580px}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.w-screen{width:100vw}.min-w-\[180px\]{min-width:180px}.min-w-\[280px\]{min-width:280px}.\!max-w-none{max-width:none!important}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-\[550px\]{max-width:550px}.max-w-\[730px\]{max-width:730px}.max-w-fit{max-width:fit-content}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xl{max-width:36rem}.flex-shrink-0,.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.origin-top-right{transform-origin:top right}.-translate-y-1{--tw-translate-y: -.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-16{--tw-translate-y: -4rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-20{--tw-translate-y: -5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-8{--tw-translate-y: -2rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-\[50\%\]{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-4{--tw-translate-x: 1rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\[0\.5px\]{--tw-translate-x: .5px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0\.5{--tw-translate-y: .125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-1{--tw-translate-y: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-12{--tw-translate-y: 3rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-2{--tw-translate-y: .5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-4{--tw-translate-y: 1rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-6{--tw-translate-y: 1.5rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-8{--tw-translate-y: 2rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-px{--tw-translate-y: 1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-180{--tw-rotate: -180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-0{--tw-rotate: 0deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-50{--tw-scale-x: .5;--tw-scale-y: .5;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-75{--tw-scale-x: .75;--tw-scale-y: .75;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-\[350\%\]{--tw-scale-x: 350%;--tw-scale-y: 350%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.animate-\[loader-bounce_1\.5s_0\.1s_ease-out_infinite\]{animation:loader-bounce 1.5s .1s ease-out infinite}.animate-\[loader-bounce_1\.5s_0\.2s_ease-out_infinite\]{animation:loader-bounce 1.5s .2s ease-out infinite}@keyframes loader-bounce{0%{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:0}15%{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:0}30%{height:60px;opacity:.8;width:10px;margin-left:4px;margin-right:4px;margin-top:-4px}50%{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:38px}65%{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:38px}80%{height:60px;opacity:.8;width:10px;margin-left:4px;margin-right:4px;margin-top:0}to{height:18px;opacity:1;width:18px;margin-left:0;margin-right:0;margin-top:0}}.animate-\[loader-bounce_1\.5s_0\.3s_ease-out_infinite\]{animation:loader-bounce 1.5s .3s ease-out infinite}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-bounce{animation:bounce 1s infinite}@keyframes ping{75%,to{transform:scale(2);opacity:0}}.animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-zoom-in{cursor:zoom-in}.cursor-zoom-out{cursor:zoom-out}.select-none{-webkit-user-select:none;user-select:none}.resize-none{resize:none}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-x-2{column-gap:.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.25rem * var(--tw-space-x-reverse));margin-left:calc(.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-1\.5>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.375rem * var(--tw-space-x-reverse));margin-left:calc(.375rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-12>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(3rem * var(--tw-space-x-reverse));margin-left:calc(3rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.75rem * var(--tw-space-x-reverse));margin-left:calc(.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-5>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.25rem * var(--tw-space-x-reverse));margin-left:calc(1.25rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-6>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(1.5rem * var(--tw-space-x-reverse));margin-left:calc(1.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(2rem * var(--tw-space-x-reverse));margin-left:calc(2rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.self-end{align-self:flex-end}.self-stretch{align-self:stretch}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-ellipsis,.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.\!rounded-lg{border-radius:.5rem!important}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-\[30px\]{border-radius:30px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-b-2xl{border-bottom-right-radius:1rem;border-bottom-left-radius:1rem}.rounded-b-3xl{border-bottom-right-radius:1.5rem;border-bottom-left-radius:1.5rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-b-xl{border-bottom-right-radius:.75rem;border-bottom-left-radius:.75rem}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}.rounded-l-xl{border-top-left-radius:.75rem;border-bottom-left-radius:.75rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.rounded-r-xl{border-top-right-radius:.75rem;border-bottom-right-radius:.75rem}.rounded-t-xl{border-top-left-radius:.75rem;border-top-right-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-\[0\.5px\]{border-width:.5px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-0{border-left-width:0px}.border-r{border-right-width:1px}.border-r-0{border-right-width:0px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-none{border-style:none}.\!border-red-500{--tw-border-opacity: 1 !important;border-color:rgb(239 68 68 / var(--tw-border-opacity))!important}.\!border-violet-200{--tw-border-opacity: 1 !important;border-color:rgb(221 214 254 / var(--tw-border-opacity))!important}.border-\[\#eceff7\]{--tw-border-opacity: 1;border-color:rgb(236 239 247 / var(--tw-border-opacity))}.border-blue-200{--tw-border-opacity: 1;border-color:rgb(191 219 254 / var(--tw-border-opacity))}.border-fuchsia-500{--tw-border-opacity: 1;border-color:rgb(217 70 239 / var(--tw-border-opacity))}.border-green-200{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity))}.border-indigo-500{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}.border-red-100{--tw-border-opacity: 1;border-color:rgb(254 226 226 / var(--tw-border-opacity))}.border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity))}.border-red-200\/70{border-color:#fecacab3}.border-red-500\/20{border-color:#ef444433}.border-slate-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity))}.border-slate-200\/50{border-color:#e2e8f080}.border-slate-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.border-slate-900{--tw-border-opacity: 1;border-color:rgb(15 23 42 / var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-violet-100{--tw-border-opacity: 1;border-color:rgb(237 233 254 / var(--tw-border-opacity))}.border-violet-300{--tw-border-opacity: 1;border-color:rgb(196 181 253 / var(--tw-border-opacity))}.border-violet-500{--tw-border-opacity: 1;border-color:rgb(139 92 246 / var(--tw-border-opacity))}.border-violet-700{--tw-border-opacity: 1;border-color:rgb(109 40 217 / var(--tw-border-opacity))}.border-white{--tw-border-opacity: 1;border-color:rgb(255 255 255 / var(--tw-border-opacity))}.border-white\/30{border-color:#ffffff4d}.border-yellow-200{--tw-border-opacity: 1;border-color:rgb(254 240 138 / var(--tw-border-opacity))}.border-yellow-500\/20{border-color:#eab30833}.\!bg-violet-100{--tw-bg-opacity: 1 !important;background-color:rgb(237 233 254 / var(--tw-bg-opacity))!important}.bg-\[\#eceff7\]{--tw-bg-opacity: 1;background-color:rgb(236 239 247 / var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity))}.bg-black\/5{background-color:#0000000d}.bg-black\/50{background-color:#00000080}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity))}.bg-fuchsia-50{--tw-bg-opacity: 1;background-color:rgb(253 244 255 / var(--tw-bg-opacity))}.bg-fuchsia-500{--tw-bg-opacity: 1;background-color:rgb(217 70 239 / var(--tw-bg-opacity))}.bg-gray-500\/90{background-color:#6b7280e6}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.bg-indigo-300\/40{background-color:#a5b4fc66}.bg-indigo-500{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.bg-purple-500{--tw-bg-opacity: 1;background-color:rgb(168 85 247 / var(--tw-bg-opacity))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity))}.bg-red-50\/20{background-color:#fef2f233}.bg-red-50\/30{background-color:#fef2f24d}.bg-red-50\/50{background-color:#fef2f280}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.bg-red-500\/20{background-color:#ef444433}.bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}.bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.bg-slate-50\/30{background-color:#f8fafc4d}.bg-slate-50\/50{background-color:#f8fafc80}.bg-slate-500\/70{background-color:#64748bb3}.bg-violet-100{--tw-bg-opacity: 1;background-color:rgb(237 233 254 / var(--tw-bg-opacity))}.bg-violet-200{--tw-bg-opacity: 1;background-color:rgb(221 214 254 / var(--tw-bg-opacity))}.bg-violet-300{--tw-bg-opacity: 1;background-color:rgb(196 181 253 / var(--tw-bg-opacity))}.bg-violet-500{--tw-bg-opacity: 1;background-color:rgb(139 92 246 / var(--tw-bg-opacity))}.bg-violet-500\/90{background-color:#8b5cf6e6}.bg-violet-700{--tw-bg-opacity: 1;background-color:rgb(109 40 217 / var(--tw-bg-opacity))}.bg-violet-800{--tw-bg-opacity: 1;background-color:rgb(91 33 182 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-white\/20{background-color:#fff3}.bg-white\/30{background-color:#ffffff4d}.bg-white\/50{background-color:#ffffff80}.bg-white\/80{background-color:#fffc}.bg-white\/90{background-color:#ffffffe6}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity))}.bg-yellow-50{--tw-bg-opacity: 1;background-color:rgb(254 252 232 / var(--tw-bg-opacity))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity))}.bg-opacity-0{--tw-bg-opacity: 0}.bg-opacity-30{--tw-bg-opacity: .3}.bg-gradient-to-b{background-image:linear-gradient(to bottom,var(--tw-gradient-stops))}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-gradient-to-tr{background-image:linear-gradient(to top right,var(--tw-gradient-stops))}.from-\[\#ffffff00\]{--tw-gradient-from: #ffffff00 var(--tw-gradient-from-position);--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-fuchsia-500{--tw-gradient-from: #d946ef var(--tw-gradient-from-position);--tw-gradient-to: rgb(217 70 239 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-indigo-400{--tw-gradient-from: #818cf8 var(--tw-gradient-from-position);--tw-gradient-to: rgb(129 140 248 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-indigo-500{--tw-gradient-from: #6366f1 var(--tw-gradient-from-position);--tw-gradient-to: rgb(99 102 241 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-indigo-600{--tw-gradient-from: #4f46e5 var(--tw-gradient-from-position);--tw-gradient-to: rgb(79 70 229 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-violet-500{--tw-gradient-from: #8b5cf6 var(--tw-gradient-from-position);--tw-gradient-to: rgb(139 92 246 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-white{--tw-gradient-from: #fff var(--tw-gradient-from-position);--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.via-white{--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), #fff var(--tw-gradient-via-position), var(--tw-gradient-to)}.to-fuchsia-400{--tw-gradient-to: #e879f9 var(--tw-gradient-to-position)}.to-fuchsia-500{--tw-gradient-to: #d946ef var(--tw-gradient-to-position)}.to-fuchsia-600{--tw-gradient-to: #c026d3 var(--tw-gradient-to-position)}.to-transparent{--tw-gradient-to: transparent var(--tw-gradient-to-position)}.to-violet-500{--tw-gradient-to: #8b5cf6 var(--tw-gradient-to-position)}.bg-clip-text{background-clip:text}.object-cover{object-fit:cover}.object-center{object-position:center}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-10{padding:2.5rem}.p-12{padding:3rem}.p-16{padding:4rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-3\.5{padding:.875rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.\!py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-12{padding-left:3rem;padding-right:3rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-7{padding-left:1.75rem;padding-right:1.75rem}.px-8{padding-left:2rem;padding-right:2rem}.px-\[12px\]{padding-left:12px;padding-right:12px}.px-\[14px\]{padding-left:14px;padding-right:14px}.py-0{padding-top:0;padding-bottom:0}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-\[2\.5px\]{padding-top:2.5px;padding-bottom:2.5px}.py-\[2px\]{padding-top:2px;padding-bottom:2px}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-6{padding-bottom:1.5rem}.pb-8{padding-bottom:2rem}.pb-px{padding-bottom:1px}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pt-0{padding-top:0}.pt-16{padding-top:4rem}.pt-3{padding-top:.75rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.font-inter{font-family:Inter,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-lato{font-family:lato,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{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-6xl{font-size:3.75rem;line-height:1}.text-\[13px\]{font-size:13px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-5{line-height:1.25rem}.leading-\[1\.25em\]{line-height:1.25em}.leading-tight{line-height:1.25}.tracking-\[8px\]{letter-spacing:8px}.tracking-wider{letter-spacing:.05em}.\!text-slate-600{--tw-text-opacity: 1 !important;color:rgb(71 85 105 / var(--tw-text-opacity))!important}.\!text-violet-700{--tw-text-opacity: 1 !important;color:rgb(109 40 217 / var(--tw-text-opacity))!important}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-black\/10{color:#0000001a}.text-black\/20{color:#0003}.text-black\/30{color:#0000004d}.text-black\/50{color:#00000080}.text-black\/70{color:#000000b3}.text-black\/80{color:#000c}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity))}.text-fuchsia-400{--tw-text-opacity: 1;color:rgb(232 121 249 / var(--tw-text-opacity))}.text-fuchsia-500{--tw-text-opacity: 1;color:rgb(217 70 239 / var(--tw-text-opacity))}.text-fuchsia-600{--tw-text-opacity: 1;color:rgb(192 38 211 / var(--tw-text-opacity))}.text-fuchsia-800{--tw-text-opacity: 1;color:rgb(134 25 143 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity))}.text-gray-600\/80{color:#4b5563cc}.text-green-300{--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity: 1;color:rgb(79 70 229 / var(--tw-text-opacity))}.text-pink-400{--tw-text-opacity: 1;color:rgb(244 114 182 / var(--tw-text-opacity))}.text-pink-500{--tw-text-opacity: 1;color:rgb(236 72 153 / var(--tw-text-opacity))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity))}.text-red-700\/70{color:#b91c1cb3}.text-slate-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}.text-slate-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-slate-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-slate-400\/80{color:#94a3b8cc}.text-slate-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-slate-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.text-slate-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.text-slate-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-transparent{color:transparent}.text-violet-500{--tw-text-opacity: 1;color:rgb(139 92 246 / var(--tw-text-opacity))}.text-violet-500\/80{color:#8b5cf6cc}.text-violet-600{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity))}.text-violet-700{--tw-text-opacity: 1;color:rgb(109 40 217 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-white\/70{color:#ffffffb3}.text-white\/80{color:#fffc}.text-yellow-200{--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity))}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity))}.text-opacity-90{--tw-text-opacity: .9}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-75{opacity:.75}.opacity-80{opacity:.8}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/5{--tw-shadow-color: rgb(0 0 0 / .05);--tw-shadow: var(--tw-shadow-colored)}.shadow-slate-300\/30{--tw-shadow-color: rgb(203 213 225 / .3);--tw-shadow: var(--tw-shadow-colored)}.shadow-slate-300\/50{--tw-shadow-color: rgb(203 213 225 / .5);--tw-shadow: var(--tw-shadow-colored)}.shadow-slate-500\/50{--tw-shadow-color: rgb(100 116 139 / .5);--tw-shadow: var(--tw-shadow-colored)}.shadow-violet-200\/80{--tw-shadow-color: rgb(221 214 254 / .8);--tw-shadow: var(--tw-shadow-colored)}.shadow-violet-500\/40{--tw-shadow-color: rgb(139 92 246 / .4);--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-slate-200{--tw-ring-opacity: 1;--tw-ring-color: rgb(226 232 240 / var(--tw-ring-opacity))}.ring-transparent{--tw-ring-color: transparent}.ring-violet-500{--tw-ring-opacity: 1;--tw-ring-color: rgb(139 92 246 / var(--tw-ring-opacity))}.ring-offset-0{--tw-ring-offset-width: 0px}.ring-offset-violet-500{--tw-ring-offset-color: #8b5cf6}.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-lg{--tw-backdrop-blur: blur(16px);-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(12px);-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: blur(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-blur-xl{--tw-backdrop-blur: blur(24px);-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,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[backdrop-filter\,background-color\]{transition-property:background-color,-webkit-backdrop-filter;transition-property:backdrop-filter,background-color;transition-property:backdrop-filter,background-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,border-color\,color\]{transition-property:background-color,border-color,color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,box-shadow\,transform\]{transition-property:background-color,box-shadow,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,box-shadow\]{transition-property:background-color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,opacity\,transform\]{transition-property:background-color,opacity,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,transform\,box-shadow\]{transition-property:background-color,transform,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\,transform\]{transition-property:background-color,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[background-color\]{transition-property:background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[border-color\,background-color\]{transition-property:border-color,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[border-color\,ring-color\]{transition-property:border-color,ring-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[border-color\,transform\,box-shadow\]{transition-property:border-color,transform,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[box-shadow\,transform\,background-color\]{transition-property:box-shadow,transform,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[color\,transform\,opacity\]{transition-property:color,transform,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[height\]{transition-property:height;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[left\,opacity\]{transition-property:left,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[opacity\,backdrop-filter\]{transition-property:opacity,-webkit-backdrop-filter;transition-property:opacity,backdrop-filter;transition-property:opacity,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[opacity\,display\,background-color\]{transition-property:opacity,display,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[opacity\,left\]{transition-property:opacity,left;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[opacity\,transform\]{transition-property:opacity,transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[outline\,background-color\]{transition-property:outline,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[padding-bottom\,border-color\]{transition-property:padding-bottom,border-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[top\]{transition-property:top;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[transform\,background-color\,box-shadow\]{transition-property:transform,background-color,box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[transform\,border-color\,border\,background-color\]{transition-property:transform,border-color,border,background-color;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[transform\,box-shadow\,opacity\]{transition-property:transform,box-shadow,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[transform\,box-shadow\,width\]{transition-property:transform,box-shadow,width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[transform\,opacity\]{transition-property:transform,opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\,height\]{transition-property:width,height;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\]{transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.delay-500{transition-delay:.5s}.delay-\[1\.3s\]{transition-delay:1.3s}.delay-\[1\.6s\]{transition-delay:1.6s}.delay-\[1\.9s\]{transition-delay:1.9s}.delay-\[1s\]{transition-delay:1s}.delay-\[3\.5s\]{transition-delay:3.5s}.delay-\[4s\]{transition-delay:4s}.duration-100{transition-duration:.1s}.duration-1000{transition-duration:1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-700{transition-duration:.7s}.duration-75{transition-duration:75ms}.duration-\[1s\]{transition-duration:1s}.duration-\[250ms\]{transition-duration:.25s}.duration-\[2s\]{transition-duration:2s}.duration-\[5s\]{transition-duration:5s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\[-webkit-background-clip\:text\;\],.\[-webkit-background-clip\:text\]{-webkit-background-clip:text}.\[backface-visibility\:hidden\]{-webkit-backface-visibility:hidden;backface-visibility:hidden}.\[background\:radial-gradient\(\#8b5cf656_0\%\,transparent_70\%\,transparent\)\]{background:radial-gradient(#8b5cf656 0%,transparent 70%,transparent)}.\[background\:radial-gradient\(\#d946ef56_0\%\,transparent_70\%\,transparent\)\]{background:radial-gradient(#d946ef56 0%,transparent 70%,transparent)}.\[perspective\:400px\]{perspective:400px}.\[transform-style\:preserve-3d\]{transform-style:preserve-3d}.\[transform\:rotateY\(-180deg\)\]{transform:rotateY(-180deg)}.\[transform\:rotateY\(180deg\)\]{transform:rotateY(180deg)}*{touch-callout:none;user-select:none;-webkit-touch-callout:none;-webkit-user-select:none}input,textarea{touch-callout:default;user-select:auto;-webkit-touch-callout:default;-webkit-user-select:auto}.dark *::-webkit-scrollbar,body.dark::-webkit-scrollbar{width:12px}.dark *::-webkit-scrollbar-track,body.dark::-webkit-scrollbar-track{background-color:#0f172a}.dark *::-webkit-scrollbar-thumb,body.dark::-webkit-scrollbar-thumb{background-color:#1e293b;border-radius:6px}.dark *::-webkit-scrollbar-thumb:hover,body.dark::-webkit-scrollbar-thumb{background-color:#334155}.placeholder\:text-slate-200::placeholder{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}.placeholder\:text-slate-400::placeholder{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.placeholder\:text-slate-400\/90::placeholder{color:#94a3b8e6}.placeholder\:antialiased::placeholder{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.before\:\[content\:\'-_\'\]:before{content:"- "}.after\:\[content\:\'_-\'\]:after{content:" -"}.even\:bg-slate-50:nth-child(even){--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.even\:text-slate-50:nth-child(even){--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity))}.hover\:-translate-y-0:hover{--tw-translate-y: -0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:-translate-y-0\.5:hover{--tw-translate-y: -.125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:-translate-y-\[1px\]:hover{--tw-translate-y: -1px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-\[102\%\]:hover{--tw-scale-x: 102%;--tw-scale-y: 102%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-slate-400:hover{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity))}.hover\:border-slate-600:hover{--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}.hover\:border-violet-200:hover{--tw-border-opacity: 1;border-color:rgb(221 214 254 / var(--tw-border-opacity))}.hover\:border-violet-50:hover{--tw-border-opacity: 1;border-color:rgb(245 243 255 / var(--tw-border-opacity))}.hover\:border-violet-800:hover{--tw-border-opacity: 1;border-color:rgb(91 33 182 / var(--tw-border-opacity))}.hover\:\!bg-violet-200:hover{--tw-bg-opacity: 1 !important;background-color:rgb(221 214 254 / var(--tw-bg-opacity))!important}.hover\:bg-red-100:hover{--tw-bg-opacity: 1;background-color:rgb(254 226 226 / var(--tw-bg-opacity))}.hover\:bg-slate-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.hover\:bg-slate-200:hover{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}.hover\:bg-slate-300:hover{--tw-bg-opacity: 1;background-color:rgb(203 213 225 / var(--tw-bg-opacity))}.hover\:bg-slate-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.hover\:bg-slate-600\/70:hover{background-color:#475569b3}.hover\:bg-violet-100:hover{--tw-bg-opacity: 1;background-color:rgb(237 233 254 / var(--tw-bg-opacity))}.hover\:bg-violet-200:hover{--tw-bg-opacity: 1;background-color:rgb(221 214 254 / var(--tw-bg-opacity))}.hover\:bg-violet-400:hover{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity))}.hover\:bg-violet-50:hover{--tw-bg-opacity: 1;background-color:rgb(245 243 255 / var(--tw-bg-opacity))}.hover\:bg-violet-600\/90:hover{background-color:#7c3aede6}.hover\:bg-violet-800:hover{--tw-bg-opacity: 1;background-color:rgb(91 33 182 / var(--tw-bg-opacity))}.hover\:bg-white\/100:hover{background-color:#fff}.hover\:bg-white\/20:hover{background-color:#fff3}.hover\:bg-white\/30:hover{background-color:#ffffff4d}.hover\:bg-white\/80:hover{background-color:#fffc}.hover\:from-indigo-600:hover{--tw-gradient-from: #4f46e5 var(--tw-gradient-from-position);--tw-gradient-to: rgb(79 70 229 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:to-fuchsia-600:hover{--tw-gradient-to: #c026d3 var(--tw-gradient-to-position)}.hover\:pb-0:hover{padding-bottom:0}.hover\:pb-0\.5:hover{padding-bottom:.125rem}.hover\:text-black:hover{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.hover\:text-black\/30:hover{color:#0000004d}.hover\:text-black\/40:hover{color:#0006}.hover\:text-red-700:hover{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity))}.hover\:text-slate-600:hover{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.hover\:shadow-2xl:hover{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-xl:hover{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-slate-300\/60:hover{--tw-shadow-color: rgb(203 213 225 / .6);--tw-shadow: var(--tw-shadow-colored)}.hover\:shadow-violet-500\/50:hover{--tw-shadow-color: rgb(139 92 246 / .5);--tw-shadow: var(--tw-shadow-colored)}.focus\:z-10:focus{z-index:10}.focus\:-translate-y-0:focus{--tw-translate-y: -0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.focus\:-translate-y-0\.5:focus{--tw-translate-y: -.125rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.focus\:border-indigo-200:focus{--tw-border-opacity: 1;border-color:rgb(199 210 254 / var(--tw-border-opacity))}.focus\:border-indigo-500:focus{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}.focus\:border-red-500:focus{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity))}.focus\:border-violet-400:focus{--tw-border-opacity: 1;border-color:rgb(167 139 250 / var(--tw-border-opacity))}.focus\:bg-slate-50:focus{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.focus\:shadow-md:focus{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:shadow-xl:focus{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:shadow-slate-300\/50:focus{--tw-shadow-color: rgb(203 213 225 / .5);--tw-shadow: var(--tw-shadow-colored)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-0:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-indigo-400\/50:focus{--tw-ring-color: rgb(129 140 248 / .5)}.focus\:ring-indigo-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity))}.focus\:ring-slate-200:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(226 232 240 / var(--tw-ring-opacity))}.focus\:ring-violet-300:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(196 181 253 / var(--tw-ring-opacity))}.focus\:ring-violet-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(139 92 246 / var(--tw-ring-opacity))}.focus\:ring-violet-700:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(109 40 217 / var(--tw-ring-opacity))}.focus\:ring-white:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(255 255 255 / var(--tw-ring-opacity))}.focus\:\!ring-offset-0:focus{--tw-ring-offset-width: 0px !important}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus\:ring-offset-slate-50:focus{--tw-ring-offset-color: #f8fafc}.active\:translate-y-0:active{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-95:active{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-\[0\.98\]:active{--tw-scale-x: .98;--tw-scale-y: .98;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-\[97\%\]:active{--tw-scale-x: 97%;--tw-scale-y: 97%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-\[98\%\]:active{--tw-scale-x: 98%;--tw-scale-y: 98%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:bg-violet-200:active{--tw-bg-opacity: 1;background-color:rgb(221 214 254 / var(--tw-bg-opacity))}.active\:shadow:active{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.active\:shadow-lg:active{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.active\:shadow-md:active{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.active\:shadow-violet-500\/30:active{--tw-shadow-color: rgb(139 92 246 / .3);--tw-shadow: var(--tw-shadow-colored)}.group:nth-child(odd) .group-odd\:bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.group:nth-child(odd) .group-odd\:to-white{--tw-gradient-to: #fff var(--tw-gradient-to-position)}.group:nth-child(even) .group-even\:bg-slate-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.group:nth-child(even) .group-even\:to-slate-50{--tw-gradient-to: #f8fafc var(--tw-gradient-to-position)}.group:hover .group-hover\:bg-slate-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.group:hover .group-hover\:to-slate-100{--tw-gradient-to: #f1f5f9 var(--tw-gradient-to-position)}:is(.dark .dark\:border){border-width:1px}:is(.dark .dark\:\!border-violet-600){--tw-border-opacity: 1 !important;border-color:rgb(124 58 237 / var(--tw-border-opacity))!important}:is(.dark .dark\:border-blue-500\/50){border-color:#3b82f680}:is(.dark .dark\:border-fuchsia-400){--tw-border-opacity: 1;border-color:rgb(232 121 249 / var(--tw-border-opacity))}:is(.dark .dark\:border-green-500\/50){border-color:#22c55e80}:is(.dark .dark\:border-green-500\/70){border-color:#22c55eb3}:is(.dark .dark\:border-indigo-400){--tw-border-opacity: 1;border-color:rgb(129 140 248 / var(--tw-border-opacity))}:is(.dark .dark\:border-red-500){--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity))}:is(.dark .dark\:border-red-500\/20){border-color:#ef444433}:is(.dark .dark\:border-red-500\/40){border-color:#ef444466}:is(.dark .dark\:border-red-500\/50){border-color:#ef444480}:is(.dark .dark\:border-red-500\/70){border-color:#ef4444b3}:is(.dark .dark\:border-red-600\/50){border-color:#dc262680}:is(.dark .dark\:border-red-700\/50){border-color:#b91c1c80}:is(.dark .dark\:border-slate-500){--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity))}:is(.dark .dark\:border-slate-600){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}:is(.dark .dark\:border-slate-700){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}:is(.dark .dark\:border-slate-700\/50){border-color:#33415580}:is(.dark .dark\:border-slate-700\/70){border-color:#334155b3}:is(.dark .dark\:border-slate-800){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}:is(.dark .dark\:border-transparent){border-color:transparent}:is(.dark .dark\:border-violet-700){--tw-border-opacity: 1;border-color:rgb(109 40 217 / var(--tw-border-opacity))}:is(.dark .dark\:border-white\/20){border-color:#fff3}:is(.dark .dark\:border-yellow-500\/50){border-color:#eab30880}:is(.dark .dark\:border-yellow-500\/70){border-color:#eab308b3}:is(.dark .dark\:\!bg-violet-700\/60){background-color:#6d28d999!important}:is(.dark .dark\:bg-black\/20){background-color:#0003}:is(.dark .dark\:bg-black\/50){background-color:#00000080}:is(.dark .dark\:bg-blue-500\/10){background-color:#3b82f61a}:is(.dark .dark\:bg-fuchsia-500\/20){background-color:#d946ef33}:is(.dark .dark\:bg-green-500\/10){background-color:#22c55e1a}:is(.dark .dark\:bg-green-500\/30){background-color:#22c55e4d}:is(.dark .dark\:bg-indigo-500\/20){background-color:#6366f133}:is(.dark .dark\:bg-indigo-600\/50){background-color:#4f46e580}:is(.dark .dark\:bg-purple-800){--tw-bg-opacity: 1;background-color:rgb(107 33 168 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-red-500\/10){background-color:#ef44441a}:is(.dark .dark\:bg-red-500\/20){background-color:#ef444433}:is(.dark .dark\:bg-red-500\/30){background-color:#ef44444d}:is(.dark .dark\:bg-red-500\/5){background-color:#ef44440d}:is(.dark .dark\:bg-red-600\/5){background-color:#dc26260d}:is(.dark .dark\:bg-slate-200){--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-slate-700){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-slate-700\/20){background-color:#33415533}:is(.dark .dark\:bg-slate-700\/50){background-color:#33415580}:is(.dark .dark\:bg-slate-800){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-slate-800\/30){background-color:#1e293b4d}:is(.dark .dark\:bg-slate-800\/50){background-color:#1e293b80}:is(.dark .dark\:bg-slate-800\/60){background-color:#1e293b99}:is(.dark .dark\:bg-slate-800\/80){background-color:#1e293bcc}:is(.dark .dark\:bg-slate-900){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-slate-900\/50){background-color:#0f172a80}:is(.dark .dark\:bg-violet-600){--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-violet-700){--tw-bg-opacity: 1;background-color:rgb(109 40 217 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-violet-700\/90){background-color:#6d28d9e6}:is(.dark .dark\:bg-white\/20){background-color:#fff3}:is(.dark .dark\:bg-white\/5){background-color:#ffffff0d}:is(.dark .dark\:bg-white\/80){background-color:#fffc}:is(.dark .dark\:bg-yellow-500\/10){background-color:#eab3081a}:is(.dark .dark\:bg-yellow-500\/30){background-color:#eab3084d}:is(.dark .dark\:bg-opacity-0){--tw-bg-opacity: 0}:is(.dark .dark\:bg-opacity-90){--tw-bg-opacity: .9}:is(.dark .dark\:from-\[\#0f172a00\]){--tw-gradient-from: #0f172a00 var(--tw-gradient-from-position);--tw-gradient-to: rgb(15 23 42 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}:is(.dark .dark\:\!text-violet-100){--tw-text-opacity: 1 !important;color:rgb(237 233 254 / var(--tw-text-opacity))!important}:is(.dark .dark\:text-blue-300){--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity))}:is(.dark .dark\:text-fuchsia-200){--tw-text-opacity: 1;color:rgb(245 208 254 / var(--tw-text-opacity))}:is(.dark .dark\:text-green-300){--tw-text-opacity: 1;color:rgb(134 239 172 / var(--tw-text-opacity))}:is(.dark .dark\:text-indigo-200){--tw-text-opacity: 1;color:rgb(199 210 254 / var(--tw-text-opacity))}:is(.dark .dark\:text-indigo-300){--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-100){--tw-text-opacity: 1;color:rgb(254 226 226 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-200){--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-300){--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-400){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-400\/50){color:#f8717180}:is(.dark .dark\:text-slate-100){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-200){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-300){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-400){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-500){--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-600){--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-700){--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}:is(.dark .dark\:text-slate-900){--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}:is(.dark .dark\:text-violet-400){--tw-text-opacity: 1;color:rgb(167 139 250 / var(--tw-text-opacity))}:is(.dark .dark\:text-white){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}:is(.dark .dark\:text-white\/10){color:#ffffff1a}:is(.dark .dark\:text-white\/20){color:#fff3}:is(.dark .dark\:text-white\/30){color:#ffffff4d}:is(.dark .dark\:text-white\/50){color:#ffffff80}:is(.dark .dark\:text-white\/60){color:#fff9}:is(.dark .dark\:text-white\/70){color:#ffffffb3}:is(.dark .dark\:text-white\/80){color:#fffc}:is(.dark .dark\:text-yellow-100){--tw-text-opacity: 1;color:rgb(254 249 195 / var(--tw-text-opacity))}:is(.dark .dark\:text-yellow-200){--tw-text-opacity: 1;color:rgb(254 240 138 / var(--tw-text-opacity))}:is(.dark .dark\:text-yellow-300){--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity))}:is(.dark .dark\:opacity-90){opacity:.9}:is(.dark .dark\:shadow-lg){--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}:is(.dark .dark\:shadow-black\/20){--tw-shadow-color: rgb(0 0 0 / .2);--tw-shadow: var(--tw-shadow-colored)}:is(.dark .dark\:shadow-black\/30){--tw-shadow-color: rgb(0 0 0 / .3);--tw-shadow: var(--tw-shadow-colored)}:is(.dark .dark\:shadow-black\/50){--tw-shadow-color: rgb(0 0 0 / .5);--tw-shadow: var(--tw-shadow-colored)}:is(.dark .dark\:ring-offset-slate-900){--tw-ring-offset-color: #0f172a}:is(.dark .dark\:backdrop-blur-sm){--tw-backdrop-blur: blur(4px);-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)}:is(.dark .dark\:\[filter\:brightness\(600\%\)\]){filter:brightness(600%)}:is(.dark .dark\:placeholder\:text-slate-500)::placeholder{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}:is(.dark .dark\:even\:bg-\[\#141A2F\]:nth-child(even)){--tw-bg-opacity: 1;background-color:rgb(20 26 47 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:border-slate-100:hover){--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:border-slate-500:hover){--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:border-violet-700:hover){--tw-border-opacity: 1;border-color:rgb(109 40 217 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:\!bg-violet-700:hover){--tw-bg-opacity: 1 !important;background-color:rgb(109 40 217 / var(--tw-bg-opacity))!important}:is(.dark .dark\:hover\:bg-red-500\/20:hover){background-color:#ef444433}:is(.dark .dark\:hover\:bg-slate-600:hover){--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-slate-700\/80:hover){background-color:#334155cc}:is(.dark .dark\:hover\:bg-slate-800:hover){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-slate-800\/50:hover){background-color:#1e293b80}:is(.dark .dark\:hover\:bg-violet-700:hover){--tw-bg-opacity: 1;background-color:rgb(109 40 217 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-violet-800\/90:hover){background-color:#5b21b6e6}:is(.dark .dark\:hover\:bg-white:hover){--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-white\/10:hover){background-color:#ffffff1a}:is(.dark .dark\:hover\:bg-white\/30:hover){background-color:#ffffff4d}:is(.dark .hover\:dark\:bg-slate-900\/80):hover{background-color:#0f172acc}:is(.dark .dark\:hover\:text-red-200:hover){--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-slate-400:hover){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-white:hover){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}:is(.dark .hover\:dark\:text-white\/30):hover{color:#ffffff4d}:is(.dark .hover\:dark\:text-white\/40):hover{color:#fff6}:is(.dark .dark\:focus\:bg-slate-800:focus){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:focus\:ring-indigo-500\/70:focus){--tw-ring-color: rgb(99 102 241 / .7)}:is(.dark .group:nth-child(odd) .dark\:group-odd\:bg-slate-900){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}:is(.dark .group:nth-child(odd) .dark\:group-odd\:to-slate-900){--tw-gradient-to: #0f172a var(--tw-gradient-to-position)}:is(.dark .group:nth-child(even) .dark\:group-even\:bg-\[\#141A2E\]){--tw-bg-opacity: 1;background-color:rgb(20 26 46 / var(--tw-bg-opacity))}:is(.dark .group:nth-child(even) .dark\:group-even\:to-\[\#141A2E\]){--tw-gradient-to: #141A2E var(--tw-gradient-to-position)}:is(.dark .group:hover .dark\:group-hover\:bg-slate-800){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .group:hover .dark\:group-hover\:to-slate-800){--tw-gradient-to: #1e293b var(--tw-gradient-to-position)}@media (min-width: 500px){.xs\:flex{display:flex}.xs\:rounded-l-none{border-top-left-radius:0;border-bottom-left-radius:0}}@media (min-width: 640px){.sm\:py-2{padding-top:.5rem;padding-bottom:.5rem}.sm\:py-2\.5{padding-top:.625rem;padding-bottom:.625rem}} diff --git a/macapp/Xcode/Gertrude/WebViews/BlockedRequests/index.js b/macapp/Xcode/Gertrude/WebViews/BlockedRequests/index.js index 555d8ef9..8f602a47 100644 --- a/macapp/Xcode/Gertrude/WebViews/BlockedRequests/index.js +++ b/macapp/Xcode/Gertrude/WebViews/BlockedRequests/index.js @@ -68,7 +68,7 @@ Error generating stack: `+o.message+` * LICENSE.md file in the root directory of this source tree. * * @license MIT - */function si(){return si=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function e0(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function t0(e,t){return e.button===0&&(!t||t==="_self")&&!e0(e)}const n0=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset"],r0=E.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:o,replace:i,state:u,target:s,to:a,preventScrollReset:h}=t,d=bp(t,n0),m=Jp(a,{relative:l}),y=l0(a,{replace:i,state:u,target:s,preventScrollReset:h,relative:l});function w(k){r&&r(k),k.defaultPrevented||y(k)}return E.createElement("a",si({},d,{href:m,onClick:o?r:w,ref:n,target:s}))});var _s;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmitImpl="useSubmitImpl",e.UseFetcher="useFetcher"})(_s||(_s={}));var Ps;(function(e){e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(Ps||(Ps={}));function l0(e,t){let{target:n,replace:r,state:l,preventScrollReset:o,relative:i}=t===void 0?{}:t,u=qp(),s=iu(),a=Kc(e,{relative:i});return E.useCallback(h=>{if(t0(h,n)){h.preventDefault();let d=r!==void 0?r:Ss(s)===Ss(a);u(e,{replace:d,state:l,preventScrollReset:o,relative:i})}},[s,u,a,r,l,n,e,o,i])}const dn=({size:e="medium",fullWidth:t=!1,testId:n,color:r,className:l,disabled:o=!1,...i})=>{let u="";if(o)u="bg-slate-50 dark:bg-black/50 text-slate-400 dark:text-slate-600 border border-slate-200 dark:border-slate-800 cursor-not-allowed ring-transparent focus:ring-slate-200";else switch(r){case"primary-on-violet-bg":u="bg-white text-violet-500 hover:bg-violet-50 border-2 border-white hover:border-violet-50 ring-violet-500 ring-offset-violet-500 focus:ring-white";break;case"secondary-on-violet-bg":u="bg-violet-500 text-white border-2 border-white hover:bg-violet-400 ring-violet-500 focus:ring-white ring-offset-violet-500";break;case"primary":u="bg-violet-700 dark:bg-violet-700 border border-violet-700 dark:border-violet-700 hover:border-violet-800 dark:hover:border-violet-700 text-white dark:hover:bg-violet-700 hover:bg-violet-800 ring-transparent focus:ring-violet-700 dark:ring-offset-slate-900";break;case"secondary":u="bg-violet-100 dark:bg-slate-800/80 border border-violet-100 dark:border-slate-700/70 hover:border-violet-200 dark:hover:bg-slate-700/80 text-violet-600 dark:text-white/80 hover:bg-violet-200 ring-transparent focus:ring-violet-300 dark:focus:ring-indigo-500/70 dark:ring-offset-slate-900";break;case"tertiary":u="bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 border dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 ring-transparent focus:ring-indigo-400/50 focus:border-indigo-200 dark:ring-offset-slate-900";break;case"warning":u="bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-300 border-red-100 dark:border-red-600/50 border hover:text-red-700 hover:bg-red-100 dark:hover:bg-red-500/20 dark:hover:text-red-200 ring-transparent focus:ring-red-500 focus:border-red-500 dark:ring-offset-slate-900";break}const s=i.type==="button"||i.type==="submit";let a="";switch(e){case"small":a="text-base px-4 py-1.5 font-semibold";break;case"medium":a="text-base px-5 py-3 sm:py-2.5";break;default:a="text-lg px-10 py-2.5";break}const h=J(u,"ring ring-offset-0 focus:ring-offset-2 rounded-xl font-bold transition duration-100 outline-none block",a,t?"w-full":"w-auto",!s&&"text-center",l);return s?S("button",{type:i.type,className:h,disabled:o,...n?{"data-test":n}:{},...i.type==="button"?{onClick:o?()=>{}:()=>i.onClick()}:{},children:i.children}):i.type==="external"?S("a",{className:h,...n?{"data-test":n}:{},...o?{onClick:d=>d.preventDefault()}:{href:i.href},children:i.children}):S(r0,{className:h,to:o?"#":i.to,...n?{"data-test":n}:{},onClick:o?d=>d.preventDefault():()=>{},children:i.children})},o0=({children:e,htmlFor:t,className:n})=>S("label",{htmlFor:t,className:J("text-left text-slate-500 dark:text-slate-300 font-semibold text-md mb-1",n),children:e});function i0(e){return u0(new Date(e),"dateInput")}function u0(e,t){return t==="short"?e.toLocaleDateString():t==="url"?[`${e.getMonth()+1}`.padStart(2,"0"),`${e.getDate()}`.padStart(2,"0"),`${e.getFullYear()}`].join("-"):t==="dateInput"?[`${e.getFullYear()}`,`${e.getMonth()+1}`.padStart(2,"0"),`${e.getDate()}`.padStart(2,"0")].join("-"):[e.toLocaleDateString("en-US",{weekday:"long"}),", ",e.toLocaleDateString("en-US",{month:t==="long"?"long":"short"})," ",e.getDate(),", ",e.getFullYear()].join("")}const s0=({label:e,optional:t,value:n,setValue:r,required:l=!1,autoFocus:o=!1,placeholder:i,className:u,disabled:s,name:a,testId:h,...d})=>{const[m,y]=E.useState(n),w=E.useId(),k=xt(d)?"input":"textarea";return O("div",{className:J("flex flex-col space-y-1 w-full",u),children:[(e||t)&&O("div",{className:"flex flex-row justify-between items-center",children:[e&&S(o0,{htmlFor:w,children:e}),t&&S("span",{className:"text-violet-500/80 translate-y-px text-sm antialiased italic",children:"*optional"})]}),O("div",{className:"flex shadow-sm rounded-lg",children:[xt(d)&&d.prefix&&S("div",{className:"hidden xs:flex justify-center items-center p-3 bg-slate-50 dark:bg-slate-700/50 border border-r-0 dark:border-slate-700 rounded-l-lg",children:S("h3",{className:"text-slate-500 dark:text-slate-400",children:d.prefix})}),S(k,{id:w,type:d.type==="positiveInteger"?"number":d.type,value:m,required:!!l,autoFocus:o,placeholder:i,disabled:s,name:a,...h?{"data-test":h}:{},...d.type==="url"?{autoCapitalize:"none",autoCorrect:"off"}:{},...d.type==="password"?{minLength:4}:{},...d.type==="date"?{min:i0(new Date().toISOString())}:{},...xt(d)?{}:{rows:d.rows},onChange:F=>{const f=F.target.value;y(f),(d.type!=="positiveInteger"||a0(f))&&r(f)},className:J("py-3 px-4 flex-grow w-12","border border-slate-200 rounded-lg","transition-[border-color,ring-color] duration-150","text-slate-600 placeholder:text-slate-400/90 placeholder:antialiased","ring-0 ring-slate-200 outline-none focus:shadow-md focus:border-indigo-500 focus:ring-indigo-500 focus:ring-1","dark:bg-slate-700/20 dark:border-slate-700 dark:placeholder:text-slate-500 dark:text-white",!xt(d)&&d.noResize&&"resize-none",xt(d)&&d.unit&&"rounded-r-none",xt(d)&&d.prefix&&"xs:rounded-l-none")}),xt(d)&&d.unit&&S("div",{className:"flex justify-center items-center p-3 bg-slate-50 dark:bg-slate-700/50 border border-l-0 dark:border-slate-700 rounded-r-lg",children:S("h3",{className:"text-slate-500 dark:text-slate-400",children:d.unit})})]})]})};function a0(e){return e.match(/^[0-9]+$/)!==null&&Number.isInteger(Number(e))&&Number(e)>=0}function xt(e){return e.type!=="textarea"}var c0=Object.defineProperty,f0=(e,t,n)=>t in e?c0(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,uo=(e,t,n)=>(f0(e,typeof t!="symbol"?t+"":t,n),n);let d0=class{constructor(){uo(this,"current",this.detect()),uo(this,"handoffState","pending"),uo(this,"currentId",0)}set(t){this.current!==t&&(this.handoffState="pending",this.currentId=0,this.current=t)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return this.current==="server"}get isClient(){return this.current==="client"}detect(){return typeof window>"u"||typeof document>"u"?"server":"client"}handoff(){this.handoffState==="pending"&&(this.handoffState="complete")}get isHandoffComplete(){return this.handoffState==="complete"}},rn=new d0,pn=(e,t)=>{rn.isServer?E.useEffect(e,t):E.useLayoutEffect(e,t)};function p0(e){let t=E.useRef(e);return pn(()=>{t.current=e},[e]),t}function h0(e){typeof queueMicrotask=="function"?queueMicrotask(e):Promise.resolve().then(e).catch(t=>setTimeout(()=>{throw t}))}function m0(){let e=[],t=[],n={enqueue(r){t.push(r)},addEventListener(r,l,o,i){return r.addEventListener(l,o,i),n.add(()=>r.removeEventListener(l,o,i))},requestAnimationFrame(...r){let l=requestAnimationFrame(...r);return n.add(()=>cancelAnimationFrame(l))},nextFrame(...r){return n.requestAnimationFrame(()=>n.requestAnimationFrame(...r))},setTimeout(...r){let l=setTimeout(...r);return n.add(()=>clearTimeout(l))},microTask(...r){let l={current:!0};return h0(()=>{l.current&&r[0]()}),n.add(()=>{l.current=!1})},add(r){return e.push(r),()=>{let l=e.indexOf(r);if(l>=0){let[o]=e.splice(l,1);o()}}},dispose(){for(let r of e.splice(0))r()},async workQueue(){for(let r of t.splice(0))await r()},style(r,l,o){let i=r.style.getPropertyValue(l);return Object.assign(r.style,{[l]:o}),this.add(()=>{Object.assign(r.style,{[l]:i})})}};return n}function v0(){let[e]=E.useState(m0);return E.useEffect(()=>()=>e.dispose(),[e]),e}let it=function(e){let t=p0(e);return _e.useCallback((...n)=>t.current(...n),[t])};function g0(){let[e,t]=E.useState(rn.isHandoffComplete);return e&&rn.isHandoffComplete===!1&&t(!1),E.useEffect(()=>{e!==!0&&t(!0)},[e]),E.useEffect(()=>rn.handoff(),[]),e}var Ts;let uu=(Ts=_e.useId)!=null?Ts:function(){let e=g0(),[t,n]=_e.useState(e?()=>rn.nextId():null);return pn(()=>{t===null&&n(rn.nextId())},[t]),t!=null?""+t:void 0};function Yc(e,t,...n){if(e in t){let l=t[e];return typeof l=="function"?l(...n):l}let r=new Error(`Tried to handle "${e}" but there is no handler defined. Only defined handlers are: ${Object.keys(t).map(l=>`"${l}"`).join(", ")}.`);throw Error.captureStackTrace&&Error.captureStackTrace(r,Yc),r}function Ls(e){var t;if(e.type)return e.type;let n=(t=e.as)!=null?t:"button";if(typeof n=="string"&&n.toLowerCase()==="button")return"button"}function y0(e,t){let[n,r]=E.useState(()=>Ls(e));return pn(()=>{r(Ls(e))},[e.type,e.as]),pn(()=>{n||!t.current||t.current instanceof HTMLButtonElement&&!t.current.hasAttribute("type")&&r("button")},[n,t]),n}let w0=Symbol();function su(...e){let t=E.useRef(e);E.useEffect(()=>{t.current=e},[e]);let n=it(r=>{for(let l of t.current)l!=null&&(typeof l=="function"?l(r):l.current=r)});return e.every(r=>r==null||r?.[w0])?void 0:n}function k0(...e){return e.filter(Boolean).join(" ")}var S0=(e=>(e[e.None=0]="None",e[e.RenderStrategy=1]="RenderStrategy",e[e.Static=2]="Static",e))(S0||{}),x0=(e=>(e[e.Unmount=0]="Unmount",e[e.Hidden=1]="Hidden",e))(x0||{});function ar({ourProps:e,theirProps:t,slot:n,defaultTag:r,features:l,visible:o=!0,name:i}){let u=Gc(t,e);if(o)return Lr(u,n,r,i);let s=l??0;if(s&2){let{static:a=!1,...h}=u;if(a)return Lr(h,n,r,i)}if(s&1){let{unmount:a=!0,...h}=u;return Yc(a?0:1,{[0](){return null},[1](){return Lr({...h,hidden:!0,style:{display:"none"}},n,r,i)}})}return Lr(u,n,r,i)}function Lr(e,t={},n,r){var l;let{as:o=n,children:i,refName:u="ref",...s}=so(e,["unmount","static"]),a=e.ref!==void 0?{[u]:e.ref}:{},h=typeof i=="function"?i(t):i;s.className&&typeof s.className=="function"&&(s.className=s.className(t));let d={};if(t){let m=!1,y=[];for(let[w,k]of Object.entries(t))typeof k=="boolean"&&(m=!0),k===!0&&y.push(w);m&&(d["data-headlessui-state"]=y.join(" "))}if(o===E.Fragment&&Object.keys(ai(s)).length>0){if(!E.isValidElement(h)||Array.isArray(h)&&h.length>1)throw new Error(['Passing props on "Fragment"!',"",`The current component <${r} /> is rendering a "Fragment".`,"However we need to passthrough the following props:",Object.keys(s).map(w=>` - ${w}`).join(` + */function si(){return si=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function e0(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function t0(e,t){return e.button===0&&(!t||t==="_self")&&!e0(e)}const n0=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset"],r0=E.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:o,replace:i,state:u,target:s,to:a,preventScrollReset:h}=t,d=bp(t,n0),m=Jp(a,{relative:l}),y=l0(a,{replace:i,state:u,target:s,preventScrollReset:h,relative:l});function w(k){r&&r(k),k.defaultPrevented||y(k)}return E.createElement("a",si({},d,{href:m,onClick:o?r:w,ref:n,target:s}))});var _s;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmitImpl="useSubmitImpl",e.UseFetcher="useFetcher"})(_s||(_s={}));var Ps;(function(e){e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(Ps||(Ps={}));function l0(e,t){let{target:n,replace:r,state:l,preventScrollReset:o,relative:i}=t===void 0?{}:t,u=qp(),s=iu(),a=Kc(e,{relative:i});return E.useCallback(h=>{if(t0(h,n)){h.preventDefault();let d=r!==void 0?r:Ss(s)===Ss(a);u(e,{replace:d,state:l,preventScrollReset:o,relative:i})}},[s,u,a,r,l,n,e,o,i])}const dn=({size:e="medium",fullWidth:t=!1,testId:n,color:r,className:l,disabled:o=!1,...i})=>{let u="";if(o)u="bg-slate-50 dark:bg-black/50 text-slate-400 dark:text-slate-600 border border-slate-200 dark:border-slate-800 cursor-not-allowed ring-transparent focus:ring-slate-200";else switch(r){case"primary-on-violet-bg":u="bg-white text-violet-500 hover:bg-violet-50 border-2 border-white hover:border-violet-50 ring-violet-500 ring-offset-violet-500 focus:ring-white";break;case"secondary-on-violet-bg":u="bg-violet-500 text-white border-2 border-white hover:bg-violet-400 ring-violet-500 focus:ring-white ring-offset-violet-500";break;case"primary":u="bg-violet-700 dark:bg-violet-700 border border-violet-700 dark:border-violet-700 hover:border-violet-800 dark:hover:border-violet-700 text-white dark:hover:bg-violet-700 hover:bg-violet-800 ring-transparent focus:ring-violet-700 dark:ring-offset-slate-900";break;case"secondary":u="bg-violet-100 dark:bg-slate-800/80 border border-violet-100 dark:border-slate-700/70 hover:border-violet-200 dark:hover:bg-slate-700/80 text-violet-600 dark:text-white/80 hover:bg-violet-200 ring-transparent focus:ring-violet-300 dark:focus:ring-indigo-500/70 dark:ring-offset-slate-900";break;case"tertiary":u="bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 border dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 ring-transparent focus:ring-indigo-400/50 focus:border-indigo-200 dark:ring-offset-slate-900";break;case"warning":u="bg-red-50 dark:bg-red-500/10 text-red-600 dark:text-red-300 border-red-100 dark:border-red-600/50 border hover:text-red-700 hover:bg-red-100 dark:hover:bg-red-500/20 dark:hover:text-red-200 ring-transparent focus:ring-red-500 focus:border-red-500 dark:ring-offset-slate-900";break}const s=i.type==="button"||i.type==="submit";let a="";switch(e){case"small":a="text-base px-4 py-1.5 font-semibold";break;case"medium":a="text-base px-5 py-3 sm:py-2.5";break;default:a="text-lg px-10 py-2.5";break}const h=J(u,"ring ring-offset-0 focus:ring-offset-2 rounded-xl font-bold transition duration-200 outline-none block active:scale-[0.98] leading-[1.25em] select-none",a,t?"w-full":"w-auto",!s&&"text-center",l);return s?S("button",{type:i.type,className:h,disabled:o,...n?{"data-test":n}:{},...i.type==="button"?{onClick:o?()=>{}:()=>i.onClick()}:{},children:i.children}):i.type==="external"?S("a",{className:h,...n?{"data-test":n}:{},...o?{onClick:d=>d.preventDefault()}:{href:i.href},children:i.children}):S(r0,{className:h,to:o?"#":i.to,...n?{"data-test":n}:{},onClick:o?d=>d.preventDefault():()=>{},children:i.children})},o0=({children:e,htmlFor:t,className:n})=>S("label",{htmlFor:t,className:J("text-left text-slate-700 dark:text-slate-200 font-medium text-md mb-1",n),children:e});function i0(e){return u0(new Date(e),"dateInput")}function u0(e,t){return t==="short"?e.toLocaleDateString():t==="url"?[`${e.getMonth()+1}`.padStart(2,"0"),`${e.getDate()}`.padStart(2,"0"),`${e.getFullYear()}`].join("-"):t==="dateInput"?[`${e.getFullYear()}`,`${e.getMonth()+1}`.padStart(2,"0"),`${e.getDate()}`.padStart(2,"0")].join("-"):[e.toLocaleDateString("en-US",{weekday:"long"}),", ",e.toLocaleDateString("en-US",{month:t==="long"?"long":"short"})," ",e.getDate(),", ",e.getFullYear()].join("")}const s0=({label:e,optional:t,value:n,setValue:r,required:l=!1,autoFocus:o=!1,placeholder:i,className:u,disabled:s,name:a,testId:h,...d})=>{const[m,y]=E.useState(n),w=E.useId(),k=xt(d)?"input":"textarea";return O("div",{className:J("flex flex-col space-y-1 w-full",u),children:[(e||t)&&O("div",{className:"flex flex-row justify-between items-center",children:[e&&S(o0,{htmlFor:w,children:e}),t&&S("span",{className:"text-violet-500/80 font-medium translate-y-px text-sm antialiased italic",children:"*optional"})]}),O("div",{className:"flex shadow-sm rounded-xl",children:[xt(d)&&d.prefix&&S("div",{className:"hidden xs:flex justify-center items-center p-3 bg-slate-50 dark:bg-slate-700/50 border border-r-0 dark:border-slate-700 rounded-l-xl",children:S("h3",{className:"text-slate-500 dark:text-slate-400",children:d.prefix})}),S(k,{id:w,type:d.type==="positiveInteger"?"number":d.type,value:m,required:!!l,autoFocus:o,placeholder:i,disabled:s,name:a,...h?{"data-test":h}:{},...d.type==="url"?{autoCapitalize:"none",autoCorrect:"off"}:{},...d.type==="password"?{minLength:4}:{},...d.type==="date"?{min:i0(new Date().toISOString())}:{},...xt(d)?{}:{rows:d.rows},onChange:F=>{const f=F.target.value;y(f),(d.type!=="positiveInteger"||a0(f))&&r(f)},className:J("py-3 px-4 flex-grow w-12","border border-slate-200 rounded-xl","transition-[border-color,ring-color] duration-150","text-slate-600 placeholder:text-slate-400/90 placeholder:antialiased","ring-0 ring-slate-200 outline-none focus:shadow-md focus:border-indigo-500 focus:ring-indigo-500 focus:ring-1","dark:bg-slate-700/20 dark:border-slate-700 dark:placeholder:text-slate-500 dark:text-white",!xt(d)&&d.noResize&&"resize-none",xt(d)&&d.unit&&"rounded-r-none",xt(d)&&d.prefix&&"xs:rounded-l-none")}),xt(d)&&d.unit&&S("div",{className:"flex justify-center items-center p-3 bg-slate-50 dark:bg-slate-700/50 border border-l-0 dark:border-slate-700 rounded-r-xl",children:S("h3",{className:"text-slate-500 dark:text-slate-400",children:d.unit})})]})]})};function a0(e){return e.match(/^[0-9]+$/)!==null&&Number.isInteger(Number(e))&&Number(e)>=0}function xt(e){return e.type!=="textarea"}var c0=Object.defineProperty,f0=(e,t,n)=>t in e?c0(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,uo=(e,t,n)=>(f0(e,typeof t!="symbol"?t+"":t,n),n);let d0=class{constructor(){uo(this,"current",this.detect()),uo(this,"handoffState","pending"),uo(this,"currentId",0)}set(t){this.current!==t&&(this.handoffState="pending",this.currentId=0,this.current=t)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return this.current==="server"}get isClient(){return this.current==="client"}detect(){return typeof window>"u"||typeof document>"u"?"server":"client"}handoff(){this.handoffState==="pending"&&(this.handoffState="complete")}get isHandoffComplete(){return this.handoffState==="complete"}},rn=new d0,pn=(e,t)=>{rn.isServer?E.useEffect(e,t):E.useLayoutEffect(e,t)};function p0(e){let t=E.useRef(e);return pn(()=>{t.current=e},[e]),t}function h0(e){typeof queueMicrotask=="function"?queueMicrotask(e):Promise.resolve().then(e).catch(t=>setTimeout(()=>{throw t}))}function m0(){let e=[],t=[],n={enqueue(r){t.push(r)},addEventListener(r,l,o,i){return r.addEventListener(l,o,i),n.add(()=>r.removeEventListener(l,o,i))},requestAnimationFrame(...r){let l=requestAnimationFrame(...r);return n.add(()=>cancelAnimationFrame(l))},nextFrame(...r){return n.requestAnimationFrame(()=>n.requestAnimationFrame(...r))},setTimeout(...r){let l=setTimeout(...r);return n.add(()=>clearTimeout(l))},microTask(...r){let l={current:!0};return h0(()=>{l.current&&r[0]()}),n.add(()=>{l.current=!1})},add(r){return e.push(r),()=>{let l=e.indexOf(r);if(l>=0){let[o]=e.splice(l,1);o()}}},dispose(){for(let r of e.splice(0))r()},async workQueue(){for(let r of t.splice(0))await r()},style(r,l,o){let i=r.style.getPropertyValue(l);return Object.assign(r.style,{[l]:o}),this.add(()=>{Object.assign(r.style,{[l]:i})})}};return n}function v0(){let[e]=E.useState(m0);return E.useEffect(()=>()=>e.dispose(),[e]),e}let it=function(e){let t=p0(e);return _e.useCallback((...n)=>t.current(...n),[t])};function g0(){let[e,t]=E.useState(rn.isHandoffComplete);return e&&rn.isHandoffComplete===!1&&t(!1),E.useEffect(()=>{e!==!0&&t(!0)},[e]),E.useEffect(()=>rn.handoff(),[]),e}var Ts;let uu=(Ts=_e.useId)!=null?Ts:function(){let e=g0(),[t,n]=_e.useState(e?()=>rn.nextId():null);return pn(()=>{t===null&&n(rn.nextId())},[t]),t!=null?""+t:void 0};function Yc(e,t,...n){if(e in t){let l=t[e];return typeof l=="function"?l(...n):l}let r=new Error(`Tried to handle "${e}" but there is no handler defined. Only defined handlers are: ${Object.keys(t).map(l=>`"${l}"`).join(", ")}.`);throw Error.captureStackTrace&&Error.captureStackTrace(r,Yc),r}function Ls(e){var t;if(e.type)return e.type;let n=(t=e.as)!=null?t:"button";if(typeof n=="string"&&n.toLowerCase()==="button")return"button"}function y0(e,t){let[n,r]=E.useState(()=>Ls(e));return pn(()=>{r(Ls(e))},[e.type,e.as]),pn(()=>{n||!t.current||t.current instanceof HTMLButtonElement&&!t.current.hasAttribute("type")&&r("button")},[n,t]),n}let w0=Symbol();function su(...e){let t=E.useRef(e);E.useEffect(()=>{t.current=e},[e]);let n=it(r=>{for(let l of t.current)l!=null&&(typeof l=="function"?l(r):l.current=r)});return e.every(r=>r==null||r?.[w0])?void 0:n}function k0(...e){return e.filter(Boolean).join(" ")}var S0=(e=>(e[e.None=0]="None",e[e.RenderStrategy=1]="RenderStrategy",e[e.Static=2]="Static",e))(S0||{}),x0=(e=>(e[e.Unmount=0]="Unmount",e[e.Hidden=1]="Hidden",e))(x0||{});function ar({ourProps:e,theirProps:t,slot:n,defaultTag:r,features:l,visible:o=!0,name:i}){let u=Gc(t,e);if(o)return Lr(u,n,r,i);let s=l??0;if(s&2){let{static:a=!1,...h}=u;if(a)return Lr(h,n,r,i)}if(s&1){let{unmount:a=!0,...h}=u;return Yc(a?0:1,{[0](){return null},[1](){return Lr({...h,hidden:!0,style:{display:"none"}},n,r,i)}})}return Lr(u,n,r,i)}function Lr(e,t={},n,r){var l;let{as:o=n,children:i,refName:u="ref",...s}=so(e,["unmount","static"]),a=e.ref!==void 0?{[u]:e.ref}:{},h=typeof i=="function"?i(t):i;s.className&&typeof s.className=="function"&&(s.className=s.className(t));let d={};if(t){let m=!1,y=[];for(let[w,k]of Object.entries(t))typeof k=="boolean"&&(m=!0),k===!0&&y.push(w);m&&(d["data-headlessui-state"]=y.join(" "))}if(o===E.Fragment&&Object.keys(ai(s)).length>0){if(!E.isValidElement(h)||Array.isArray(h)&&h.length>1)throw new Error(['Passing props on "Fragment"!',"",`The current component <${r} /> is rendering a "Fragment".`,"However we need to passthrough the following props:",Object.keys(s).map(w=>` - ${w}`).join(` `),"","You can apply a few solutions:",['Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',"Render a single element as the child so that we can forward the props onto that element."].map(w=>` - ${w}`).join(` `)].join(` -`));let m=k0((l=h.props)==null?void 0:l.className,s.className),y=m?{className:m}:{};return E.cloneElement(h,Object.assign({},Gc(h.props,ai(so(s,["ref"]))),d,a,E0(h.ref,a.ref),y))}return E.createElement(o,Object.assign({},so(s,["ref"]),o!==E.Fragment&&a,o!==E.Fragment&&d),h)}function E0(...e){return{ref:e.every(t=>t==null)?void 0:t=>{for(let n of e)n!=null&&(typeof n=="function"?n(t):n.current=t)}}}function Gc(...e){if(e.length===0)return{};if(e.length===1)return e[0];let t={},n={};for(let r of e)for(let l in r)l.startsWith("on")&&typeof r[l]=="function"?(n[l]!=null||(n[l]=[]),n[l].push(r[l])):t[l]=r[l];if(t.disabled||t["aria-disabled"])return Object.assign(t,Object.fromEntries(Object.keys(n).map(r=>[r,void 0])));for(let r in n)Object.assign(t,{[r](l,...o){let i=n[r];for(let u of i){if((l instanceof Event||l?.nativeEvent instanceof Event)&&l.defaultPrevented)return;u(l,...o)}}});return t}function Ll(e){var t;return Object.assign(E.forwardRef(e),{displayName:(t=e.displayName)!=null?t:e.name})}function ai(e){let t=Object.assign({},e);for(let n in t)t[n]===void 0&&delete t[n];return t}function so(e,t=[]){let n=Object.assign({},e);for(let r of t)r in n&&delete n[r];return n}function C0(e){let t=e.parentElement,n=null;for(;t&&!(t instanceof HTMLFieldSetElement);)t instanceof HTMLLegendElement&&(n=t),t=t.parentElement;let r=t?.getAttribute("disabled")==="";return r&&N0(n)?!1:r}function N0(e){if(!e)return!1;let t=e.previousElementSibling;for(;t!==null;){if(t instanceof HTMLLegendElement)return!1;t=t.previousElementSibling}return!0}function _0(e){var t;let n=(t=e?.form)!=null?t:e.closest("form");if(n){for(let r of n.elements)if(r.tagName==="INPUT"&&r.type==="submit"||r.tagName==="BUTTON"&&r.type==="submit"||r.nodeName==="INPUT"&&r.type==="image"){r.click();return}}}let P0="div";var Xc=(e=>(e[e.None=1]="None",e[e.Focusable=2]="Focusable",e[e.Hidden=4]="Hidden",e))(Xc||{});let T0=Ll(function(e,t){let{features:n=1,...r}=e,l={ref:t,"aria-hidden":(n&2)===2?!0:void 0,style:{position:"fixed",top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0",...(n&4)===4&&(n&2)!==2&&{display:"none"}}};return ar({ourProps:l,theirProps:r,slot:{},defaultTag:P0,name:"Hidden"})});var ci=(e=>(e.Space=" ",e.Enter="Enter",e.Escape="Escape",e.Backspace="Backspace",e.Delete="Delete",e.ArrowLeft="ArrowLeft",e.ArrowUp="ArrowUp",e.ArrowRight="ArrowRight",e.ArrowDown="ArrowDown",e.Home="Home",e.End="End",e.PageUp="PageUp",e.PageDown="PageDown",e.Tab="Tab",e))(ci||{});function L0(e,t,n){let[r,l]=E.useState(n),o=e!==void 0,i=E.useRef(o),u=E.useRef(!1),s=E.useRef(!1);return o&&!i.current&&!u.current?(u.current=!0,i.current=o,console.error("A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.")):!o&&i.current&&!s.current&&(s.current=!0,i.current=o,console.error("A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.")),[o?e:r,it(a=>(o||l(a),t?.(a)))]}let Zc=E.createContext(null);function Jc(){let e=E.useContext(Zc);if(e===null){let t=new Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(t,Jc),t}return e}function z0(){let[e,t]=E.useState([]);return[e.length>0?e.join(" "):void 0,E.useMemo(()=>function(n){let r=it(o=>(t(i=>[...i,o]),()=>t(i=>{let u=i.slice(),s=u.indexOf(o);return s!==-1&&u.splice(s,1),u}))),l=E.useMemo(()=>({register:r,slot:n.slot,name:n.name,props:n.props}),[r,n.slot,n.name,n.props]);return _e.createElement(Zc.Provider,{value:l},n.children)},[t])]}let O0="p",R0=Ll(function(e,t){let n=uu(),{id:r=`headlessui-description-${n}`,...l}=e,o=Jc(),i=su(t);pn(()=>o.register(r),[r,o.register]);let u={ref:i,...o.props,id:r};return ar({ourProps:u,theirProps:l,slot:o.slot||{},defaultTag:O0,name:o.name||"Description"})}),qc=E.createContext(null);function bc(){let e=E.useContext(qc);if(e===null){let t=new Error("You used a