diff --git a/.github/workflows/hacs-validate.yml b/.github/workflows/hacs-validate.yml new file mode 100644 index 000000000..111ead4d9 --- /dev/null +++ b/.github/workflows/hacs-validate.yml @@ -0,0 +1,17 @@ +name: Validate HACS + +on: + push: + pull_request: + workflow_dispatch: + +permissions: {} + +jobs: + validate-hacs: + runs-on: ubuntu-latest + steps: + - name: HACS validation + uses: hacs/action@main + with: + category: plugin diff --git a/.gitignore b/.gitignore index d01fc0bdd..d47dff39c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ package-lock.json # Testing coverage +test-results/ # Turbo .turbo @@ -30,7 +31,9 @@ supabase/.temp/ .next/ out/ build -dist +**/dist/ +!/dist/ +!/dist/pascal-viewer-card.js *.tsbuildinfo diff --git a/apps/editor/app/api/home-assistant/connect/route.ts b/apps/editor/app/api/home-assistant/connect/route.ts new file mode 100644 index 000000000..772e46a70 --- /dev/null +++ b/apps/editor/app/api/home-assistant/connect/route.ts @@ -0,0 +1,32 @@ +import { + resolveHomeAssistantServerConfig, + validateHomeAssistantConnection, +} from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +export async function GET() { + try { + const result = await validateHomeAssistantConnection(await resolveHomeAssistantServerConfig()) + return Response.json(result, { status: result.success ? 200 : 200 }) + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to connect to Home Assistant.' + return Response.json( + { + baseUrl: null, + castEntityId: null, + castFriendlyName: null, + clientId: null, + entityCount: 0, + error: message, + externalUrl: null, + instanceUrl: null, + linked: false, + message, + mode: 'unlinked', + success: false, + }, + { status: 500 }, + ) + } +} diff --git a/apps/editor/app/api/home-assistant/connection-status/route.ts b/apps/editor/app/api/home-assistant/connection-status/route.ts new file mode 100644 index 000000000..283e0f2fc --- /dev/null +++ b/apps/editor/app/api/home-assistant/connection-status/route.ts @@ -0,0 +1,32 @@ +import { + resolveHomeAssistantServerConfig, + validateHomeAssistantConnection, +} from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +export async function GET() { + try { + const result = await validateHomeAssistantConnection(await resolveHomeAssistantServerConfig()) + return Response.json(result, { status: result.success ? 200 : 200 }) + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to connect to Home Assistant.' + return Response.json( + { + baseUrl: null, + castEntityId: null, + castFriendlyName: null, + clientId: null, + entityCount: 0, + error: message, + externalUrl: null, + instanceUrl: null, + linked: false, + message, + mode: 'unlinked', + success: false, + }, + { status: 200 }, + ) + } +} diff --git a/apps/editor/app/api/home-assistant/device-action/route.ts b/apps/editor/app/api/home-assistant/device-action/route.ts new file mode 100644 index 000000000..8afd4e0b0 --- /dev/null +++ b/apps/editor/app/api/home-assistant/device-action/route.ts @@ -0,0 +1,70 @@ +import type { + HomeAssistantActionRequest, + HomeAssistantCollectionBinding, +} from '@pascal-app/home-assistant' +import { getHomeAssistantLink } from '@pascal-app/home-assistant' +import { + resolveHomeAssistantServerConfig, + runHomeAssistantCollectionAction, + runHomeAssistantDeviceAction, +} from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +type DeviceActionRequestBody = { + binding?: HomeAssistantCollectionBinding + collectionName?: string + itemName?: string + link?: unknown + request?: HomeAssistantActionRequest +} + +export async function POST(request: Request) { + try { + const body = (await request.json()) as DeviceActionRequestBody + if ( + body.binding && + typeof body.binding === 'object' && + body.request && + typeof body.request === 'object' + ) { + const collectionName = + typeof body.collectionName === 'string' && body.collectionName.trim().length > 0 + ? body.collectionName.trim() + : 'Linked collection' + + const result = await runHomeAssistantCollectionAction( + await resolveHomeAssistantServerConfig(), + collectionName, + body.binding, + body.request, + ) + return Response.json(result) + } + + const itemName = + typeof body.itemName === 'string' && body.itemName.trim().length > 0 + ? body.itemName.trim() + : 'Linked item' + const link = getHomeAssistantLink({ + homeAssistantLink: body.link, + }) + + if (!link) { + return Response.json( + { error: 'Missing or invalid Home Assistant link payload.' }, + { status: 400 }, + ) + } + + const result = await runHomeAssistantDeviceAction( + await resolveHomeAssistantServerConfig(), + itemName, + link, + ) + return Response.json(result) + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown Home Assistant action error.' + return Response.json({ error: message }, { status: 500 }) + } +} diff --git a/apps/editor/app/api/home-assistant/discover-devices/route.ts b/apps/editor/app/api/home-assistant/discover-devices/route.ts new file mode 100644 index 000000000..5dc8d8f60 --- /dev/null +++ b/apps/editor/app/api/home-assistant/discover-devices/route.ts @@ -0,0 +1,38 @@ +import { discoverHomeAssistantDevices } from '@pascal-app/home-assistant/server' +import { + hasHomeAssistantServerConfig, + resolveHomeAssistantServerConfig, +} from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +export async function GET() { + try { + const config = await resolveHomeAssistantServerConfig() + if (!hasHomeAssistantServerConfig(config)) { + return Response.json( + { + devices: [], + error: 'Home Assistant is not linked yet.', + }, + { status: 412 }, + ) + } + + const devices = await discoverHomeAssistantDevices(config) + return Response.json({ + devices, + scannedAt: new Date().toISOString(), + }) + } catch (error) { + const message = + error instanceof Error ? error.message : 'Unknown Home Assistant discovery error.' + return Response.json( + { + devices: [], + error: message, + }, + { status: 500 }, + ) + } +} diff --git a/apps/editor/app/api/home-assistant/discover-instances/route.ts b/apps/editor/app/api/home-assistant/discover-instances/route.ts new file mode 100644 index 000000000..e6ded6b6f --- /dev/null +++ b/apps/editor/app/api/home-assistant/discover-instances/route.ts @@ -0,0 +1,24 @@ +import { discoverHomeAssistantInstances } from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +export async function GET() { + try { + const instances = await discoverHomeAssistantInstances() + return Response.json({ + instances, + scannedAt: new Date().toISOString(), + }) + } catch (error) { + const message = + error instanceof Error ? error.message : 'Unknown Home Assistant discovery error.' + + return Response.json( + { + error: message, + instances: [], + }, + { status: 500 }, + ) + } +} diff --git a/apps/editor/app/api/home-assistant/import-resources/route.ts b/apps/editor/app/api/home-assistant/import-resources/route.ts new file mode 100644 index 000000000..40fefc416 --- /dev/null +++ b/apps/editor/app/api/home-assistant/import-resources/route.ts @@ -0,0 +1,37 @@ +import { listImportableHomeAssistantResources } from '@pascal-app/home-assistant/server' +import { + hasHomeAssistantServerConfig, + resolveHomeAssistantServerConfig, +} from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +export async function GET() { + try { + const config = await resolveHomeAssistantServerConfig() + if (!hasHomeAssistantServerConfig(config)) { + return Response.json( + { + error: 'Home Assistant is not linked yet.', + resources: [], + }, + { status: 412 }, + ) + } + + const resources = await listImportableHomeAssistantResources(config) + return Response.json({ + importedAt: new Date().toISOString(), + resources, + }) + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown Home Assistant import error.' + return Response.json( + { + error: message, + resources: [], + }, + { status: 500 }, + ) + } +} diff --git a/apps/editor/app/api/home-assistant/oauth/callback/route.ts b/apps/editor/app/api/home-assistant/oauth/callback/route.ts new file mode 100644 index 000000000..8320af37f --- /dev/null +++ b/apps/editor/app/api/home-assistant/oauth/callback/route.ts @@ -0,0 +1,81 @@ +import type { NextRequest } from 'next/server' +import { NextResponse } from 'next/server' +import { + exchangeAuthorizationCode, + HOME_ASSISTANT_OAUTH_COOKIE, +} from '@pascal-app/home-assistant/server' +import { writeLinkedHomeAssistantProfile } from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +function buildRedirectUrl(base: string, status: 'success' | 'error', message?: string) { + const redirectUrl = new URL('/', base) + redirectUrl.searchParams.set('ha_link', status) + if (message) { + redirectUrl.searchParams.set('ha_message', message) + } + return redirectUrl +} + +export async function GET(request: NextRequest) { + const oauthCookie = request.cookies.get(HOME_ASSISTANT_OAUTH_COOKIE)?.value + const fallbackBase = request.nextUrl.origin + + if (!oauthCookie) { + return NextResponse.redirect( + buildRedirectUrl(fallbackBase, 'error', 'Missing Home Assistant OAuth state.'), + ) + } + + try { + const oauthState = JSON.parse(oauthCookie) as { + clientId?: string + externalUrl?: string | null + instanceUrl?: string + state?: string + } + const code = request.nextUrl.searchParams.get('code') + const state = request.nextUrl.searchParams.get('state') + + if (!(oauthState.clientId && oauthState.instanceUrl && oauthState.state && code && state)) { + throw new Error('Missing OAuth callback parameters.') + } + + if (state !== oauthState.state) { + throw new Error('Home Assistant OAuth state did not match.') + } + + const tokens = await exchangeAuthorizationCode( + oauthState.instanceUrl, + oauthState.clientId, + code, + oauthState.externalUrl, + ) + + await writeLinkedHomeAssistantProfile({ + accessToken: tokens.access_token, + accessTokenExpiresAt: new Date(Date.now() + tokens.expires_in * 1000).toISOString(), + clientId: oauthState.clientId, + externalUrl: + typeof oauthState.externalUrl === 'string' && oauthState.externalUrl.trim().length > 0 + ? oauthState.externalUrl + : null, + instanceUrl: oauthState.instanceUrl, + linkedAt: new Date().toISOString(), + refreshToken: tokens.refresh_token ?? '', + }) + + const response = NextResponse.redirect(buildRedirectUrl(oauthState.clientId, 'success')) + response.cookies.delete(HOME_ASSISTANT_OAUTH_COOKIE) + return response + } catch (error) { + const message = + error instanceof Error ? error.message : 'Failed to complete Home Assistant sign-in.' + const parsedCookie = JSON.parse(oauthCookie) as { clientId?: string } + const response = NextResponse.redirect( + buildRedirectUrl(parsedCookie.clientId ?? fallbackBase, 'error', message), + ) + response.cookies.delete(HOME_ASSISTANT_OAUTH_COOKIE) + return response + } +} diff --git a/apps/editor/app/api/home-assistant/oauth/start/route.ts b/apps/editor/app/api/home-assistant/oauth/start/route.ts new file mode 100644 index 000000000..4b0593378 --- /dev/null +++ b/apps/editor/app/api/home-assistant/oauth/start/route.ts @@ -0,0 +1,51 @@ +import type { NextRequest } from 'next/server' +import { NextResponse } from 'next/server' +import { + buildHomeAssistantAuthorizeUrl, + buildHomeAssistantOauthState, + HOME_ASSISTANT_OAUTH_COOKIE, + normalizeOptionalHomeAssistantUrl, +} from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +type StartOauthRequestBody = { + externalUrl?: string + instanceUrl?: string +} + +export async function POST(request: NextRequest) { + try { + const body = (await request.json()) as StartOauthRequestBody + const instanceUrl = normalizeOptionalHomeAssistantUrl(body.instanceUrl) + const externalUrl = normalizeOptionalHomeAssistantUrl(body.externalUrl) + const resolvedInstanceUrl = instanceUrl ?? externalUrl + + if (!resolvedInstanceUrl) { + return Response.json( + { error: 'A Home Assistant local or remote URL is required.' }, + { status: 400 }, + ) + } + + const oauthState = buildHomeAssistantOauthState(request, resolvedInstanceUrl, externalUrl) + + const response = NextResponse.json({ + authorizeUrl: buildHomeAssistantAuthorizeUrl(oauthState), + }) + + response.cookies.set(HOME_ASSISTANT_OAUTH_COOKIE, JSON.stringify(oauthState), { + httpOnly: true, + maxAge: 10 * 60, + path: '/', + sameSite: 'lax', + secure: request.nextUrl.protocol === 'https:', + }) + + return response + } catch (error) { + const message = + error instanceof Error ? error.message : 'Failed to start Home Assistant sign-in.' + return Response.json({ error: message }, { status: 500 }) + } +} diff --git a/apps/editor/app/api/home-assistant/unlink/route.ts b/apps/editor/app/api/home-assistant/unlink/route.ts new file mode 100644 index 000000000..990b02d1d --- /dev/null +++ b/apps/editor/app/api/home-assistant/unlink/route.ts @@ -0,0 +1,13 @@ +import { clearLinkedHomeAssistantProfile } from '@pascal-app/home-assistant/server' + +export const runtime = 'nodejs' + +export async function DELETE() { + try { + await clearLinkedHomeAssistantProfile() + return Response.json({ success: true }) + } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to unlink Home Assistant.' + return Response.json({ error: message, success: false }, { status: 500 }) + } +} diff --git a/apps/editor/app/globals.css b/apps/editor/app/globals.css index 78ca5812e..643487698 100644 --- a/apps/editor/app/globals.css +++ b/apps/editor/app/globals.css @@ -1,6 +1,7 @@ @import "tailwindcss"; @import "tw-animate-css"; @source "../../../packages/editor/src"; +@source "../../../packages/home-assistant/src"; @custom-variant dark (&:is(.dark *)); diff --git a/apps/editor/next.config.ts b/apps/editor/next.config.mjs similarity index 57% rename from apps/editor/next.config.ts rename to apps/editor/next.config.mjs index 6c684f1bf..6bf3a4216 100644 --- a/apps/editor/next.config.ts +++ b/apps/editor/next.config.mjs @@ -1,6 +1,10 @@ -import type { NextConfig } from 'next' +import path from 'node:path' +import { fileURLToPath } from 'node:url' -const nextConfig: NextConfig = { +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +/** @type {import('next').NextConfig} */ +const nextConfig = { typescript: { ignoreBuildErrors: true, }, @@ -12,13 +16,22 @@ const nextConfig: NextConfig = { '@pascal-app/mcp', ], turbopack: { + root: path.resolve(__dirname, '../..'), resolveAlias: { - react: './node_modules/react', - three: './node_modules/three', '@react-three/fiber': './node_modules/@react-three/fiber', '@react-three/drei': './node_modules/@react-three/drei', }, }, + webpack: (config) => { + config.resolve ??= {} + config.resolve.alias = { + ...(config.resolve.alias ?? {}), + '@react-three/fiber': path.resolve(__dirname, 'node_modules/@react-three/fiber'), + '@react-three/drei': path.resolve(__dirname, 'node_modules/@react-three/drei'), + } + + return config + }, experimental: { serverActions: { bodySizeLimit: '100mb', diff --git a/apps/editor/package.json b/apps/editor/package.json index e8b713347..789b12724 100644 --- a/apps/editor/package.json +++ b/apps/editor/package.json @@ -14,6 +14,7 @@ "@number-flow/react": "^0.5.14", "@pascal-app/core": "*", "@pascal-app/editor": "*", + "@pascal-app/home-assistant": "*", "@pascal-app/mcp": "*", "@pascal-app/viewer": "*", "@react-three/drei": "^10.7.7", diff --git a/apps/pascal-homeassistant/app/globals.css b/apps/pascal-homeassistant/app/globals.css new file mode 100644 index 000000000..643487698 --- /dev/null +++ b/apps/pascal-homeassistant/app/globals.css @@ -0,0 +1,369 @@ +@import "tailwindcss"; +@import "tw-animate-css"; +@source "../../../packages/editor/src"; +@source "../../../packages/home-assistant/src"; + +@custom-variant dark (&:is(.dark *)); + +@theme { + --font-sans: + var(--font-barlow), var(--font-geist-sans), ui-sans-serif, system-ui, + sans-serif; + --font-mono: + var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, + Consolas, monospace; + --font-pixel: + var(--font-geist-pixel-square), var(--font-geist-mono), ui-monospace, + SFMono-Regular, Menlo, Monaco, Consolas, monospace; + --font-barlow: + var(--font-barlow), var(--font-geist-sans), ui-sans-serif, system-ui, + sans-serif; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-barlow), sans-serif; + --font-mono: var(--font-geist-mono), monospace; + --font-pixel: + var(--font-geist-pixel-square), var(--font-geist-mono), monospace; + --font-barlow: var(--font-barlow), sans-serif; + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +:root { + --radius: 0.625rem; + --background: oklch(0.998 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(0.998 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(0.998 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.205 0 0); /* ~171717 */ + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch( + 0.235 0 0 + ); /* slightly lighter than background (0.205) but darker than previous (0.269) */ + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.235 0 0); /* matching accent */ + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + button, + [role="button"], + a { + cursor: pointer; + } +} + +/* Apple-style smooth corners (squircle) — progressive enhancement */ +.rounded-smooth { + border-radius: var(--radius-lg); + corner-shape: squircle; +} +.rounded-smooth-xl { + border-radius: var(--radius-xl); + corner-shape: squircle; +} + +.no-scrollbar::-webkit-scrollbar { + display: none; +} + +.no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* Loaders */ +.pascal-loader-1 { + width: 45px; + aspect-ratio: 1; + --c: no-repeat linear-gradient(currentColor 0 0); + background: var(--c), var(--c), var(--c); + animation: + pascal-l1-1 1s infinite, + pascal-l1-2 1s infinite; +} +@keyframes pascal-l1-1 { + 0%, + 100% { + background-size: 20% 100%; + } + 33%, + 66% { + background-size: 20% 20%; + } +} +@keyframes pascal-l1-2 { + 0%, + 33% { + background-position: + 0 0, + 50% 50%, + 100% 100%; + } + 66%, + 100% { + background-position: + 100% 0, + 50% 50%, + 0 100%; + } +} + +.pascal-loader-2 { + width: 45px; + aspect-ratio: 0.75; + --c: no-repeat linear-gradient(currentColor 0 0); + background: + var(--c) 0% 50%, + var(--c) 50% 50%, + var(--c) 100% 50%; + background-size: 20% 50%; + animation: pascal-l2 1s infinite linear; +} +@keyframes pascal-l2 { + 20% { + background-position: + 0% 0%, + 50% 50%, + 100% 50%; + } + 40% { + background-position: + 0% 100%, + 50% 0%, + 100% 50%; + } + 60% { + background-position: + 0% 50%, + 50% 100%, + 100% 0%; + } + 80% { + background-position: + 0% 50%, + 50% 50%, + 100% 100%; + } +} + +.pascal-loader-3 { + width: 45px; + aspect-ratio: 0.75; + --c: no-repeat linear-gradient(currentColor 0 0); + background: + var(--c) 0% 100%, + var(--c) 50% 100%, + var(--c) 100% 100%; + background-size: 20% 65%; + animation: pascal-l3 1s infinite linear; +} +@keyframes pascal-l3 { + 16.67% { + background-position: + 0% 0%, + 50% 100%, + 100% 100%; + } + 33.33% { + background-position: + 0% 0%, + 50% 0%, + 100% 100%; + } + 50% { + background-position: + 0% 0%, + 50% 0%, + 100% 0%; + } + 66.67% { + background-position: + 0% 100%, + 50% 0%, + 100% 0%; + } + 83.33% { + background-position: + 0% 100%, + 50% 100%, + 100% 0%; + } +} + +.pascal-loader-4 { + width: 45px; + aspect-ratio: 1; + --c: no-repeat linear-gradient(currentColor 0 0); + background: var(--c), var(--c), var(--c); + animation: + pascal-l4-1 1s infinite, + pascal-l4-2 1s infinite; +} +@keyframes pascal-l4-1 { + 0%, + 100% { + background-size: 20% 100%; + } + 33%, + 66% { + background-size: 20% 40%; + } +} +@keyframes pascal-l4-2 { + 0%, + 33% { + background-position: + 0 0, + 50% 100%, + 100% 100%; + } + 66%, + 100% { + background-position: + 100% 0, + 0 100%, + 50% 100%; + } +} + +.pascal-loader-5 { + width: 45px; + aspect-ratio: 1; + --c: no-repeat linear-gradient(currentColor 0 0); + background: var(--c), var(--c), var(--c); + animation: + pascal-l5-1 1s infinite, + pascal-l5-2 1s infinite; +} +@keyframes pascal-l5-1 { + 0%, + 100% { + background-size: 20% 100%; + } + 33%, + 66% { + background-size: 20% 40%; + } +} +@keyframes pascal-l5-2 { + 0%, + 33% { + background-position: + 0 0, + 50% 100%, + 100% 0; + } + 66%, + 100% { + background-position: + 0 100%, + 50% 0, + 100% 100%; + } +} diff --git a/apps/pascal-homeassistant/app/layout.tsx b/apps/pascal-homeassistant/app/layout.tsx new file mode 100644 index 000000000..71a133ea1 --- /dev/null +++ b/apps/pascal-homeassistant/app/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from 'next' +import './globals.css' + +export const metadata: Metadata = { + title: 'Pascal Home Assistant', + description: 'Create a Pascal house locally and export it as a Home Assistant Lovelace card.', +} + +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ) +} diff --git a/apps/pascal-homeassistant/app/page.tsx b/apps/pascal-homeassistant/app/page.tsx new file mode 100644 index 000000000..ba0d78c57 --- /dev/null +++ b/apps/pascal-homeassistant/app/page.tsx @@ -0,0 +1,55 @@ +'use client' + +import { Editor, type SidebarTab, ViewerToolbarLeft, ViewerToolbarRight } from '@pascal-app/editor' +import { HardDrive, Home, Upload } from 'lucide-react' + +const SIDEBAR_TABS: (SidebarTab & { component: React.ComponentType })[] = [ + { + id: 'site', + label: 'Scene', + component: () => null, + }, + { + id: 'settings', + label: 'Settings', + component: () => null, + }, +] + +const PROJECT_ID = 'pascal-homeassistant-local' + +export default function HomeAssistantAuthoringPage() { + return ( +
+
+
+ + + Pascal Home Assistant + + + | + + + + Saved in this browser + + + | + + + + Export from Smart Home + +
+
+ } + viewerToolbarRight={} + /> +
+ ) +} diff --git a/apps/pascal-homeassistant/next-env.d.ts b/apps/pascal-homeassistant/next-env.d.ts new file mode 100644 index 000000000..9edff1c7c --- /dev/null +++ b/apps/pascal-homeassistant/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/pascal-homeassistant/next.config.mjs b/apps/pascal-homeassistant/next.config.mjs new file mode 100644 index 000000000..2265303b2 --- /dev/null +++ b/apps/pascal-homeassistant/next.config.mjs @@ -0,0 +1,41 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'export', + typescript: { + ignoreBuildErrors: true, + }, + transpilePackages: [ + 'three', + '@pascal-app/core', + '@pascal-app/editor', + '@pascal-app/home-assistant', + '@pascal-app/viewer', + ], + turbopack: { + root: path.resolve(__dirname, '../..'), + resolveAlias: { + '@react-three/fiber': './node_modules/@react-three/fiber', + '@react-three/drei': './node_modules/@react-three/drei', + }, + }, + webpack: (config) => { + config.resolve ??= {} + config.resolve.alias = { + ...(config.resolve.alias ?? {}), + '@react-three/fiber': path.resolve(__dirname, 'node_modules/@react-three/fiber'), + '@react-three/drei': path.resolve(__dirname, 'node_modules/@react-three/drei'), + } + + return config + }, + images: { + unoptimized: true, + }, +} + +export default nextConfig diff --git a/apps/pascal-homeassistant/package.json b/apps/pascal-homeassistant/package.json new file mode 100644 index 000000000..fd793687b --- /dev/null +++ b/apps/pascal-homeassistant/package.json @@ -0,0 +1,40 @@ +{ + "name": "pascal-homeassistant", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "build": "next build && node ./scripts/copy-editor-public.mjs", + "dev": "next dev --port 3012", + "start": "next start", + "check-types": "next typegen && tsc --noEmit" + }, + "dependencies": { + "@number-flow/react": "^0.5.14", + "@pascal-app/core": "*", + "@pascal-app/editor": "*", + "@pascal-app/home-assistant": "*", + "@pascal-app/viewer": "*", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.5.0", + "@tailwindcss/postcss": "^4.2.1", + "clsx": "^2.1.1", + "geist": "^1.7.0", + "lucide-react": "^0.562.0", + "next": "16.2.1", + "postcss": "^8.5.6", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.1", + "three": "^0.184.0", + "tw-animate-css": "^1.4.0" + }, + "devDependencies": { + "@pascal/typescript-config": "*", + "@types/node": "^22.19.12", + "@types/react": "19.2.2", + "@types/react-dom": "19.2.2", + "typescript": "5.9.3" + } +} diff --git a/apps/pascal-homeassistant/postcss.config.mjs b/apps/pascal-homeassistant/postcss.config.mjs new file mode 100644 index 000000000..a7f73a2d1 --- /dev/null +++ b/apps/pascal-homeassistant/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +} diff --git a/apps/pascal-homeassistant/scripts/copy-editor-public.mjs b/apps/pascal-homeassistant/scripts/copy-editor-public.mjs new file mode 100644 index 000000000..1460b4eb9 --- /dev/null +++ b/apps/pascal-homeassistant/scripts/copy-editor-public.mjs @@ -0,0 +1,14 @@ +import { cp, mkdir } from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const appDir = path.dirname(path.dirname(fileURLToPath(import.meta.url))) +const repoRoot = path.resolve(appDir, '../..') +const sourcePublic = path.join(repoRoot, 'apps/editor/public') +const outputPublic = path.join(appDir, 'out') + +await mkdir(outputPublic, { recursive: true }) +await cp(sourcePublic, outputPublic, { + recursive: true, + force: true, +}) diff --git a/apps/pascal-homeassistant/tsconfig.json b/apps/pascal-homeassistant/tsconfig.json new file mode 100644 index 000000000..70924e110 --- /dev/null +++ b/apps/pascal-homeassistant/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "@pascal/typescript-config/nextjs.json", + "compilerOptions": { + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "next-env.d.ts", + "next.config.js", + ".next/types/**/*.ts" + ], + "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx"], + "references": [ + { "path": "../../packages/core" }, + { "path": "../../packages/viewer" } + ] +} diff --git a/bun.lock b/bun.lock index c0e5fbbea..018346230 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "editor", @@ -19,6 +18,7 @@ "@number-flow/react": "^0.5.14", "@pascal-app/core": "*", "@pascal-app/editor": "*", + "@pascal-app/home-assistant": "*", "@pascal-app/mcp": "*", "@pascal-app/viewer": "*", "@react-three/drei": "^10.7.7", @@ -48,6 +48,38 @@ "typescript": "5.9.3", }, }, + "apps/pascal-homeassistant": { + "name": "pascal-homeassistant", + "version": "0.1.0", + "dependencies": { + "@number-flow/react": "^0.5.14", + "@pascal-app/core": "*", + "@pascal-app/editor": "*", + "@pascal-app/home-assistant": "*", + "@pascal-app/viewer": "*", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.5.0", + "@tailwindcss/postcss": "^4.2.1", + "clsx": "^2.1.1", + "geist": "^1.7.0", + "lucide-react": "^0.562.0", + "next": "16.2.1", + "postcss": "^8.5.6", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.1", + "three": "^0.184.0", + "tw-animate-css": "^1.4.0", + }, + "devDependencies": { + "@pascal/typescript-config": "*", + "@types/node": "^22.19.12", + "@types/react": "19.2.2", + "@types/react-dom": "19.2.2", + "typescript": "5.9.3", + }, + }, "packages/core": { "name": "@pascal-app/core", "version": "0.6.0", @@ -82,6 +114,7 @@ "@dnd-kit/utilities": "^3.2.2", "@iconify/react": "^6.0.2", "@number-flow/react": "^0.5.14", + "@pascal-app/home-assistant": "*", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.15", @@ -148,6 +181,52 @@ "typescript-eslint": "^8.50.0", }, }, + "packages/home-assistant": { + "name": "@pascal-app/home-assistant", + "version": "0.1.0", + "devDependencies": { + "@pascal-app/core": "^0.6.0", + "@pascal-app/viewer": "^0.6.0", + "@pascal/typescript-config": "*", + "@types/node": "^22.19.12", + "@types/react": "19.2.2", + "@types/react-dom": "19.2.2", + "@types/three": "^0.184.0", + "typescript": "5.9.3", + "zod": "^4.3.5", + }, + "peerDependencies": { + "@pascal-app/core": "^0.6.0", + "@pascal-app/viewer": "^0.6.0", + "@react-three/fiber": "^9", + "lucide-react": "^0.562.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19", + "three": "^0.184", + }, + }, + "packages/lovelace-card": { + "name": "@pascal-app/lovelace-card", + "version": "0.1.0", + "dependencies": { + "@pascal-app/core": "*", + "@pascal-app/home-assistant": "*", + "@pascal-app/viewer": "*", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.5.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "three": "^0.184.0", + "zustand": "^5.0.11", + }, + "devDependencies": { + "@pascal/typescript-config": "*", + "@types/react": "19.2.2", + "@types/react-dom": "19.2.2", + "@types/three": "^0.184.0", + "typescript": "5.9.3", + }, + }, "packages/mcp": { "name": "@pascal-app/mcp", "version": "0.1.0", @@ -218,6 +297,10 @@ "version": "0.0.0", }, }, + "overrides": { + "@types/three": "0.184.0", + "three": "0.184.0", + }, "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -483,6 +566,10 @@ "@pascal-app/editor": ["@pascal-app/editor@workspace:packages/editor"], + "@pascal-app/home-assistant": ["@pascal-app/home-assistant@workspace:packages/home-assistant"], + + "@pascal-app/lovelace-card": ["@pascal-app/lovelace-card@workspace:packages/lovelace-card"], + "@pascal-app/mcp": ["@pascal-app/mcp@workspace:packages/mcp"], "@pascal-app/viewer": ["@pascal-app/viewer@workspace:packages/viewer"], @@ -697,8 +784,6 @@ "@visual-json/react": ["@visual-json/react@0.4.0", "", { "dependencies": { "@visual-json/core": "0.4.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-qiDrXI7wu9VoTE0TaLoJp7kwRj8SQTpkS/wkts5sBPA8078zdrIKc+0u0Y8wAXGYd3udw9dNfD4kmpO48OEwXQ=="], - "@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="], - "@zappar/msdf-generator": ["@zappar/msdf-generator@1.2.4", "", { "dependencies": { "comlink": "^4.4.2" } }, "sha512-6S/MCk0Ky0ipewZJw4xFEzH/2aYfWmPXEkTdBtNyDDfkbicrNwgJgtxZ4SnTDyNe9XHMqDA4sL9srRsgDLRMqA=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], @@ -1271,6 +1356,8 @@ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "pascal-homeassistant": ["pascal-homeassistant@workspace:apps/pascal-homeassistant"], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -1567,6 +1654,10 @@ "@pascal-app/editor/@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + "@pascal-app/home-assistant/@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + + "@pascal-app/lovelace-card/@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + "@pascal-app/mcp/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], "@pascal-app/viewer/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], @@ -1641,6 +1732,8 @@ "ora/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "pascal-homeassistant/@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "react-scan/@types/node": ["@types/node@20.19.37", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw=="], @@ -1649,10 +1742,6 @@ "sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "stats-gl/@types/three": ["@types/three@0.183.1", "", { "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": ">=0.5.17", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~1.0.1" } }, "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw=="], - - "stats-gl/three": ["three@0.170.0", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="], - "three-stdlib/fflate": ["fflate@0.6.10", "", {}, "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="], "tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], @@ -1671,8 +1760,6 @@ "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "stats-gl/@types/three/meshoptimizer": ["meshoptimizer@1.0.1", "", {}, "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/dist/pascal-viewer-card.js b/dist/pascal-viewer-card.js new file mode 100644 index 000000000..24529ae5b --- /dev/null +++ b/dist/pascal-viewer-card.js @@ -0,0 +1,5548 @@ +var process = { env: { NEXT_PUBLIC_ASSETS_CDN_URL: 'https://editor.pascal.app', NODE_ENV: 'production' } }; +var BkA=Object.create;var{getPrototypeOf:qkA,defineProperty:Do,getOwnPropertyNames:YkA}=Object;var GkA=Object.prototype.hasOwnProperty;var u0=(A,Q,$)=>{$=A!=null?BkA(qkA(A)):{};let J=Q||!A||!A.__esModule?Do($,"default",{value:A,enumerable:!0}):$;for(let U of YkA(A))if(!GkA.call(J,U))Do(J,U,{get:()=>A[U],enumerable:!0});return J};var pG=(A,Q)=>()=>(Q||A((Q={exports:{}}).exports,Q),Q.exports);var uG=(A,Q)=>{for(var $ in Q)Do(A,$,{get:Q[$],enumerable:!0,configurable:!0,set:(J)=>Q[$]=()=>J})};var TQ=pG((XkA,Yg)=>{(function(){function A(l,BA){Object.defineProperty(J.prototype,l,{get:function(){console.warn("%s(...) is deprecated in plain JavaScript React classes. %s",BA[0],BA[1])}})}function Q(l){if(l===null||typeof l!=="object")return null;return l=x0&&l[x0]||l["@@iterator"],typeof l==="function"?l:null}function $(l,BA){l=(l=l.constructor)&&(l.displayName||l.name)||"ReactClass";var WA=l+"."+BA;vA[WA]||(console.error("Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the %s component.",BA,l),vA[WA]=!0)}function J(l,BA,WA){this.props=l,this.context=BA,this.refs=E0,this.updater=WA||c0}function U(){}function K(l,BA,WA){this.props=l,this.context=BA,this.refs=E0,this.updater=WA||c0}function E(){}function B(l){return""+l}function Y(l){try{B(l);var BA=!1}catch(yA){BA=!0}if(BA){BA=console;var WA=BA.error,IA=typeof Symbol==="function"&&Symbol.toStringTag&&l[Symbol.toStringTag]||l.constructor.name||"Object";return WA.call(BA,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",IA),B(l)}}function X(l){if(l==null)return null;if(typeof l==="function")return l.$$typeof===C0?null:l.displayName||l.name||null;if(typeof l==="string")return l;switch(l){case MA:return"Fragment";case FA:return"Profiler";case JA:return"StrictMode";case TA:return"Suspense";case nA:return"SuspenseList";case q0:return"Activity"}if(typeof l==="object")switch(typeof l.tag==="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),l.$$typeof){case XA:return"Portal";case bA:return l.displayName||"Context";case OA:return(l._context.displayName||"Context")+".Consumer";case rA:var BA=l.render;return l=l.displayName,l||(l=BA.displayName||BA.name||"",l=l!==""?"ForwardRef("+l+")":"ForwardRef"),l;case K0:return BA=l.displayName||null,BA!==null?BA:X(l.type)||"Memo";case A0:BA=l._payload,l=l._init;try{return X(l(BA))}catch(WA){}}return null}function Z(l){if(l===MA)return"<>";if(typeof l==="object"&&l!==null&&l.$$typeof===A0)return"<...>";try{var BA=X(l);return BA?"<"+BA+">":"<...>"}catch(WA){return"<...>"}}function C(){var l=k0.A;return l===null?null:l.getOwner()}function W(){return Error("react-stack-top-frame")}function N(l){if(VA.call(l,"key")){var BA=Object.getOwnPropertyDescriptor(l,"key").get;if(BA&&BA.isReactWarning)return!1}return l.key!==void 0}function V(l,BA){function WA(){m0||(m0=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",BA))}WA.isReactWarning=!0,Object.defineProperty(l,"key",{get:WA,configurable:!0})}function z(){var l=X(this.type);return N0[l]||(N0[l]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),l=this.props.ref,l!==void 0?l:null}function D(l,BA,WA,IA,yA,uA){var GA=WA.ref;return l={$$typeof:UA,type:l,key:BA,props:WA,_owner:IA},(GA!==void 0?GA:null)!==null?Object.defineProperty(l,"ref",{enumerable:!1,get:z}):Object.defineProperty(l,"ref",{enumerable:!1,value:null}),l._store={},Object.defineProperty(l._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(l,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(l,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:yA}),Object.defineProperty(l,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:uA}),Object.freeze&&(Object.freeze(l.props),Object.freeze(l)),l}function k(l,BA){return BA=D(l.type,BA,l.props,l._owner,l._debugStack,l._debugTask),l._store&&(BA._store.validated=l._store.validated),BA}function P(l){R(l)?l._store&&(l._store.validated=1):typeof l==="object"&&l!==null&&l.$$typeof===A0&&(l._payload.status==="fulfilled"?R(l._payload.value)&&l._payload.value._store&&(l._payload.value._store.validated=1):l._store&&(l._store.validated=1))}function R(l){return typeof l==="object"&&l!==null&&l.$$typeof===UA}function L(l){var BA={"=":"=0",":":"=2"};return"$"+l.replace(/[=:]/g,function(WA){return BA[WA]})}function w(l,BA){return typeof l==="object"&&l!==null&&l.key!=null?(Y(l.key),L(""+l.key)):BA.toString(36)}function _(l){switch(l.status){case"fulfilled":return l.value;case"rejected":throw l.reason;default:switch(typeof l.status==="string"?l.then(E,E):(l.status="pending",l.then(function(BA){l.status==="pending"&&(l.status="fulfilled",l.value=BA)},function(BA){l.status==="pending"&&(l.status="rejected",l.reason=BA)})),l.status){case"fulfilled":return l.value;case"rejected":throw l.reason}}throw l}function b(l,BA,WA,IA,yA){var uA=typeof l;if(uA==="undefined"||uA==="boolean")l=null;var GA=!1;if(l===null)GA=!0;else switch(uA){case"bigint":case"string":case"number":GA=!0;break;case"object":switch(l.$$typeof){case UA:case XA:GA=!0;break;case A0:return GA=l._init,b(GA(l._payload),BA,WA,IA,yA)}}if(GA){GA=l,yA=yA(GA);var wA=IA===""?"."+w(GA,0):IA;return B0(yA)?(WA="",wA!=null&&(WA=wA.replace(KA,"$&/")+"/"),b(yA,BA,WA,"",function(H0){return H0})):yA!=null&&(R(yA)&&(yA.key!=null&&(GA&&GA.key===yA.key||Y(yA.key)),WA=k(yA,WA+(yA.key==null||GA&&GA.key===yA.key?"":(""+yA.key).replace(KA,"$&/")+"/")+wA),IA!==""&&GA!=null&&R(GA)&&GA.key==null&&GA._store&&!GA._store.validated&&(WA._store.validated=2),yA=WA),BA.push(yA)),1}if(GA=0,wA=IA===""?".":IA+":",B0(l))for(var RA=0;RA import('./MyComponent')) + +Did you accidentally put curly braces around the import?`,BA),"default"in BA||console.error(`lazy: Expected the result of a dynamic import() call. Instead received: %s + +Your code should look like: + const MyComponent = lazy(() => import('./MyComponent'))`,BA),BA.default;throw l._result}function x(){var l=k0.H;return l===null&&console.error(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: +1. You might have mismatching versions of React and the renderer (such as React DOM) +2. You might be breaking the Rules of Hooks +3. You might have more than one copy of React in the same app +See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.`),l}function f(){k0.asyncTransitions--}function p(l){if(_0===null)try{var BA=("require"+Math.random()).slice(0,7);_0=(Yg&&Yg[BA]).call(Yg,"timers").setImmediate}catch(WA){_0=function(IA){J0===!1&&(J0=!0,typeof MessageChannel>"u"&&console.error("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var yA=new MessageChannel;yA.port1.onmessage=IA,yA.port2.postMessage(void 0)}}return _0(l)}function m(l){return 1 ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),{then:function(RA,H0){yA=!0,GA.then(function(P0){if(d(BA,WA),WA===0){try{t(IA),p(function(){return o(P0,RA,H0)})}catch(o0){k0.thrownErrors.push(o0)}if(0 ...)"))}),k0.actQueue=null),0k0.recentlyCreatedOwnerStacks++;return D(l,yA,IA,C(),RA?Error("react-stack-top-frame"):xQ,RA?g0(Z(l)):p0)},XkA.createRef=function(){var l={current:null};return Object.seal(l),l},XkA.forwardRef=function(l){l!=null&&l.$$typeof===K0?console.error("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof l!=="function"?console.error("forwardRef requires a render function but was given %s.",l===null?"null":typeof l):l.length!==0&&l.length!==2&&console.error("forwardRef render functions accept exactly two parameters: props and ref. %s",l.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),l!=null&&l.defaultProps!=null&&console.error("forwardRef render functions do not support defaultProps. Did you accidentally pass a React component?");var BA={$$typeof:rA,render:l},WA;return Object.defineProperty(BA,"displayName",{enumerable:!1,configurable:!0,get:function(){return WA},set:function(IA){WA=IA,l.name||l.displayName||(Object.defineProperty(l,"name",{value:IA}),l.displayName=IA)}}),BA},XkA.isValidElement=R,XkA.lazy=function(l){l={_status:-1,_result:l};var BA={$$typeof:A0,_payload:l,_init:y},WA={name:"lazy",start:-1,end:-1,value:null,owner:null,debugStack:Error("react-stack-top-frame"),debugTask:console.createTask?console.createTask("lazy()"):null};return l._ioInfo=WA,BA._debugInfo=[{awaited:WA}],BA},XkA.memo=function(l,BA){l==null&&console.error("memo: The first argument must be a component. Instead received: %s",l===null?"null":typeof l),BA={$$typeof:K0,type:l,compare:BA===void 0?null:BA};var WA;return Object.defineProperty(BA,"displayName",{enumerable:!1,configurable:!0,get:function(){return WA},set:function(IA){WA=IA,l.name||l.displayName||(Object.defineProperty(l,"name",{value:IA}),l.displayName=IA)}}),BA},XkA.startTransition=function(l){var BA=k0.T,WA={};WA._updatedFibers=new Set,k0.T=WA;try{var IA=l(),yA=k0.S;yA!==null&&yA(WA,IA),typeof IA==="object"&&IA!==null&&typeof IA.then==="function"&&(k0.asyncTransitions++,IA.then(f,f),IA.then(E,xA))}catch(uA){xA(uA)}finally{BA===null&&WA._updatedFibers&&(l=WA._updatedFibers.size,WA._updatedFibers.clear(),10{(function(){function A(){if(L=!1,T){var o=ZkA.unstable_now();f=o;var t=!0;try{A:{P=!1,R&&(R=!1,_(y),y=-1),k=!0;var UA=D;try{Q:{K(o);for(z=$(W);z!==null&&!(z.expirationTime>o&&B());){var XA=z.callback;if(typeof XA==="function"){z.callback=null,D=z.priorityLevel;var MA=XA(z.expirationTime<=o);if(o=ZkA.unstable_now(),typeof MA==="function"){z.callback=MA,K(o),t=!0;break Q}z===$(W)&&J(W),K(o)}else J(W);z=$(W)}if(z!==null)t=!0;else{var JA=$(N);JA!==null&&Y(E,JA.startTime-o),t=!1}}break A}finally{z=null,D=UA,k=!1}t=void 0}}finally{t?p():T=!1}}}function Q(o,t){var UA=o.length;o.push(t);A:for(;0>>1,MA=o[XA];if(0>>1;XAU(OA,UA))bAU(rA,OA)?(o[XA]=rA,o[bA]=UA,XA=bA):(o[XA]=OA,o[FA]=UA,XA=FA);else if(bAU(rA,UA))o[XA]=rA,o[bA]=UA,XA=bA;else break A}}return t}function U(o,t){var UA=o.sortIndex-t.sortIndex;return UA!==0?UA:o.id-t.id}function K(o){for(var t=$(N);t!==null;){if(t.callback===null)J(N);else if(t.startTime<=o)J(N),t.sortIndex=t.expirationTime,Q(W,t);else break;t=$(N)}}function E(o){if(R=!1,K(o),!P)if($(W)!==null)P=!0,T||(T=!0,p());else{var t=$(N);t!==null&&Y(E,t.startTime-o)}}function B(){return L?!0:ZkA.unstable_now()-fo||125XA?(o.sortIndex=UA,Q(N,o),$(W)===null&&o===$(N)&&(R?(_(y),y=-1):R=!0,Y(E,UA-XA))):(o.sortIndex=MA,Q(W,o),P||k||(P=!0,T||(T=!0,p()))),o},ZkA.unstable_shouldYield=B,ZkA.unstable_wrapCallback=function(o){var t=D;return function(){var UA=D;D=t;try{return o.apply(this,arguments)}finally{D=UA}}},typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop==="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error())})()});var U1A=pG((FkA)=>{var Ro=u0(TQ());(function(){function A(){}function Q(Z){return""+Z}function $(Z,C,W){var N=3` tag.%s',W),typeof Z==="string"&&typeof C==="object"&&C!==null&&typeof C.as==="string"){W=C.as;var N=J(W,C.crossOrigin);B.d.L(Z,W,{crossOrigin:N,integrity:typeof C.integrity==="string"?C.integrity:void 0,nonce:typeof C.nonce==="string"?C.nonce:void 0,type:typeof C.type==="string"?C.type:void 0,fetchPriority:typeof C.fetchPriority==="string"?C.fetchPriority:void 0,referrerPolicy:typeof C.referrerPolicy==="string"?C.referrerPolicy:void 0,imageSrcSet:typeof C.imageSrcSet==="string"?C.imageSrcSet:void 0,imageSizes:typeof C.imageSizes==="string"?C.imageSizes:void 0,media:typeof C.media==="string"?C.media:void 0})}},FkA.preloadModule=function(Z,C){var W="";typeof Z==="string"&&Z||(W+=" The `href` argument encountered was "+U(Z)+"."),C!==void 0&&typeof C!=="object"?W+=" The `options` argument encountered was "+U(C)+".":C&&("as"in C)&&typeof C.as!=="string"&&(W+=" The `as` option encountered was "+U(C.as)+"."),W&&console.error('ReactDOM.preloadModule(): Expected two arguments, a non-empty `href` string and, optionally, an `options` object with an `as` property valid for a `` tag.%s',W),typeof Z==="string"&&(C?(W=J(C.as,C.crossOrigin),B.d.m(Z,{as:typeof C.as==="string"&&C.as!=="script"?C.as:void 0,crossOrigin:W,integrity:typeof C.integrity==="string"?C.integrity:void 0})):B.d.m(Z))},FkA.requestFormReset=function(Z){B.d.r(Z)},FkA.unstable_batchedUpdates=function(Z,C){return Z(C)},FkA.useFormState=function(Z,C,W){return E().useFormState(Z,C,W)},FkA.useFormStatus=function(){return E().useHostTransitionStatus()},FkA.version="19.2.4",typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop==="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error())})()});var _o=pG((qQ0,K1A)=>{K1A.exports=U1A()});var E1A=pG((CkA)=>{var l8=u0(Gg()),Fz=u0(TQ()),zo=u0(_o());(function(){function A(q,G){for(q=q.memoizedState;q!==null&&0=G.length)return I;var S=G[M],v=s8(q)?q.slice():R$({},q);return v[S]=Q(q[S],G,M+1,I),v}function $(q,G,M){if(G.length!==M.length)console.warn("copyWithRename() expects paths of the same length");else{for(var I=0;IVB?console.error("Unexpected pop."):(G!==ZW[VB]&&console.error("Unexpected Fiber popped."),q.current=$$[VB],$$[VB]=null,ZW[VB]=null,VB--)}function m(q,G,M){VB++,$$[VB]=q.current,ZW[VB]=M,q.current=G}function d(q){return q===null&&console.error("Expected host context to exist. This error is likely caused by a bug in React. Please file an issue."),q}function o(q,G){m(OB,G,q),m(IZ,q,q),m(KY,null,q);var M=G.nodeType;switch(M){case 9:case 11:M=M===9?"#document":"#fragment",G=(G=G.documentElement)?(G=G.namespaceURI)?hb(G):pC:pC;break;default:if(M=G.tagName,G=G.namespaceURI)G=hb(G),G=pb(G,M);else switch(M){case"svg":G=Xz;break;case"math":G=$g;break;default:G=pC}}M=M.toLowerCase(),M=X6(null,M),M={context:G,ancestorInfo:M},p(KY,q),m(KY,M,q)}function t(q){p(KY,q),p(IZ,q),p(OB,q)}function UA(){return d(KY.current)}function XA(q){q.memoizedState!==null&&m(DG,q,q);var G=d(KY.current),M=q.type,I=pb(G.context,M);M=X6(G.ancestorInfo,M),I={context:I,ancestorInfo:M},G!==I&&(m(IZ,q,q),m(KY,I,q))}function MA(q){IZ.current===q&&(p(KY,q),p(IZ,q)),DG.current===q&&(p(DG,q),TS._currentValue=fO)}function JA(){}function FA(){if(EY===0){Kx=console.log,Ex=console.info,Bx=console.warn,ZJ=console.error,Q9=console.group,UO=console.groupCollapsed,yU=console.groupEnd;var q={configurable:!0,enumerable:!0,value:JA,writable:!0};Object.defineProperties(console,{info:q,log:q,warn:q,error:q,group:q,groupCollapsed:q,groupEnd:q})}EY++}function OA(){if(EY--,EY===0){var q={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:R$({},q,{value:Kx}),info:R$({},q,{value:Ex}),warn:R$({},q,{value:Bx}),error:R$({},q,{value:ZJ}),group:R$({},q,{value:Q9}),groupCollapsed:R$({},q,{value:UO}),groupEnd:R$({},q,{value:yU})})}0>EY&&console.error("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}function bA(q){var G=Error.prepareStackTrace;if(Error.prepareStackTrace=void 0,q=q.stack,Error.prepareStackTrace=G,q.startsWith(`Error: react-stack-top-frame +`)&&(q=q.slice(29)),G=q.indexOf(` +`),G!==-1&&(q=q.slice(G+1)),G=q.indexOf("react_stack_bottom_frame"),G!==-1&&(G=q.lastIndexOf(` +`,G)),G!==-1)q=q.slice(0,G);else return"";return q}function rA(q){if(BY===void 0)try{throw Error()}catch(M){var G=M.stack.trim().match(/\n( *(at )?)/);BY=G&&G[1]||"",qY=-1)":-1u||YA[v]!==lA[u]){var dA=` +`+YA[v].replace(" at new "," at ");return q.displayName&&dA.includes("")&&(dA=dA.replace("",q.displayName)),typeof q==="function"&&bU.set(q,dA),dA}while(1<=v&&0<=u);break}}}finally{LB=!1,W0.H=I,OA(),Error.prepareStackTrace=M}return YA=(YA=q?q.displayName||q.name:"")?rA(YA):"",typeof q==="function"&&bU.set(q,YA),YA}function nA(q,G){switch(q.tag){case 26:case 27:case 5:return rA(q.type);case 16:return rA("Lazy");case 13:return q.child!==G&&G!==null?rA("Suspense Fallback"):rA("Suspense");case 19:return rA("SuspenseList");case 0:case 15:return TA(q.type,!1);case 11:return TA(q.type.render,!1);case 1:return TA(q.type,!0);case 31:return rA("Activity");default:return""}}function K0(q){try{var G="",M=null;do{G+=nA(q,M);var I=q._debugInfo;if(I)for(var S=I.length-1;0<=S;S--){var v=I[S];if(typeof v.name==="string"){var u=G;A:{var{name:c,env:$A,debugLocation:YA}=v;if(YA!=null){var lA=bA(YA),dA=lA.lastIndexOf(` +`),kA=dA===-1?lA:lA.slice(dA+1);if(kA.indexOf(c)!==-1){var G0=` +`+kA;break A}}G0=rA(c+($A?" ["+$A+"]":""))}G=u+G0}}M=q,q=q.return}while(q);return G}catch(ZQ){return` +Error generating stack: `+ZQ.message+` +`+ZQ.stack}}function A0(q){return(q=q?q.displayName||q.name:"")?rA(q):""}function q0(){if(f9===null)return null;var q=f9._debugOwner;return q!=null?y(q):null}function x0(){if(f9===null)return"";var q=f9;try{var G="";switch(q.tag===6&&(q=q.return),q.tag){case 26:case 27:case 5:G+=rA(q.type);break;case 13:G+=rA("Suspense");break;case 19:G+=rA("SuspenseList");break;case 31:G+=rA("Activity");break;case 30:case 0:case 15:case 1:q._debugOwner||G!==""||(G+=A0(q.type));break;case 11:q._debugOwner||G!==""||(G+=A0(q.type.render))}for(;q;)if(typeof q.tag==="number"){var M=q;q=M._debugOwner;var I=M._debugStack;if(q&&I){var S=bA(I);S!==""&&(G+=` +`+S)}}else if(q.debugStack!=null){var v=q.debugStack;(q=q.owner)&&v&&(G+=` +`+bA(v))}else break;var u=G}catch(c){u=` +Error generating stack: `+c.message+` +`+c.stack}return u}function vA(q,G,M,I,S,v,u){var c=f9;c0(q);try{return q!==null&&q._debugTask?q._debugTask.run(G.bind(null,M,I,S,v,u)):G(M,I,S,v,u)}finally{c0(c)}throw Error("runWithFiberInDEV should never be called in production. This is a bug in React.")}function c0(q){W0.getCurrentStack=q===null?null:x0,y8=!1,f9=q}function tA(q){return typeof Symbol==="function"&&Symbol.toStringTag&&q[Symbol.toStringTag]||q.constructor.name||"Object"}function E0(q){try{return fA(q),!1}catch(G){return!0}}function fA(q){return""+q}function B0(q,G){if(E0(q))return console.error("The provided `%s` attribute is an unsupported type %s. This value must be coerced to a string before using it here.",G,tA(q)),fA(q)}function C0(q,G){if(E0(q))return console.error("The provided `%s` CSS property is an unsupported type %s. This value must be coerced to a string before using it here.",G,tA(q)),fA(q)}function k0(q){if(E0(q))return console.error("Form field values (value, checked, defaultValue, or defaultChecked props) must be strings, not %s. This value must be coerced to a string before using it here.",tA(q)),fA(q)}function VA(q){if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u")return!1;var G=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(G.isDisabled)return!0;if(!G.supportsFiber)return console.error("The installed version of React DevTools is too old and will not work with the current version of React. Please update React DevTools. https://react.dev/link/react-devtools"),!0;try{OE=G.inject(q),FJ=G}catch(M){console.error("React instrumentation encountered an error: %o.",M)}return G.checkDCE?!0:!1}function g0(q){if(typeof FW==="function"&&P_(q),FJ&&typeof FJ.setStrictMode==="function")try{FJ.setStrictMode(OE,q)}catch(G){$9||($9=!0,console.error("React instrumentation encountered an error: %o",G))}}function m0(q){return q>>>=0,q===0?32:31-(qx(q)/Yx|0)|0}function R0(q){var G=q&42;if(G!==0)return G;switch(q&-q){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return q&261888;case 262144:case 524288:case 1048576:case 2097152:return q&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return q&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return console.error("Should have found matching lanes. This is a bug in React."),q}}function N0(q,G,M){var I=q.pendingLanes;if(I===0)return 0;var S=0,v=q.suspendedLanes,u=q.pingedLanes;q=q.warmLanes;var c=I&134217727;return c!==0?(I=c&~v,I!==0?S=R0(I):(u&=c,u!==0?S=R0(u):M||(M=c&~q,M!==0&&(S=R0(M))))):(c=I&~v,c!==0?S=R0(c):u!==0?S=R0(u):M||(M=I&~q,M!==0&&(S=R0(M)))),S===0?0:G!==0&&G!==S&&(G&v)===0&&(v=S&-S,M=G&-G,v>=M||v===32&&(M&4194048)!==0)?G:S}function xQ(q,G){return(q.pendingLanes&~(q.suspendedLanes&~q.pingedLanes)&G)===0}function p0(q,G){switch(q){case 1:case 2:case 4:case 8:case 64:return G+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return G+5000;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return console.error("Should have found matching lanes. This is a bug in React."),-1}}function HA(){var q=DB;return DB<<=1,(DB&62914560)===0&&(DB=4194304),q}function KA(q){for(var G=[],M=0;31>M;M++)G.push(q);return G}function xA(q,G){q.pendingLanes|=G,G!==268435456&&(q.suspendedLanes=0,q.pingedLanes=0,q.warmLanes=0)}function J0(q,G,M,I,S,v){var u=q.pendingLanes;q.pendingLanes=M,q.suspendedLanes=0,q.pingedLanes=0,q.warmLanes=0,q.expiredLanes&=M,q.entangledLanes&=M,q.errorRecoveryDisabledLanes&=M,q.shellSuspendCounter=0;var{entanglements:c,expirationTimes:$A,hiddenUpdates:YA}=q;for(M=u&~M;0"u")return null;try{return q.activeElement||q.body}catch(G){return q.body}}function T8(q){return q.replace(PB,function(G){return"\\"+G.charCodeAt(0).toString(16)+" "})}function H9(q,G){G.checked===void 0||G.defaultChecked===void 0||WW||(console.error("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://react.dev/link/controlled-components",q0()||"A component",G.type),WW=!0),G.value===void 0||G.defaultValue===void 0||HW||(console.error("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://react.dev/link/controlled-components",q0()||"A component",G.type),HW=!0)}function sJ(q,G,M,I,S,v,u,c){if(q.name="",u!=null&&typeof u!=="function"&&typeof u!=="symbol"&&typeof u!=="boolean"?(B0(u,"type"),q.type=u):q.removeAttribute("type"),G!=null)if(u==="number"){if(G===0&&q.value===""||q.value!=G)q.value=""+B$(G)}else q.value!==""+B$(G)&&(q.value=""+B$(G));else u!=="submit"&&u!=="reset"||q.removeAttribute("value");G!=null?xK(q,u,B$(G)):M!=null?xK(q,u,B$(M)):I!=null&&q.removeAttribute("value"),S==null&&v!=null&&(q.defaultChecked=!!v),S!=null&&(q.checked=S&&typeof S!=="function"&&typeof S!=="symbol"),c!=null&&typeof c!=="function"&&typeof c!=="symbol"&&typeof c!=="boolean"?(B0(c,"name"),q.name=""+B$(c)):q.removeAttribute("name")}function bK(q,G,M,I,S,v,u,c){if(v!=null&&typeof v!=="function"&&typeof v!=="symbol"&&typeof v!=="boolean"&&(B0(v,"type"),q.type=v),G!=null||M!=null){if(!(v!=="submit"&&v!=="reset"||G!==void 0&&G!==null)){T6(q);return}M=M!=null?""+B$(M):"",G=G!=null?""+B$(G):M,c||G===q.value||(q.value=G),q.defaultValue=G}I=I!=null?I:S,I=typeof I!=="function"&&typeof I!=="symbol"&&!!I,q.checked=c?q.checked:!!I,q.defaultChecked=!!I,u!=null&&typeof u!=="function"&&typeof u!=="symbol"&&typeof u!=="boolean"&&(B0(u,"name"),q.name=u),T6(q)}function xK(q,G,M){G==="number"&&G6(q.ownerDocument)===q||q.defaultValue===""+M||(q.defaultValue=""+M)}function yq(q,G){G.value==null&&(typeof G.children==="object"&&G.children!==null?Fz.Children.forEach(G.children,function(M){M==null||typeof M==="string"||typeof M==="number"||typeof M==="bigint"||NW||(NW=!0,console.error("Cannot infer the option value of complex children. Pass a `value` prop or use a plain string as children to